@athenaintel/react 0.60.0 → 0.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/agent-runtime/src/client-tool-bridge.cjs +20 -2
- package/dist/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
- package/dist/agent-runtime/src/client-tool-bridge.d.ts +9 -4
- package/dist/agent-runtime/src/client-tool-bridge.js +20 -2
- package/dist/agent-runtime/src/client-tool-bridge.js.map +1 -1
- package/dist/agent-runtime/src/client-tools.cjs.map +1 -1
- package/dist/agent-runtime/src/client-tools.d.ts +9 -0
- package/dist/agent-runtime/src/client-tools.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +2 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +2 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +69 -1
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.d.ts +14 -1
- package/dist/chat/StatewireClientToolBridge.js +70 -2
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireClientToolConsentCard.cjs +65 -0
- package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -0
- package/dist/chat/StatewireClientToolConsentCard.d.ts +24 -0
- package/dist/chat/StatewireClientToolConsentCard.js +65 -0
- package/dist/chat/StatewireClientToolConsentCard.js.map +1 -0
- package/dist/chat/client-tool-write-consent.cjs +111 -0
- package/dist/chat/client-tool-write-consent.cjs.map +1 -0
- package/dist/chat/client-tool-write-consent.d.ts +71 -0
- package/dist/chat/client-tool-write-consent.js +111 -0
- package/dist/chat/client-tool-write-consent.js.map +1 -0
- package/dist/chat/statewire-client-tools.cjs +30 -5
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.d.ts +34 -2
- package/dist/chat/statewire-client-tools.js +30 -5
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +13 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.d.ts +12 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +13 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +2 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js +3 -2
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs.map +1 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.d.ts +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js +2 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js.map +1 -1
- package/dist/chat-ui/statewire.cjs +4 -0
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.d.ts +2 -0
- package/dist/chat-ui/statewire.js +4 -0
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +22 -3
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +8 -1
- package/dist/provider/AthenaProvider.js +23 -4
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-tools.cjs","sources":["../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":["CLIENT_TOOL_INTERRUPT_SOURCE","CLIENT_TOOL_RESULT_STATUS_SUCCESS","CLIENT_TOOL_RESULT_STATUS_ERROR"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client-tools.cjs","sources":["../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n /**\n * Aborts when the parked call's request goes away before the handler\n * finished: the run was stopped, the call was answered elsewhere, or the\n * person opened another conversation. Whatever the handler returns after\n * that is discarded, so a handler that takes visible time (a paced\n * walkthrough, typing shown to the person) should stop on it. Absent when a\n * host invokes the handler outside the bridge.\n */\n signal?: AbortSignal;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":["CLIENT_TOOL_INTERRUPT_SOURCE","CLIENT_TOOL_RESULT_STATUS_SUCCESS","CLIENT_TOOL_RESULT_STATUS_ERROR"],"mappings":"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"}
|
|
@@ -32,6 +32,15 @@ export interface ClientToolDefinition {
|
|
|
32
32
|
export interface ClientToolHandlerContext {
|
|
33
33
|
/** The model's tool-call id, for handlers that correlate calls. */
|
|
34
34
|
toolCallId: string;
|
|
35
|
+
/**
|
|
36
|
+
* Aborts when the parked call's request goes away before the handler
|
|
37
|
+
* finished: the run was stopped, the call was answered elsewhere, or the
|
|
38
|
+
* person opened another conversation. Whatever the handler returns after
|
|
39
|
+
* that is discarded, so a handler that takes visible time (a paced
|
|
40
|
+
* walkthrough, typing shown to the person) should stop on it. Absent when a
|
|
41
|
+
* host invokes the handler outside the bridge.
|
|
42
|
+
*/
|
|
43
|
+
signal?: AbortSignal;
|
|
35
44
|
}
|
|
36
45
|
export type ClientToolHandler = (args: Record<string, unknown>, context?: ClientToolHandlerContext) => Promise<unknown>;
|
|
37
46
|
export interface RegisteredClientTool {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-tools.js","sources":["../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client-tools.js","sources":["../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n /**\n * Aborts when the parked call's request goes away before the handler\n * finished: the run was stopped, the call was answered elsewhere, or the\n * person opened another conversation. Whatever the handler returns after\n * that is discarded, so a handler that takes visible time (a paced\n * walkthrough, typing shown to the person) should stop on it. Absent when a\n * host invokes the handler outside the bridge.\n */\n signal?: AbortSignal;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/chat/AthenaChat.cjs
CHANGED
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const tiptapText = require("./tiptap-text.cjs");
|
|
5
5
|
const AthenaChatErrorBoundary = require("./AthenaChatErrorBoundary.cjs");
|
|
6
6
|
const StatewireApprovalCard = require("./StatewireApprovalCard.cjs");
|
|
7
|
+
const StatewireClientToolConsentCard = require("./StatewireClientToolConsentCard.cjs");
|
|
7
8
|
const StatewireTypedInterruptCard = require("./typed-interrupts/StatewireTypedInterruptCard.cjs");
|
|
8
9
|
const AthenaPlanStrip = require("./AthenaPlanStrip.cjs");
|
|
9
10
|
const StatewireQueuedMessages = require("./StatewireQueuedMessages.cjs");
|
|
@@ -368,6 +369,7 @@ const AthenaChatContent = ({
|
|
|
368
369
|
typedInterrupts: StatewireTypedInterruptCard.statewireTypedInterrupts
|
|
369
370
|
}
|
|
370
371
|
),
|
|
372
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatewireClientToolConsentCard.StatewireClientToolConsentCard, {}),
|
|
371
373
|
plan === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(AthenaPlanStrip.AthenaPlanStrip, { className: "mx-3" }) : plan,
|
|
372
374
|
/* @__PURE__ */ jsxRuntime.jsx(StatewireQueuedMessages.StatewireQueuedMessages, {}),
|
|
373
375
|
/* @__PURE__ */ jsxRuntime.jsx(AttachmentContext.AttachmentProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(QuoteContext.QuoteProvider, { children: [
|