diff --git a/client/src/hooks/Chat/parts.tsx b/client/src/Providers/MessagePartsHostContext.tsx similarity index 99% rename from client/src/hooks/Chat/parts.tsx rename to client/src/Providers/MessagePartsHostContext.tsx index f37cce90333..0dae1dc782a 100644 --- a/client/src/hooks/Chat/parts.tsx +++ b/client/src/Providers/MessagePartsHostContext.tsx @@ -14,7 +14,7 @@ import type { MessagePartsUserTextPreferences, MessagePartMessage, MessagePartsHost, -} from './contract'; +} from '~/hooks/Chat/contract'; import type { Artifact } from '~/common'; import { liveAppliedSteerFamily, diff --git a/client/src/common/index.ts b/client/src/common/index.ts index e1a3ab0a052..98ef815d58f 100644 --- a/client/src/common/index.ts +++ b/client/src/common/index.ts @@ -3,6 +3,7 @@ export * from './artifacts'; export * from './types'; export * from './menus'; export * from './tools'; +export * from './ptc'; export * from './selector'; export * from './assistants-types'; export * from './agents-types'; diff --git a/client/src/common/ptc.ts b/client/src/common/ptc.ts new file mode 100644 index 00000000000..bf3047d23d8 --- /dev/null +++ b/client/src/common/ptc.ts @@ -0,0 +1,29 @@ +import type { PtcToolCallStatus } from 'librechat-data-provider'; + +/** + * A row's outcome. Widens the wire status with `interrupted`, which the + * backend never sends: it is what the client concludes locally about a call + * that was still running when a stream gap swallowed its settling event. + */ +export type PtcTraceStatus = PtcToolCallStatus | 'interrupted'; + +/** One tool call a programmatic (PTC) program made from inside the sandbox. */ +export interface PtcTraceEntry { + /** Stable id from the backend; the settle event updates this row in place. */ + callId: string; + /** Inner tool id, e.g. `search_code_mcp_github`. */ + name: string; + status: PtcTraceStatus; + /** `key=value` preview of the call's input. */ + args?: string; + /** Truncated failure message on a failed call. */ + error?: string; + durationMs?: number; +} + +/** One PTC program's trace: the retained tail plus what the cap discarded. */ +export interface PtcTrace { + entries: PtcTraceEntry[]; + /** Rows evicted by the trace cap (`PTC_TRACE_MAX_ENTRIES`), so the cap is never silent. */ + dropped: number; +} diff --git a/client/src/components/Chat/Messages/Content/Parts/BashCall.tsx b/client/src/components/Chat/Messages/Content/Parts/BashCall.tsx index 3206feb18b8..69d0e721cca 100644 --- a/client/src/components/Chat/Messages/Content/Parts/BashCall.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/BashCall.tsx @@ -4,10 +4,10 @@ import type { TAttachment, PartMetadata } from 'librechat-data-provider'; import { parseBackgroundHandle, splitBackgroundAttachments } from './handle'; import ProgressText from '~/components/Chat/Messages/Content/ProgressText'; import parseJsonField, { areToolCallArgsComplete } from './parseJsonField'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import CopyButton from '~/components/Messages/Content/CopyButton'; import LangIcon from '~/components/Messages/Content/LangIcon'; import { toolPanelSpacingClassName } from '../disclosure'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import useToolCallState from './useToolCallState'; import useLazyHighlight from './useLazyHighlight'; import useFollowScroll from './useFollowScroll'; diff --git a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx index 5bb04238d65..d3cfa66cf84 100644 --- a/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/ExecuteCode.tsx @@ -3,8 +3,8 @@ import { SquareTerminal } from 'lucide-react'; import type { TAttachment, PartMetadata } from 'librechat-data-provider'; import { parseBackgroundHandle, splitBackgroundAttachments } from './handle'; import ProgressText from '~/components/Chat/Messages/Content/ProgressText'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { toolPanelSpacingClassName } from '../disclosure'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import useLazyHighlight from './useLazyHighlight'; import useToolCallState from './useToolCallState'; import CodeWindowHeader from './CodeWindowHeader'; diff --git a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx index 7bfeab1f09a..2b565d0a08d 100644 --- a/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/LogLink.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { FileSources, sharedFileDownload } from 'librechat-data-provider'; import { getDownloadFilename, isHttpDownloadTarget, triggerDownload } from '~/utils'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { useCodeOutputDownload, useFileDownload } from '~/data-provider'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import useLocalize from '~/hooks/useLocalize'; import { useShareContext } from '~/Providers'; diff --git a/client/src/components/Chat/Messages/Content/Parts/PendingSteers.tsx b/client/src/components/Chat/Messages/Content/Parts/PendingSteers.tsx index 8abddd2b578..bb0f5cdbdb2 100644 --- a/client/src/components/Chat/Messages/Content/Parts/PendingSteers.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/PendingSteers.tsx @@ -5,11 +5,11 @@ import { QueryKeys, type TMessage } from 'librechat-data-provider'; import type { SteerReceiptState } from '~/components/Chat/Steering/Receipt'; import useSteerCancel, { useSteerMoveToQueue, useSteerRehome } from '~/hooks/Chat/useSteerCancel'; import EscalateNowButton from '~/components/Chat/Input/EscalateNowButton'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import useSteerEscalate from '~/hooks/Chat/useSteerEscalate'; import useSteerRecovery from '~/hooks/Chat/useSteerRecovery'; import { hasLiveRunPause } from '~/hooks/Chat/useSteering'; import { useGetMessagesByConvoId } from '~/data-provider'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { cn, isLegacyDeliveryUncertain } from '~/utils'; import { useLatestMessage } from '~/hooks/Messages'; import { useLocalize } from '~/hooks'; diff --git a/client/src/components/Chat/Messages/Content/Parts/PtcToolTrace.tsx b/client/src/components/Chat/Messages/Content/Parts/PtcToolTrace.tsx index 0f237c5ed7f..f5788b4a02e 100644 --- a/client/src/components/Chat/Messages/Content/Parts/PtcToolTrace.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/PtcToolTrace.tsx @@ -1,9 +1,9 @@ import { useTranslation } from 'react-i18next'; import { isReportableRunStepDuration } from 'librechat-data-provider'; -import type { PtcTraceEntry } from '~/hooks/Chat/contract'; import type { TranslationKeys } from '~/hooks'; +import type { PtcTraceEntry } from '~/common'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { cn, parseToolName, getRunStepDurationLabels } from '~/utils'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { useMCPServerNames } from '~/hooks/MCP'; import useFollowScroll from './useFollowScroll'; import { useLocalize } from '~/hooks'; diff --git a/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx b/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx index 6486838e8c4..8d445e82814 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx @@ -13,9 +13,9 @@ import { useInViewport, } from './Thinking'; import { useLocalize, useExpandCollapse, useLazyCollapseBody } from '~/hooks'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import useSmoothStreaming from '~/hooks/Messages/useSmoothStreaming'; import CopyButton from '~/components/Messages/Content/CopyButton'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { useReasoningDisclosure } from '../disclosure'; import { ROW_GLYPH_SLOT } from '../rows'; import { cn } from '~/utils'; diff --git a/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx b/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx index 223a8853a2a..43fad4cd362 100644 --- a/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx @@ -6,9 +6,9 @@ import MessageTimestamp from '~/components/Chat/Messages/ui/MessageTimestamp'; import MessageQuotes from '~/components/Chat/Messages/Content/MessageQuotes'; import { cn, hydrateFileDeliveryMetadata, usesImagePreview } from '~/utils'; import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import FileContainer from '~/components/Chat/Input/Files/FileContainer'; import Image from '~/components/Chat/Messages/Content/Image'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import CollapsibleText from './CollapsibleText'; import { useShareContext } from '~/Providers'; import { useLocalize } from '~/hooks'; diff --git a/client/src/components/Chat/Messages/Content/Parts/Summary.tsx b/client/src/components/Chat/Messages/Content/Parts/Summary.tsx index ca4317c339f..f29f8e7078f 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Summary.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Summary.tsx @@ -4,7 +4,7 @@ import { ScrollText, ChevronDown, ChevronUp } from 'lucide-react'; import { Button, MorphIcon, TooltipAnchor } from '@librechat/client'; import type { SummaryContentPart } from 'librechat-data-provider'; import type { MouseEvent, FocusEvent } from 'react'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { useLocalize } from '~/hooks'; import { cn } from '~/utils'; diff --git a/client/src/components/Chat/Messages/Content/Parts/Text.tsx b/client/src/components/Chat/Messages/Content/Parts/Text.tsx index 90a00c4a8e0..b512f05733c 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Text.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Text.tsx @@ -1,8 +1,8 @@ import { memo, useMemo, ReactElement } from 'react'; import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import useSmoothStreaming from '~/hooks/Messages/useSmoothStreaming'; import Markdown from '~/components/Chat/Messages/Content/Markdown'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import CollapsibleText from './CollapsibleText'; import { cn } from '~/utils'; diff --git a/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx b/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx index 470b232ae47..d04046f1cdc 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx @@ -12,8 +12,8 @@ import { Lightbulb, ChevronDown } from 'lucide-react'; import { Button, MorphIcon, TooltipAnchor } from '@librechat/client'; import { ChevronUp as ChevronUpNode, ChevronDown as ChevronDownNode } from 'lucide'; import type { FocusEvent, FC } from 'react'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import CopyButton from '~/components/Messages/Content/CopyButton'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { useLocalize, useExpandCollapse } from '~/hooks'; import { AnimatedText } from '../animate'; import { ROW_GLYPH_SLOT } from '../rows'; diff --git a/client/src/components/Chat/Messages/Content/Parts/ToolArtifactCard.tsx b/client/src/components/Chat/Messages/Content/Parts/ToolArtifactCard.tsx index 9c7669ae6b4..c3f9fa5dd91 100644 --- a/client/src/components/Chat/Messages/Content/Parts/ToolArtifactCard.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/ToolArtifactCard.tsx @@ -1,8 +1,8 @@ import { memo, useEffect, useId, useLayoutEffect, useRef } from 'react'; import type { TAttachment, TFile, TAttachmentMetadata } from 'librechat-data-provider'; import type { Artifact } from '~/common'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { artifactRowKind, isCodeOnlyArtifact } from '~/utils/artifacts'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { displayFilename } from './attachmentTypes'; import { useAttachmentLink } from './LogLink'; import ArtifactRow from './ArtifactRow'; diff --git a/client/src/components/Chat/Messages/Content/Parts/ToolMermaidArtifact.tsx b/client/src/components/Chat/Messages/Content/Parts/ToolMermaidArtifact.tsx index dc9d92540ac..8158f141efe 100644 --- a/client/src/components/Chat/Messages/Content/Parts/ToolMermaidArtifact.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/ToolMermaidArtifact.tsx @@ -2,8 +2,8 @@ import { memo, useId, useLayoutEffect, useMemo, useState } from 'react'; import { Download } from 'lucide-react'; import type { TAttachment, TFile, TAttachmentMetadata } from 'librechat-data-provider'; import { fileToArtifact, TOOL_ARTIFACT_TYPES, toolArtifactKey } from '~/utils/artifacts'; +import { useMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import Mermaid from '~/components/Messages/Content/Mermaid/Mermaid'; -import { useMessagePartsHost } from '~/hooks/Chat/parts'; import { displayFilename } from './attachmentTypes'; import { useAttachmentLink } from './LogLink'; import { useLocalize } from '~/hooks'; diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/ExecuteCode.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/ExecuteCode.test.tsx index e97803660b7..5b38329b3f5 100644 --- a/client/src/components/Chat/Messages/Content/Parts/__tests__/ExecuteCode.test.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/ExecuteCode.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { RecoilRoot } from 'recoil'; import { render } from '@testing-library/react'; -import type { PtcTraceEntry } from '~/store/ptc'; +import type { PtcTraceEntry } from '~/common'; import { ptcTraceByToolCallId, ptcTraceKey } from '~/store/ptc'; import { MessageContext } from '~/Providers/MessageContext'; import ExecuteCode from '../ExecuteCode'; diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/PendingSteers.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/PendingSteers.test.tsx index b57d6d310ed..88ec9779b51 100644 --- a/client/src/components/Chat/Messages/Content/Parts/__tests__/PendingSteers.test.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/PendingSteers.test.tsx @@ -6,7 +6,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Constants, ContentTypes, QueryKeys } from 'librechat-data-provider'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import type { Agents, TConversation, TMessage } from 'librechat-data-provider'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { siblingIdxFamily, siblingKey } from '~/components/Chat/Messages/Thread/state'; import { hasLiveRunPause } from '~/hooks/Chat/useSteering'; import { applyPendingAction } from '~/utils/approval'; diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/PtcToolTrace.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/PtcToolTrace.test.tsx index 9a73aa0c76f..3053caf7f80 100644 --- a/client/src/components/Chat/Messages/Content/Parts/__tests__/PtcToolTrace.test.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/PtcToolTrace.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { RecoilRoot, useSetRecoilState } from 'recoil'; import { render, screen } from '@testing-library/react'; -import type { PtcTraceEntry } from '~/store/ptc'; +import type { PtcTraceEntry } from '~/common'; import { ptcTraceByToolCallId, ptcTraceKey } from '~/store/ptc'; import { MessageContext } from '~/Providers/MessageContext'; import PtcToolTrace from '../PtcToolTrace'; diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/host.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/host.test.tsx index f5e132faa94..85237fdf382 100644 --- a/client/src/components/Chat/Messages/Content/Parts/__tests__/host.test.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/host.test.tsx @@ -5,10 +5,9 @@ import type { MessagePartArtifactPanel, MessagePartMessage, MessagePartsHost, - PtcTrace, } from '~/hooks/Chat/contract'; -import type { Artifact } from '~/common'; -import { MessagePartsHostProvider, appMessagePartsHost } from '~/hooks/Chat/parts'; +import type { Artifact, PtcTrace } from '~/common'; +import { MessagePartsHostProvider, appMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import ToolArtifactCard from '../ToolArtifactCard'; import PtcToolTrace from '../PtcToolTrace'; import TextPart from '../Text'; diff --git a/client/src/components/Chat/Messages/MessagesView.tsx b/client/src/components/Chat/Messages/MessagesView.tsx index 7f6cd4f9296..684c479846a 100644 --- a/client/src/components/Chat/Messages/MessagesView.tsx +++ b/client/src/components/Chat/Messages/MessagesView.tsx @@ -10,8 +10,8 @@ import { useMessageScrolling, useConversationSeen, } from '~/hooks'; +import { MessagePartsHostProvider, appMessagePartsHost } from '~/Providers/MessagePartsHostContext'; import { MessagesViewProvider, useChatContext, useFileMapContext } from '~/Providers'; -import { MessagePartsHostProvider, appMessagePartsHost } from '~/hooks/Chat/parts'; import { RowMountProvider, useProgressiveRowMount } from '~/hooks/Messages'; import { useChatSurface } from '~/components/Chat/Subagents/surface'; import useThreadRows from '~/hooks/Messages/useThreadRows'; diff --git a/client/src/hooks/Chat/__tests__/transport.spec.tsx b/client/src/hooks/Chat/__tests__/transport.spec.tsx index 8be4e480fa6..cc0d77d9797 100644 --- a/client/src/hooks/Chat/__tests__/transport.spec.tsx +++ b/client/src/hooks/Chat/__tests__/transport.spec.tsx @@ -14,7 +14,7 @@ import type { } from 'librechat-data-provider'; import type { MutableSnapshot } from 'recoil'; import type { Transport } from '~/hooks/Chat/contract'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { queuedMessagesByConvoId, resetQueueFamilies } from '~/hooks/Chat/queue'; import { ChatTransportContext } from '~/Providers/ChatTransportContext'; import { useSteerReclaim } from '~/hooks/Chat/useSteerCancel'; diff --git a/client/src/hooks/Chat/__tests__/useSteerCancel.spec.tsx b/client/src/hooks/Chat/__tests__/useSteerCancel.spec.tsx index 6f04f2a9304..4772621fef2 100644 --- a/client/src/hooks/Chat/__tests__/useSteerCancel.spec.tsx +++ b/client/src/hooks/Chat/__tests__/useSteerCancel.spec.tsx @@ -4,7 +4,7 @@ import { RecoilRoot, useRecoilValue } from 'recoil'; import { act, renderHook } from '@testing-library/react'; import { ReasoningEffort } from 'librechat-data-provider'; import type { RestoreToComposer } from '~/Providers/ComposerRestoreContext'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { ComposerRestoreProvider, useComposerRestoreHost, diff --git a/client/src/hooks/Chat/__tests__/useSteerEscalate.spec.tsx b/client/src/hooks/Chat/__tests__/useSteerEscalate.spec.tsx index 06b7af7b44b..8ba0fd4d9fb 100644 --- a/client/src/hooks/Chat/__tests__/useSteerEscalate.spec.tsx +++ b/client/src/hooks/Chat/__tests__/useSteerEscalate.spec.tsx @@ -3,7 +3,7 @@ import { RecoilRoot, useRecoilValue } from 'recoil'; import { getDefaultStore, useAtomValue } from 'jotai'; import { act, renderHook, waitFor } from '@testing-library/react'; import type { MutableSnapshot } from 'recoil'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { escalatingSteerFamily } from '~/store/steer'; import useSteerEscalate from '../useSteerEscalate'; import store from '~/store'; diff --git a/client/src/hooks/Chat/contract.ts b/client/src/hooks/Chat/contract.ts index 0202313e718..bc43464abdd 100644 --- a/client/src/hooks/Chat/contract.ts +++ b/client/src/hooks/Chat/contract.ts @@ -26,11 +26,9 @@ import type { ExtendedFile, TAskFunction, Artifact, + PtcTrace, } from '~/common'; -import type { PendingSteer } from '~/store/families'; -import type { PtcTrace } from '~/store/ptc'; - -export type { PtcTrace, PtcTraceEntry } from '~/store/ptc'; +import type { PendingSteer } from './queue'; /** Options accepted by {@link ChatConversationContract.newConversation}: the shared params plus * the two flags only the root pane's generator honors. */ diff --git a/client/src/hooks/Chat/queue.ts b/client/src/hooks/Chat/queue.ts index 50012364d20..e4f5308d8ab 100644 --- a/client/src/hooks/Chat/queue.ts +++ b/client/src/hooks/Chat/queue.ts @@ -2,6 +2,54 @@ import { atom } from 'jotai'; import { atomFamily, RESET } from 'jotai/utils'; import type { TMessage } from 'librechat-data-provider'; import type { PrimitiveAtom } from 'jotai'; +import type { GenerationProtocolVersion } from '~/data-provider/SSE/protocol'; + +/** + * A steer message submitted mid-run. Server truth: `sending` covers the POST + * in flight, `pending` means the server queued it (awaiting its injection + * boundary — the next tool batch, or the next safe token boundary when + * `preempt` was armed), `failed` keeps the text recoverable after a rejected + * POST. The chip disappears when `on_steer_applied` lands (the inline content + * part becomes the durable record). + */ +export type PendingSteer = { + steerId: string; + /** Optimistic id echoed by server state when SYNC beats the POST callback. */ + clientSteerId?: string; + text: string; + status: 'sending' | 'pending' | 'failed'; + /** The transport failed without a definitive server rejection. The durable + * enqueue may have committed. Same-id Retry is safe only under protocol v2; + * edit/queue/remove stay hidden until ownership is resolved. */ + deliveryUncertain?: boolean; + /** Protocol selected for the generation that owns this attempt. */ + generationProtocolVersion?: GenerationProtocolVersion; + createdAt: number; + /** Attachments steered with the message (refs; already uploaded). */ + files?: TMessage['files']; + /** Quoted excerpts riding this steer (also sent on the POST — the server + * merges them into the injected turn); kept on the chip so a steer that + * never injects restores onto the queued item with them intact. */ + quotes?: string[]; + /** Manual skill picks, carried for restoration only (a skill pick + * configures a NEW turn's run, so it never rides the steer POST). */ + manualSkills?: string[]; + /** Full-generation setting carried for restoration only; it cannot alter a live steer. */ + reasoningOverride?: TMessage['reasoningOverride']; + /** Asked the run to seal generation at the next safe boundary rather than + * wait for a tool step. Labelling only — the server owns the behaviour and + * echoes what it actually armed. */ + preempt?: boolean; + /** Monotonic server revision; delayed ACKs cannot undo SSE corrections. */ + preemptRevision?: number; + /** Exact server generation this steer belongs to. Conversation ids are + * reused by later turns, so retries/arm/cancel must retain this epoch rather + * than mutating whatever generation currently occupies the conversation. */ + generationCreatedAt?: number; + /** Exact client queue identity/order to restore if this accepted steer is + * returned as a terminal leftover before injection. */ + queuedOrigin?: QueuedMessageOrigin; +}; /** A message composed during a run, queued to send after it finishes. * Attachments ride the queued item (already uploaded at attach time) and are diff --git a/client/src/hooks/Chat/useSteerCancel.ts b/client/src/hooks/Chat/useSteerCancel.ts index 2d0f6e23c37..783b510903c 100644 --- a/client/src/hooks/Chat/useSteerCancel.ts +++ b/client/src/hooks/Chat/useSteerCancel.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { useSetAtom, useStore } from 'jotai'; import { useRecoilCallback, useRecoilValue } from 'recoil'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { useComposerRestoreHost } from '~/Providers/ComposerRestoreContext'; import { appendAppliedSteerIds, carriedSteerContext } from '~/utils'; import { pendingSteerCancelClientIdsFamily } from '~/store/steer'; diff --git a/client/src/hooks/Chat/useSteerRecovery.ts b/client/src/hooks/Chat/useSteerRecovery.ts index ffd7c0c93c1..167aa102e9f 100644 --- a/client/src/hooks/Chat/useSteerRecovery.ts +++ b/client/src/hooks/Chat/useSteerRecovery.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { useStore } from 'jotai'; import { useRecoilCallback } from 'recoil'; import type { Snapshot } from 'recoil'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { getSteerErrorCode, isDefiniteSteerRejection, diff --git a/client/src/hooks/Chat/useSteering.ts b/client/src/hooks/Chat/useSteering.ts index b87005b839b..b3643ecdb86 100644 --- a/client/src/hooks/Chat/useSteering.ts +++ b/client/src/hooks/Chat/useSteering.ts @@ -27,7 +27,7 @@ import type { import type { AgentQueuedTurnReceipt, GenerationProtocolVersion } from '~/data-provider'; import type { QueueSendLock } from '~/utils/queueIntent'; import type { ExtendedFile, FileSetter } from '~/common'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import type { DuringRunAction } from './contract'; import { useGetStartupConfig, diff --git a/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts b/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts index 9cf3109da43..aea019d690e 100644 --- a/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts +++ b/client/src/hooks/SSE/__tests__/useResumableSSE.spec.ts @@ -12,7 +12,7 @@ import { } from 'librechat-data-provider'; import type { TMessage, TSubmission } from 'librechat-data-provider'; import type { Query, QueryKey } from '@tanstack/react-query'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { activeUsageResponseIdFamily, liveTokensFamily, diff --git a/client/src/hooks/SSE/__tests__/useResumeOnLoad.spec.tsx b/client/src/hooks/SSE/__tests__/useResumeOnLoad.spec.tsx index 73871cd98c7..de535a194e3 100644 --- a/client/src/hooks/SSE/__tests__/useResumeOnLoad.spec.tsx +++ b/client/src/hooks/SSE/__tests__/useResumeOnLoad.spec.tsx @@ -7,7 +7,7 @@ import type { Agents, TMessage, TConversation, TSubmission } from 'librechat-dat import type { MutableSnapshot } from 'recoil'; import type { ReactNode } from 'react'; import type { QueuedMessage } from '~/hooks/Chat/queue'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { siblingIdxFamily, siblingKey } from '~/components/Chat/Messages/Thread/state'; import { queuedMessagesByConvoId, resetQueueFamilies } from '~/hooks/Chat/queue'; import { pendingApprovalActionFamily } from '~/components/Chat/approval/state'; diff --git a/client/src/hooks/SSE/__tests__/useStepHandler.spec.ts b/client/src/hooks/SSE/__tests__/useStepHandler.spec.ts index a224c754696..c31ef1b3c1a 100644 --- a/client/src/hooks/SSE/__tests__/useStepHandler.spec.ts +++ b/client/src/hooks/SSE/__tests__/useStepHandler.spec.ts @@ -20,7 +20,7 @@ import type { PtcToolCallEvent, Agents, } from 'librechat-data-provider'; -import type { PtcTrace, PtcTraceEntry } from '~/store/ptc'; +import type { PtcTrace, PtcTraceEntry } from '~/common'; import { subagentProgressByToolCallId, subagentProgressKey, diff --git a/client/src/hooks/SSE/useResumableSSE.ts b/client/src/hooks/SSE/useResumableSSE.ts index eaabeca5dca..265da6c4267 100644 --- a/client/src/hooks/SSE/useResumableSSE.ts +++ b/client/src/hooks/SSE/useResumableSSE.ts @@ -46,7 +46,7 @@ import type { DrainAfterAbort, QueuedMessageOrigin } from '~/hooks/Chat/queue'; import type { GenerationProtocolVersion } from '~/data-provider'; import type { EventHandlerParams } from './useEventHandlers'; import type { TResData, TFinalResData } from '~/common'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { logger, clearComposerDrafts, diff --git a/client/src/store/families.ts b/client/src/store/families.ts index 65f89fb5445..8a71753e485 100644 --- a/client/src/store/families.ts +++ b/client/src/store/families.ts @@ -11,16 +11,10 @@ import { useSetRecoilState, useRecoilCallback, } from 'recoil'; -import type { - EModelEndpoint, - TConversation, - TSubmission, - TMessage, - TPreset, -} from 'librechat-data-provider'; +import type { EModelEndpoint, TConversation, TSubmission, TPreset } from 'librechat-data-provider'; import type { GenerationProtocolVersion } from '~/data-provider/SSE/protocol'; import type { TOptionSettings, ExtendedFile } from '~/common'; -import type { QueuedMessageOrigin } from '~/hooks/Chat/queue'; +import type { PendingSteer } from '~/hooks/Chat/queue'; import { clearModelForNonEphemeralAgent, createChatSearchParams, @@ -347,53 +341,6 @@ const pendingComposerTextByConvoId = atomFamily({ default: undefined, }); -/** - * A steer message submitted mid-run. Server truth: `sending` covers the POST - * in flight, `pending` means the server queued it (awaiting its injection - * boundary — the next tool batch, or the next safe token boundary when - * `preempt` was armed), `failed` keeps the text recoverable after a rejected - * POST. The chip disappears when `on_steer_applied` lands (the inline content - * part becomes the durable record). - */ -export type PendingSteer = { - steerId: string; - /** Optimistic id echoed by server state when SYNC beats the POST callback. */ - clientSteerId?: string; - text: string; - status: 'sending' | 'pending' | 'failed'; - /** The transport failed without a definitive server rejection. The durable - * enqueue may have committed. Same-id Retry is safe only under protocol v2; - * edit/queue/remove stay hidden until ownership is resolved. */ - deliveryUncertain?: boolean; - /** Protocol selected for the generation that owns this attempt. */ - generationProtocolVersion?: GenerationProtocolVersion; - createdAt: number; - /** Attachments steered with the message (refs; already uploaded). */ - files?: TMessage['files']; - /** Quoted excerpts riding this steer (also sent on the POST — the server - * merges them into the injected turn); kept on the chip so a steer that - * never injects restores onto the queued item with them intact. */ - quotes?: string[]; - /** Manual skill picks, carried for restoration only (a skill pick - * configures a NEW turn's run, so it never rides the steer POST). */ - manualSkills?: string[]; - /** Full-generation setting carried for restoration only; it cannot alter a live steer. */ - reasoningOverride?: TMessage['reasoningOverride']; - /** Asked the run to seal generation at the next safe boundary rather than - * wait for a tool step. Labelling only — the server owns the behaviour and - * echoes what it actually armed. */ - preempt?: boolean; - /** Monotonic server revision; delayed ACKs cannot undo SSE corrections. */ - preemptRevision?: number; - /** Exact server generation this steer belongs to. Conversation ids are - * reused by later turns, so retries/arm/cancel must retain this epoch rather - * than mutating whatever generation currently occupies the conversation. */ - generationCreatedAt?: number; - /** Exact client queue identity/order to restore if this accepted steer is - * returned as a terminal leftover before injection. */ - queuedOrigin?: QueuedMessageOrigin; -}; - /** * Per-conversation steers awaiting injection. Reconciled against the server: * `on_steer_applied` removes its chip; `sync`/`resumeState.pendingSteers` diff --git a/client/src/store/ptc.ts b/client/src/store/ptc.ts index 32332f1860a..78350325d2c 100644 --- a/client/src/store/ptc.ts +++ b/client/src/store/ptc.ts @@ -1,26 +1,5 @@ import { atomFamily } from 'recoil'; -import type { PtcToolCallStatus } from 'librechat-data-provider'; - -/** - * A row's outcome. Widens the wire status with `interrupted`, which the - * backend never sends: it is what the client concludes locally about a call - * that was still running when a stream gap swallowed its settling event. - */ -export type PtcTraceStatus = PtcToolCallStatus | 'interrupted'; - -/** One tool call a programmatic (PTC) program made from inside the sandbox. */ -export interface PtcTraceEntry { - /** Stable id from the backend; the settle event updates this row in place. */ - callId: string; - /** Inner tool id, e.g. `search_code_mcp_github`. */ - name: string; - status: PtcTraceStatus; - /** `key=value` preview of the call's input. */ - args?: string; - /** Truncated failure message on a failed call. */ - error?: string; - durationMs?: number; -} +import type { PtcTrace } from '~/common'; /** * Stable identity for one PTC invocation in its parent message. Providers @@ -39,13 +18,6 @@ export const ptcTraceKey = (parentMessageId: string, toolCallId: string) => */ export const PTC_TRACE_MAX_ENTRIES = 100; -/** One PTC program's trace: the retained tail plus what the cap discarded. */ -export interface PtcTrace { - entries: PtcTraceEntry[]; - /** Rows evicted by {@link PTC_TRACE_MAX_ENTRIES}, so the cap is never silent. */ - dropped: number; -} - /** Shared empty value — one reference, so untouched atoms compare equal. */ export const EMPTY_PTC_TRACE: PtcTrace = { entries: [], dropped: 0 }; diff --git a/client/src/utils/steer.ts b/client/src/utils/steer.ts index f939b9b3e8f..4a1c1311fde 100644 --- a/client/src/utils/steer.ts +++ b/client/src/utils/steer.ts @@ -6,7 +6,7 @@ import type { TMessageContentParts, } from 'librechat-data-provider'; import type { QueuedMessage, QueuedMessageOrigin } from '~/hooks/Chat/queue'; -import type { PendingSteer } from '~/store/families'; +import type { PendingSteer } from '~/hooks/Chat/queue'; type SteerPart = Extract;