@copilotkit/runtime 1.61.1 → 1.61.2
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/dist/agent/converters/aisdk.cjs +52 -1
- package/dist/agent/converters/aisdk.cjs.map +1 -1
- package/dist/agent/converters/aisdk.d.cts +6 -2
- package/dist/agent/converters/aisdk.d.cts.map +1 -1
- package/dist/agent/converters/aisdk.d.mts +6 -2
- package/dist/agent/converters/aisdk.d.mts.map +1 -1
- package/dist/agent/converters/aisdk.mjs +52 -1
- package/dist/agent/converters/aisdk.mjs.map +1 -1
- package/dist/agent/converters/tanstack.cjs +17 -1
- package/dist/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts +6 -2
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts +6 -2
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs +17 -1
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/agent/index.cjs +165 -39
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +42 -4
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +42 -4
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +165 -40
- package/dist/agent/index.mjs.map +1 -1
- package/dist/graphql/message-conversion/agui-to-gql.cjs +5 -3
- package/dist/graphql/message-conversion/agui-to-gql.cjs.map +1 -1
- package/dist/graphql/message-conversion/agui-to-gql.mjs +5 -3
- package/dist/graphql/message-conversion/agui-to-gql.mjs.map +1 -1
- package/dist/lib/integrations/node-http/request-handler.cjs.map +1 -1
- package/dist/lib/integrations/node-http/request-handler.mjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.cjs.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.cts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.mts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.mjs.map +1 -1
- package/dist/service-adapters/openai/utils.cjs.map +1 -1
- package/dist/service-adapters/openai/utils.mjs.map +1 -1
- package/dist/v2/index.cjs +1 -0
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +3 -3
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +3 -3
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/express-fetch-bridge.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/express-fetch-bridge.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.cjs +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.mjs +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +4 -3
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +4 -3
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs +0 -2
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs +0 -2
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs +8 -4
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs +8 -4
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
- package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tanstack.mjs","names":[],"sources":["../../../src/agent/converters/tanstack.ts"],"sourcesContent":["import type {\n BaseEvent,\n RunAgentInput,\n Message,\n TextMessageChunkEvent,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolCallResultEvent,\n StateSnapshotEvent,\n StateDeltaEvent,\n ReasoningStartEvent,\n ReasoningMessageStartEvent,\n ReasoningMessageContentEvent,\n ReasoningMessageEndEvent,\n ReasoningEndEvent,\n} from \"@ag-ui/client\";\nimport { EventType } from \"@ag-ui/client\";\nimport { randomUUID } from \"@copilotkit/shared\";\n\ntype ContentPartSource =\n | { type: \"data\"; value: string; mimeType: string }\n | { type: \"url\"; value: string; mimeType?: string };\n\n/**\n * A TanStack AI content part (text, image, audio, video, or document).\n */\nexport type TanStackContentPart =\n | { type: \"text\"; content: string }\n | { type: \"image\"; source: ContentPartSource }\n | { type: \"audio\"; source: ContentPartSource }\n | { type: \"video\"; source: ContentPartSource }\n | { type: \"document\"; source: ContentPartSource };\n\n/**\n * Message format expected by TanStack AI's `chat()`.\n *\n * Content is typed as `any[]` for the multimodal case so messages are directly\n * passable to any adapter without casts — different adapters constrain which\n * modalities they accept (e.g. OpenAI only allows text + image).\n * Use `TanStackContentPart` to inspect individual parts if needed.\n */\nexport interface TanStackChatMessage {\n role: \"user\" | \"assistant\" | \"tool\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n content: string | null | any[];\n name?: string;\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>;\n toolCallId?: string;\n}\n\n/**\n * A TanStack AI client-side tool, derived from a frontend-provided AG-UI tool.\n *\n * Shaped to match `@tanstack/ai`'s `ClientTool` (`__toolSide: \"client\"`, no\n * `execute`): the model may CALL it, but TanStack does not run it — it pauses\n * the run and hands the call back to the AG-UI client (the CopilotKit frontend\n * / bot) to execute, mirroring CopilotKit's client-tool round-trip. `chat()`\n * accepts a JSON Schema directly as `inputSchema`, so the AG-UI tool's\n * `parameters` pass through unchanged.\n */\nexport interface TanStackClientTool {\n __toolSide: \"client\";\n name: string;\n description: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n inputSchema: any;\n}\n\n/**\n * Result of converting RunAgentInput to TanStack AI format.\n */\nexport interface TanStackInputResult {\n /** Chat messages (only user/assistant/tool roles; all others excluded) */\n messages: TanStackChatMessage[];\n /** System prompts extracted from system/developer messages, context, and state */\n systemPrompts: string[];\n /**\n * Client-side tools derived from `input.tools` (the frontend-provided tools\n * the CopilotKit client forwards on every run). Pass these into `chat()`\n * alongside any server/provider tools so the model can call the frontend's\n * generative-UI and human-in-the-loop tools; TanStack pauses the run on a\n * client-tool call and the client executes it.\n */\n tools: TanStackClientTool[];\n}\n\n/**\n * Converts AG-UI user message content to TanStack AI format.\n * Handles plain strings, multimodal parts (image/audio/video/document),\n * and legacy BinaryInputContent for backward compatibility.\n */\nfunction convertUserContent(\n content: unknown,\n): string | null | TanStackContentPart[] {\n if (!content) return null;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return null;\n if (content.length === 0) return \"\";\n\n const parts: TanStackContentPart[] = [];\n\n for (const part of content) {\n if (!part || typeof part !== \"object\" || !(\"type\" in part)) continue;\n\n switch ((part as { type: string }).type) {\n case \"text\": {\n const text = (part as { text?: string }).text;\n if (text != null) parts.push({ type: \"text\", content: text });\n break;\n }\n\n case \"image\":\n case \"audio\":\n case \"video\":\n case \"document\": {\n const source = (part as { source?: any }).source;\n if (!source) break;\n const partType = (part as { type: string }).type as\n | \"image\"\n | \"audio\"\n | \"video\"\n | \"document\";\n if (source.type === \"data\") {\n parts.push({\n type: partType,\n source: {\n type: \"data\",\n value: source.value,\n mimeType: source.mimeType,\n },\n });\n } else if (source.type === \"url\") {\n parts.push({\n type: partType,\n source: {\n type: \"url\",\n value: source.value,\n ...(source.mimeType ? { mimeType: source.mimeType } : {}),\n },\n });\n }\n break;\n }\n\n // Legacy BinaryInputContent backward compatibility\n case \"binary\": {\n const legacy = part as {\n mimeType?: string;\n data?: string;\n url?: string;\n };\n const mimeType = legacy.mimeType ?? \"application/octet-stream\";\n const isImage = mimeType.startsWith(\"image/\");\n\n if (legacy.data) {\n const partType = isImage ? \"image\" : \"document\";\n parts.push({\n type: partType,\n source: { type: \"data\", value: legacy.data, mimeType },\n });\n } else if (legacy.url) {\n const partType = isImage ? \"image\" : \"document\";\n parts.push({\n type: partType,\n source: { type: \"url\", value: legacy.url, mimeType },\n });\n }\n break;\n }\n }\n }\n\n return parts.length > 0 ? parts : \"\";\n}\n\n/**\n * Recursively normalizes a frontend tool's JSON Schema so OpenAI accepts it as\n * a function-tool schema.\n *\n * Frontend tools are often authored with permissive Zod (`z.any()`,\n * `z.record(...)`, `.passthrough()`), which serialize to open objects —\n * `additionalProperties: {}` (an empty sub-schema) or `additionalProperties:\n * true`. OpenAI rejects both: strict mode requires `additionalProperties:\n * false`, and an empty `{}` sub-schema fails base validation (\"schema must\n * have a 'type' key\"). The classic (Vercel AI SDK) path sanitized these\n * implicitly via a Zod round-trip; the TanStack path forwards the raw schema,\n * so we close open objects here to match. (Models can't supply free-form extra\n * keys either way — same as the classic path.)\n */\nfunction sanitizeClientToolSchema(schema: unknown): unknown {\n if (Array.isArray(schema)) {\n return schema.map(sanitizeClientToolSchema);\n }\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n const node: Record<string, unknown> = {\n ...(schema as Record<string, unknown>),\n };\n\n // Any `additionalProperties` (empty `{}`, `true`, or a sub-schema) becomes\n // `false` — the only form OpenAI accepts for strict function tools.\n if (\"additionalProperties\" in node) {\n node.additionalProperties = false;\n }\n\n if (node.properties && typeof node.properties === \"object\") {\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(\n node.properties as Record<string, unknown>,\n )) {\n props[key] = sanitizeClientToolSchema(value);\n }\n node.properties = props;\n }\n\n if (\"items\" in node) {\n node.items = sanitizeClientToolSchema(node.items);\n }\n\n for (const combinator of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n if (Array.isArray(node[combinator])) {\n node[combinator] = (node[combinator] as unknown[]).map(\n sanitizeClientToolSchema,\n );\n }\n }\n\n return node;\n}\n\n/**\n * Converts a RunAgentInput into the format expected by TanStack AI's `chat()`.\n *\n * - Keeps only user/assistant/tool messages (activity, reasoning, and other roles are also excluded)\n * - Extracts system/developer messages into `systemPrompts`\n * - Appends context entries and application state to `systemPrompts`\n * - Preserves tool calls on assistant messages and toolCallId on tool messages\n */\nexport function convertInputToTanStackAI(\n input: RunAgentInput,\n): TanStackInputResult {\n // Allowlist: only pass user/assistant/tool messages to TanStack.\n // Other roles (system, developer, activity, reasoning) are either\n // extracted into systemPrompts or not applicable.\n const chatRoles = new Set([\"user\", \"assistant\", \"tool\"]);\n const messages: TanStackChatMessage[] = input.messages\n .filter((m: Message) => chatRoles.has(m.role))\n .map((m: Message): TanStackChatMessage => {\n const msg: TanStackChatMessage = {\n role: m.role as \"user\" | \"assistant\" | \"tool\",\n content:\n m.role === \"user\"\n ? convertUserContent(m.content)\n : typeof m.content === \"string\"\n ? m.content\n : null,\n };\n if (m.role === \"assistant\" && \"toolCalls\" in m && m.toolCalls) {\n msg.toolCalls = m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n }));\n }\n if (m.role === \"tool\" && \"toolCallId\" in m) {\n msg.toolCallId = (m as Record<string, unknown>).toolCallId as string;\n }\n return msg;\n });\n\n const systemPrompts: string[] = [];\n for (const m of input.messages) {\n if ((m.role === \"system\" || m.role === \"developer\") && m.content) {\n systemPrompts.push(\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n );\n }\n }\n\n if (input.context?.length) {\n for (const ctx of input.context) {\n systemPrompts.push(`${ctx.description}:\\n${ctx.value}`);\n }\n }\n\n if (\n input.state !== undefined &&\n input.state !== null &&\n typeof input.state === \"object\" &&\n Object.keys(input.state).length > 0\n ) {\n systemPrompts.push(\n `Application State:\\n\\`\\`\\`json\\n${JSON.stringify(input.state, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n // Frontend-provided tools become client-side TanStack tools (no executor):\n // the model can call them, TanStack pauses the run, and the AG-UI client\n // executes them and resumes — the CopilotKit client-tool round-trip.\n const tools: TanStackClientTool[] = (input.tools ?? []).map((t) => ({\n __toolSide: \"client\",\n name: t.name,\n description: t.description,\n inputSchema: sanitizeClientToolSchema(t.parameters),\n }));\n\n return { messages, systemPrompts, tools };\n}\n\n/**\n * Converts a TanStack AI stream into AG-UI `BaseEvent` objects.\n *\n * This is a pure converter — it does NOT emit lifecycle events\n * (RUN_STARTED / RUN_FINISHED / RUN_ERROR). The caller (Agent class)\n * is responsible for those.\n */\nexport async function* convertTanStackStream(\n stream: AsyncIterable<unknown>,\n abortSignal: AbortSignal,\n): AsyncGenerator<BaseEvent> {\n const messageId = randomUUID();\n const toolNamesById = new Map<string, string>();\n // Track the reasoning lifecycle at two granularities so closeReasoningIfOpen\n // emits exactly the events still owed. A single boolean conflates the run\n // (REASONING_START → REASONING_END) with the message\n // (REASONING_MESSAGE_START → REASONING_MESSAGE_END) and produces a duplicate\n // REASONING_MESSAGE_END when upstream emits MSG_END but not END before\n // text/tools resume.\n let reasoningRunOpen = false;\n let reasoningMessageOpen = false;\n let reasoningMessageId = randomUUID();\n\n function* closeReasoningIfOpen(): Generator<BaseEvent> {\n if (reasoningMessageOpen) {\n reasoningMessageOpen = false;\n const msgEnd: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield msgEnd;\n }\n if (reasoningRunOpen) {\n reasoningRunOpen = false;\n const end: ReasoningEndEvent = {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n };\n yield end;\n }\n }\n\n // TanStack's chat() engine runs a multi-turn agent loop and emits a\n // RUN_STARTED / RUN_FINISHED pair PER model turn — not once for the whole\n // run. When it executes a tool itself (an MCP server tool or a provider tool\n // like web_search), it does so between turns and streams a TOOL_CALL_RESULT\n // followed by the next turn's text. The overall run lifecycle is owned by the\n // Agent wrapper (it emits exactly one outer RUN_STARTED / RUN_FINISHED), so\n // we drop TanStack's per-turn lifecycle markers and convert every content\n // event across all turns. (A previous version stopped converting at the first\n // RUN_FINISHED — that truncated the run at the first tool turn and silently\n // dropped both the tool result and the model's final answer.)\n //\n // chat() can re-announce a tool call when it re-prompts after executing it,\n // so START / END are de-duplicated by toolCallId to avoid emitting a pair\n // twice (which would violate the ag-ui verify middleware).\n const startedToolCalls = new Set<string>();\n const endedToolCalls = new Set<string>();\n\n for await (const chunk of stream) {\n if (abortSignal.aborted) break;\n\n const raw = chunk as Record<string, unknown>;\n const type = raw.type as string;\n\n // Per-turn lifecycle markers are owned by the Agent wrapper, not forwarded.\n if (type === \"RUN_STARTED\" || type === \"RUN_FINISHED\") {\n continue;\n }\n\n // Surface engine errors instead of dropping them: throw so the Agent\n // wrapper emits a terminal RUN_ERROR. Without this a failed run (e.g. a\n // provider 4xx) would finish empty with no indication of what went wrong.\n if (type === \"RUN_ERROR\") {\n throw new Error(\n typeof raw.message === \"string\" ? raw.message : \"TanStack AI run error\",\n );\n }\n\n if (type === \"TEXT_MESSAGE_CONTENT\" && raw.delta != null) {\n yield* closeReasoningIfOpen();\n const textEvent: TextMessageChunkEvent = {\n type: EventType.TEXT_MESSAGE_CHUNK,\n role: \"assistant\",\n messageId,\n delta: raw.delta as string,\n };\n yield textEvent;\n } else if (type === \"TOOL_CALL_START\") {\n const toolCallId = raw.toolCallId as string;\n if (startedToolCalls.has(toolCallId)) continue;\n startedToolCalls.add(toolCallId);\n yield* closeReasoningIfOpen();\n toolNamesById.set(toolCallId, raw.toolCallName as string);\n const startEvent: ToolCallStartEvent = {\n type: EventType.TOOL_CALL_START,\n parentMessageId: messageId,\n toolCallId,\n toolCallName: raw.toolCallName as string,\n };\n yield startEvent;\n } else if (type === \"TOOL_CALL_ARGS\") {\n // Drop args re-announced after the call has ended (the re-prompt pass);\n // forwarding them would corrupt the already-closed call's accumulated args.\n if (endedToolCalls.has(raw.toolCallId as string)) continue;\n yield* closeReasoningIfOpen();\n const argsEvent: ToolCallArgsEvent = {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: raw.toolCallId as string,\n delta: raw.delta as string,\n };\n yield argsEvent;\n } else if (type === \"TOOL_CALL_END\") {\n const toolCallId = raw.toolCallId as string;\n if (endedToolCalls.has(toolCallId)) continue;\n endedToolCalls.add(toolCallId);\n yield* closeReasoningIfOpen();\n const endEvent: ToolCallEndEvent = {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n };\n yield endEvent;\n } else if (type === \"TOOL_CALL_RESULT\") {\n yield* closeReasoningIfOpen();\n const toolCallId = raw.toolCallId as string;\n const toolName = toolNamesById.get(toolCallId);\n // Accept the payload from either `content` (canonical TanStack shape)\n // or `result` (alternate shape used by some adapters / tests). Both\n // state-tool detection and the final TOOL_CALL_RESULT serialization\n // must read the same field, otherwise STATE_SNAPSHOT/STATE_DELTA can\n // be silently dropped when upstream uses `result`.\n const rawPayload = raw.content ?? raw.result;\n\n const parsedContent =\n typeof rawPayload === \"string\" ? safeParse(rawPayload) : rawPayload;\n\n if (\n toolName === \"AGUISendStateSnapshot\" &&\n parsedContent &&\n typeof parsedContent === \"object\" &&\n \"snapshot\" in parsedContent\n ) {\n const stateSnapshotEvent: StateSnapshotEvent = {\n type: EventType.STATE_SNAPSHOT,\n snapshot: (parsedContent as Record<string, unknown>).snapshot,\n };\n yield stateSnapshotEvent;\n }\n\n if (\n toolName === \"AGUISendStateDelta\" &&\n parsedContent &&\n typeof parsedContent === \"object\" &&\n \"delta\" in parsedContent\n ) {\n const stateDeltaEvent: StateDeltaEvent = {\n type: EventType.STATE_DELTA,\n delta: (parsedContent as Record<string, unknown>).delta as never,\n };\n yield stateDeltaEvent;\n }\n\n let serializedContent: string;\n if (typeof rawPayload === \"string\") {\n serializedContent = rawPayload;\n } else {\n try {\n serializedContent = JSON.stringify(rawPayload ?? null);\n } catch {\n serializedContent = \"[Unserializable tool result]\";\n }\n }\n\n const resultEvent: ToolCallResultEvent = {\n type: EventType.TOOL_CALL_RESULT,\n role: \"tool\",\n messageId: randomUUID(),\n toolCallId,\n content: serializedContent,\n };\n yield resultEvent;\n toolNamesById.delete(toolCallId);\n } else if (type === \"REASONING_START\") {\n // If a prior reasoning run is still open (no REASONING_END before this\n // new START), close it cleanly first so MSG_END / END pair correctly.\n yield* closeReasoningIfOpen();\n reasoningRunOpen = true;\n reasoningMessageId = (raw.messageId as string) ?? randomUUID();\n const startEvt: ReasoningStartEvent = {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n };\n yield startEvt;\n } else if (type === \"REASONING_MESSAGE_START\") {\n reasoningMessageOpen = true;\n const evt: ReasoningMessageStartEvent = {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: \"reasoning\",\n };\n yield evt;\n } else if (type === \"REASONING_MESSAGE_CONTENT\") {\n const evt: ReasoningMessageContentEvent = {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: raw.delta as string,\n };\n yield evt;\n } else if (type === \"REASONING_MESSAGE_END\") {\n reasoningMessageOpen = false;\n const evt: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield evt;\n } else if (type === \"REASONING_END\") {\n // If upstream sends REASONING_END while a message is still open, emit\n // the missing REASONING_MESSAGE_END FIRST so the closing pair stays in\n // order (MSG_END before END). Otherwise the next non-reasoning chunk\n // would trigger closeReasoningIfOpen and emit MSG_END after END.\n if (reasoningMessageOpen) {\n reasoningMessageOpen = false;\n const msgEnd: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield msgEnd;\n }\n reasoningRunOpen = false;\n const evt: ReasoningEndEvent = {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n };\n yield evt;\n }\n }\n\n yield* closeReasoningIfOpen();\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tanstack.mjs","names":[],"sources":["../../../src/agent/converters/tanstack.ts"],"sourcesContent":["import type {\n BaseEvent,\n Interrupt,\n RunAgentInput,\n Message,\n TextMessageChunkEvent,\n ToolCallArgsEvent,\n ToolCallEndEvent,\n ToolCallStartEvent,\n ToolCallResultEvent,\n StateSnapshotEvent,\n StateDeltaEvent,\n ReasoningStartEvent,\n ReasoningMessageStartEvent,\n ReasoningMessageContentEvent,\n ReasoningMessageEndEvent,\n ReasoningEndEvent,\n} from \"@ag-ui/client\";\nimport { EventType } from \"@ag-ui/client\";\nimport { randomUUID } from \"@copilotkit/shared\";\n\ntype ContentPartSource =\n | { type: \"data\"; value: string; mimeType: string }\n | { type: \"url\"; value: string; mimeType?: string };\n\n/**\n * A TanStack AI content part (text, image, audio, video, or document).\n */\nexport type TanStackContentPart =\n | { type: \"text\"; content: string }\n | { type: \"image\"; source: ContentPartSource }\n | { type: \"audio\"; source: ContentPartSource }\n | { type: \"video\"; source: ContentPartSource }\n | { type: \"document\"; source: ContentPartSource };\n\n/**\n * Message format expected by TanStack AI's `chat()`.\n *\n * Content is typed as `any[]` for the multimodal case so messages are directly\n * passable to any adapter without casts — different adapters constrain which\n * modalities they accept (e.g. OpenAI only allows text + image).\n * Use `TanStackContentPart` to inspect individual parts if needed.\n */\nexport interface TanStackChatMessage {\n role: \"user\" | \"assistant\" | \"tool\";\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n content: string | null | any[];\n name?: string;\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>;\n toolCallId?: string;\n}\n\n/**\n * A TanStack AI client-side tool, derived from a frontend-provided AG-UI tool.\n *\n * Shaped to match `@tanstack/ai`'s `ClientTool` (`__toolSide: \"client\"`, no\n * `execute`): the model may CALL it, but TanStack does not run it — it pauses\n * the run and hands the call back to the AG-UI client (the CopilotKit frontend\n * / bot) to execute, mirroring CopilotKit's client-tool round-trip. `chat()`\n * accepts a JSON Schema directly as `inputSchema`, so the AG-UI tool's\n * `parameters` pass through unchanged.\n */\nexport interface TanStackClientTool {\n __toolSide: \"client\";\n name: string;\n description: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n inputSchema: any;\n}\n\n/**\n * Result of converting RunAgentInput to TanStack AI format.\n */\nexport interface TanStackInputResult {\n /** Chat messages (only user/assistant/tool roles; all others excluded) */\n messages: TanStackChatMessage[];\n /** System prompts extracted from system/developer messages, context, and state */\n systemPrompts: string[];\n /**\n * Client-side tools derived from `input.tools` (the frontend-provided tools\n * the CopilotKit client forwards on every run). Pass these into `chat()`\n * alongside any server/provider tools so the model can call the frontend's\n * generative-UI and human-in-the-loop tools; TanStack pauses the run on a\n * client-tool call and the client executes it.\n */\n tools: TanStackClientTool[];\n}\n\n/**\n * Converts AG-UI user message content to TanStack AI format.\n * Handles plain strings, multimodal parts (image/audio/video/document),\n * and legacy BinaryInputContent for backward compatibility.\n */\nfunction convertUserContent(\n content: unknown,\n): string | null | TanStackContentPart[] {\n if (!content) return null;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return null;\n if (content.length === 0) return \"\";\n\n const parts: TanStackContentPart[] = [];\n\n for (const part of content) {\n if (!part || typeof part !== \"object\" || !(\"type\" in part)) continue;\n\n switch ((part as { type: string }).type) {\n case \"text\": {\n const text = (part as { text?: string }).text;\n if (text != null) parts.push({ type: \"text\", content: text });\n break;\n }\n\n case \"image\":\n case \"audio\":\n case \"video\":\n case \"document\": {\n const source = (part as { source?: any }).source;\n if (!source) break;\n const partType = (part as { type: string }).type as\n | \"image\"\n | \"audio\"\n | \"video\"\n | \"document\";\n if (source.type === \"data\") {\n parts.push({\n type: partType,\n source: {\n type: \"data\",\n value: source.value,\n mimeType: source.mimeType,\n },\n });\n } else if (source.type === \"url\") {\n parts.push({\n type: partType,\n source: {\n type: \"url\",\n value: source.value,\n ...(source.mimeType ? { mimeType: source.mimeType } : {}),\n },\n });\n }\n break;\n }\n\n // Legacy BinaryInputContent backward compatibility\n case \"binary\": {\n const legacy = part as {\n mimeType?: string;\n data?: string;\n url?: string;\n };\n const mimeType = legacy.mimeType ?? \"application/octet-stream\";\n const isImage = mimeType.startsWith(\"image/\");\n\n if (legacy.data) {\n const partType = isImage ? \"image\" : \"document\";\n parts.push({\n type: partType,\n source: { type: \"data\", value: legacy.data, mimeType },\n });\n } else if (legacy.url) {\n const partType = isImage ? \"image\" : \"document\";\n parts.push({\n type: partType,\n source: { type: \"url\", value: legacy.url, mimeType },\n });\n }\n break;\n }\n }\n }\n\n return parts.length > 0 ? parts : \"\";\n}\n\n/**\n * Recursively normalizes a frontend tool's JSON Schema so OpenAI accepts it as\n * a function-tool schema.\n *\n * Frontend tools are often authored with permissive Zod (`z.any()`,\n * `z.record(...)`, `.passthrough()`), which serialize to open objects —\n * `additionalProperties: {}` (an empty sub-schema) or `additionalProperties:\n * true`. OpenAI rejects both: strict mode requires `additionalProperties:\n * false`, and an empty `{}` sub-schema fails base validation (\"schema must\n * have a 'type' key\"). The classic (Vercel AI SDK) path sanitized these\n * implicitly via a Zod round-trip; the TanStack path forwards the raw schema,\n * so we close open objects here to match. (Models can't supply free-form extra\n * keys either way — same as the classic path.)\n */\nfunction sanitizeClientToolSchema(schema: unknown): unknown {\n if (Array.isArray(schema)) {\n return schema.map(sanitizeClientToolSchema);\n }\n if (!schema || typeof schema !== \"object\") {\n return schema;\n }\n const node: Record<string, unknown> = {\n ...(schema as Record<string, unknown>),\n };\n\n // Any `additionalProperties` (empty `{}`, `true`, or a sub-schema) becomes\n // `false` — the only form OpenAI accepts for strict function tools.\n if (\"additionalProperties\" in node) {\n node.additionalProperties = false;\n }\n\n if (node.properties && typeof node.properties === \"object\") {\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(\n node.properties as Record<string, unknown>,\n )) {\n props[key] = sanitizeClientToolSchema(value);\n }\n node.properties = props;\n }\n\n if (\"items\" in node) {\n node.items = sanitizeClientToolSchema(node.items);\n }\n\n for (const combinator of [\"anyOf\", \"allOf\", \"oneOf\"] as const) {\n if (Array.isArray(node[combinator])) {\n node[combinator] = (node[combinator] as unknown[]).map(\n sanitizeClientToolSchema,\n );\n }\n }\n\n return node;\n}\n\n/**\n * Converts a RunAgentInput into the format expected by TanStack AI's `chat()`.\n *\n * - Keeps only user/assistant/tool messages (activity, reasoning, and other roles are also excluded)\n * - Extracts system/developer messages into `systemPrompts`\n * - Appends context entries and application state to `systemPrompts`\n * - Preserves tool calls on assistant messages and toolCallId on tool messages\n */\nexport function convertInputToTanStackAI(\n input: RunAgentInput,\n): TanStackInputResult {\n // Allowlist: only pass user/assistant/tool messages to TanStack.\n // Other roles (system, developer, activity, reasoning) are either\n // extracted into systemPrompts or not applicable.\n const chatRoles = new Set([\"user\", \"assistant\", \"tool\"]);\n const messages: TanStackChatMessage[] = input.messages\n .filter((m: Message) => chatRoles.has(m.role))\n .map((m: Message): TanStackChatMessage => {\n const msg: TanStackChatMessage = {\n role: m.role as \"user\" | \"assistant\" | \"tool\",\n content:\n m.role === \"user\"\n ? convertUserContent(m.content)\n : typeof m.content === \"string\"\n ? m.content\n : null,\n };\n if (m.role === \"assistant\" && \"toolCalls\" in m && m.toolCalls) {\n msg.toolCalls = m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments: tc.function.arguments,\n },\n }));\n }\n if (m.role === \"tool\" && \"toolCallId\" in m) {\n msg.toolCallId = (m as Record<string, unknown>).toolCallId as string;\n }\n return msg;\n });\n\n const systemPrompts: string[] = [];\n for (const m of input.messages) {\n if ((m.role === \"system\" || m.role === \"developer\") && m.content) {\n systemPrompts.push(\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n );\n }\n }\n\n if (input.context?.length) {\n for (const ctx of input.context) {\n systemPrompts.push(`${ctx.description}:\\n${ctx.value}`);\n }\n }\n\n if (\n input.state !== undefined &&\n input.state !== null &&\n typeof input.state === \"object\" &&\n Object.keys(input.state).length > 0\n ) {\n systemPrompts.push(\n `Application State:\\n\\`\\`\\`json\\n${JSON.stringify(input.state, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n // Frontend-provided tools become client-side TanStack tools (no executor):\n // the model can call them, TanStack pauses the run, and the AG-UI client\n // executes them and resumes — the CopilotKit client-tool round-trip.\n const tools: TanStackClientTool[] = (input.tools ?? []).map((t) => ({\n __toolSide: \"client\",\n name: t.name,\n description: t.description,\n inputSchema: sanitizeClientToolSchema(t.parameters),\n }));\n\n return { messages, systemPrompts, tools };\n}\n\n/**\n * Converts a TanStack AI stream into AG-UI `BaseEvent` objects.\n *\n * This is a pure converter — it does NOT emit lifecycle events\n * (RUN_STARTED / RUN_FINISHED / RUN_ERROR). The caller (Agent class)\n * is responsible for those.\n *\n * `pendingInterrupts`, when provided, is filled with one AG-UI Interrupt per\n * CUSTOM \"approval-requested\" chunk (a tool declared `needsApproval: true`).\n * The caller turns a non-empty array into a RUN_FINISHED `outcome:interrupt`.\n */\nexport async function* convertTanStackStream(\n stream: AsyncIterable<unknown>,\n abortSignal: AbortSignal,\n pendingInterrupts?: Interrupt[],\n): AsyncGenerator<BaseEvent> {\n const messageId = randomUUID();\n const toolNamesById = new Map<string, string>();\n // Track the reasoning lifecycle at two granularities so closeReasoningIfOpen\n // emits exactly the events still owed. A single boolean conflates the run\n // (REASONING_START → REASONING_END) with the message\n // (REASONING_MESSAGE_START → REASONING_MESSAGE_END) and produces a duplicate\n // REASONING_MESSAGE_END when upstream emits MSG_END but not END before\n // text/tools resume.\n let reasoningRunOpen = false;\n let reasoningMessageOpen = false;\n let reasoningMessageId = randomUUID();\n\n function* closeReasoningIfOpen(): Generator<BaseEvent> {\n if (reasoningMessageOpen) {\n reasoningMessageOpen = false;\n const msgEnd: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield msgEnd;\n }\n if (reasoningRunOpen) {\n reasoningRunOpen = false;\n const end: ReasoningEndEvent = {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n };\n yield end;\n }\n }\n\n // TanStack's chat() engine runs a multi-turn agent loop and emits a\n // RUN_STARTED / RUN_FINISHED pair PER model turn — not once for the whole\n // run. When it executes a tool itself (an MCP server tool or a provider tool\n // like web_search), it does so between turns and streams a TOOL_CALL_RESULT\n // followed by the next turn's text. The overall run lifecycle is owned by the\n // Agent wrapper (it emits exactly one outer RUN_STARTED / RUN_FINISHED), so\n // we drop TanStack's per-turn lifecycle markers and convert every content\n // event across all turns. (A previous version stopped converting at the first\n // RUN_FINISHED — that truncated the run at the first tool turn and silently\n // dropped both the tool result and the model's final answer.)\n //\n // chat() can re-announce a tool call when it re-prompts after executing it,\n // so START / END are de-duplicated by toolCallId to avoid emitting a pair\n // twice (which would violate the ag-ui verify middleware).\n const startedToolCalls = new Set<string>();\n const endedToolCalls = new Set<string>();\n\n for await (const chunk of stream) {\n if (abortSignal.aborted) break;\n\n const raw = chunk as Record<string, unknown>;\n const type = raw.type as string;\n\n // TanStack native human-in-the-loop: a tool declared `needsApproval: true`\n // emits a CUSTOM \"approval-requested\" chunk. These are built from the\n // finish event and can arrive around lifecycle markers, so handle them\n // before dropping TanStack's per-turn lifecycle events.\n // The tool-call lifecycle was already streamed in the model pass.\n if (type === \"CUSTOM\" && raw.name === \"approval-requested\") {\n const value = (raw.value ?? {}) as {\n toolCallId?: string;\n toolName?: string;\n };\n const toolCallId = value.toolCallId;\n if (toolCallId) {\n pendingInterrupts?.push({\n id: toolCallId,\n toolCallId,\n reason: \"tool_approval\",\n message: value.toolName ? `Approve \"${value.toolName}\"?` : undefined,\n ...(value.toolName ? { metadata: { toolName: value.toolName } } : {}),\n });\n }\n continue;\n }\n\n // Per-turn lifecycle markers are owned by the Agent wrapper, not forwarded.\n if (type === \"RUN_STARTED\" || type === \"RUN_FINISHED\") {\n continue;\n }\n\n // Surface engine errors instead of dropping them: throw so the Agent\n // wrapper emits a terminal RUN_ERROR. Without this a failed run (e.g. a\n // provider 4xx) would finish empty with no indication of what went wrong.\n if (type === \"RUN_ERROR\") {\n throw new Error(\n typeof raw.message === \"string\" ? raw.message : \"TanStack AI run error\",\n );\n }\n\n if (type === \"TEXT_MESSAGE_CONTENT\" && raw.delta != null) {\n yield* closeReasoningIfOpen();\n const textEvent: TextMessageChunkEvent = {\n type: EventType.TEXT_MESSAGE_CHUNK,\n role: \"assistant\",\n messageId,\n delta: raw.delta as string,\n };\n yield textEvent;\n } else if (type === \"TOOL_CALL_START\") {\n const toolCallId = raw.toolCallId as string;\n if (startedToolCalls.has(toolCallId)) continue;\n startedToolCalls.add(toolCallId);\n yield* closeReasoningIfOpen();\n toolNamesById.set(toolCallId, raw.toolCallName as string);\n const startEvent: ToolCallStartEvent = {\n type: EventType.TOOL_CALL_START,\n parentMessageId: messageId,\n toolCallId,\n toolCallName: raw.toolCallName as string,\n };\n yield startEvent;\n } else if (type === \"TOOL_CALL_ARGS\") {\n // Drop args re-announced after the call has ended (the re-prompt pass);\n // forwarding them would corrupt the already-closed call's accumulated args.\n if (endedToolCalls.has(raw.toolCallId as string)) continue;\n yield* closeReasoningIfOpen();\n const argsEvent: ToolCallArgsEvent = {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: raw.toolCallId as string,\n delta: raw.delta as string,\n };\n yield argsEvent;\n } else if (type === \"TOOL_CALL_END\") {\n const toolCallId = raw.toolCallId as string;\n if (endedToolCalls.has(toolCallId)) continue;\n endedToolCalls.add(toolCallId);\n yield* closeReasoningIfOpen();\n const endEvent: ToolCallEndEvent = {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n };\n yield endEvent;\n } else if (type === \"TOOL_CALL_RESULT\") {\n yield* closeReasoningIfOpen();\n const toolCallId = raw.toolCallId as string;\n const toolName = toolNamesById.get(toolCallId);\n // Accept the payload from either `content` (canonical TanStack shape)\n // or `result` (alternate shape used by some adapters / tests). Both\n // state-tool detection and the final TOOL_CALL_RESULT serialization\n // must read the same field, otherwise STATE_SNAPSHOT/STATE_DELTA can\n // be silently dropped when upstream uses `result`.\n const rawPayload = raw.content ?? raw.result;\n\n const parsedContent =\n typeof rawPayload === \"string\" ? safeParse(rawPayload) : rawPayload;\n\n if (\n toolName === \"AGUISendStateSnapshot\" &&\n parsedContent &&\n typeof parsedContent === \"object\" &&\n \"snapshot\" in parsedContent\n ) {\n const stateSnapshotEvent: StateSnapshotEvent = {\n type: EventType.STATE_SNAPSHOT,\n snapshot: (parsedContent as Record<string, unknown>).snapshot,\n };\n yield stateSnapshotEvent;\n }\n\n if (\n toolName === \"AGUISendStateDelta\" &&\n parsedContent &&\n typeof parsedContent === \"object\" &&\n \"delta\" in parsedContent\n ) {\n const stateDeltaEvent: StateDeltaEvent = {\n type: EventType.STATE_DELTA,\n delta: (parsedContent as Record<string, unknown>).delta as never,\n };\n yield stateDeltaEvent;\n }\n\n let serializedContent: string;\n if (typeof rawPayload === \"string\") {\n serializedContent = rawPayload;\n } else {\n try {\n serializedContent = JSON.stringify(rawPayload ?? null);\n } catch {\n serializedContent = \"[Unserializable tool result]\";\n }\n }\n\n const resultEvent: ToolCallResultEvent = {\n type: EventType.TOOL_CALL_RESULT,\n role: \"tool\",\n messageId: randomUUID(),\n toolCallId,\n content: serializedContent,\n };\n yield resultEvent;\n toolNamesById.delete(toolCallId);\n } else if (type === \"REASONING_START\") {\n // If a prior reasoning run is still open (no REASONING_END before this\n // new START), close it cleanly first so MSG_END / END pair correctly.\n yield* closeReasoningIfOpen();\n reasoningRunOpen = true;\n reasoningMessageId = (raw.messageId as string) ?? randomUUID();\n const startEvt: ReasoningStartEvent = {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n };\n yield startEvt;\n } else if (type === \"REASONING_MESSAGE_START\") {\n reasoningMessageOpen = true;\n const evt: ReasoningMessageStartEvent = {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: \"reasoning\",\n };\n yield evt;\n } else if (type === \"REASONING_MESSAGE_CONTENT\") {\n const evt: ReasoningMessageContentEvent = {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: raw.delta as string,\n };\n yield evt;\n } else if (type === \"REASONING_MESSAGE_END\") {\n reasoningMessageOpen = false;\n const evt: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield evt;\n } else if (type === \"REASONING_END\") {\n // If upstream sends REASONING_END while a message is still open, emit\n // the missing REASONING_MESSAGE_END FIRST so the closing pair stays in\n // order (MSG_END before END). Otherwise the next non-reasoning chunk\n // would trigger closeReasoningIfOpen and emit MSG_END after END.\n if (reasoningMessageOpen) {\n reasoningMessageOpen = false;\n const msgEnd: ReasoningMessageEndEvent = {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n };\n yield msgEnd;\n }\n reasoningRunOpen = false;\n const evt: ReasoningEndEvent = {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n };\n yield evt;\n }\n }\n\n yield* closeReasoningIfOpen();\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n}\n"],"mappings":"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"}
|
package/dist/agent/index.cjs
CHANGED
|
@@ -42,6 +42,18 @@ function resolveModel(spec, apiKey) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
+
* Thrown by `AgentFactoryContext.interrupt()` on a fresh (non-resume) run to
|
|
46
|
+
* pause the factory. Caught in `runFactory` and translated into a RUN_FINISHED
|
|
47
|
+
* event carrying `outcome:{type:"interrupt",interrupts}`. Not a real error.
|
|
48
|
+
*/
|
|
49
|
+
var InterruptSignal = class extends Error {
|
|
50
|
+
constructor(interrupts) {
|
|
51
|
+
super("CopilotKit interrupt: run paused awaiting human input");
|
|
52
|
+
this.interrupts = interrupts;
|
|
53
|
+
this.name = "InterruptSignal";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
45
57
|
* Define a tool for use with BuiltInAgent
|
|
46
58
|
* @param name - The name of the tool
|
|
47
59
|
* @param description - Description of what the tool does
|
|
@@ -54,7 +66,10 @@ function defineTool(config) {
|
|
|
54
66
|
name: config.name,
|
|
55
67
|
description: config.description,
|
|
56
68
|
parameters: config.parameters,
|
|
57
|
-
execute: config.execute
|
|
69
|
+
execute: config.execute,
|
|
70
|
+
interrupt: config.interrupt,
|
|
71
|
+
interruptReason: config.interruptReason,
|
|
72
|
+
interruptMessage: config.interruptMessage
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
/**
|
|
@@ -270,6 +285,19 @@ function isJsonSchema(obj) {
|
|
|
270
285
|
"array"
|
|
271
286
|
].includes(schema.type);
|
|
272
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Type-only pass-through for handing a Zod schema to the AI SDK's `tool()`.
|
|
290
|
+
* The raw Zod schema is returned unchanged at runtime — the SDK's `asSchema()`
|
|
291
|
+
* converts and validates it internally exactly as before.
|
|
292
|
+
*
|
|
293
|
+
* The Zod type is erased through `unknown` deliberately: letting tsc relate
|
|
294
|
+
* Zod schema types to the AI SDK's `FlexibleSchema` union (conditional types
|
|
295
|
+
* spanning zod v3/v4) makes type instantiation explode (TS2589 / compiler
|
|
296
|
+
* OOM) under this package's `moduleResolution: node`.
|
|
297
|
+
*/
|
|
298
|
+
function toLanguageModelSchema(schema) {
|
|
299
|
+
return schema;
|
|
300
|
+
}
|
|
273
301
|
function convertToolsToVercelAITools(tools) {
|
|
274
302
|
const result = {};
|
|
275
303
|
for (const tool of tools) {
|
|
@@ -277,7 +305,7 @@ function convertToolsToVercelAITools(tools) {
|
|
|
277
305
|
const zodSchema = convertJsonSchemaToZodSchema(tool.parameters, true);
|
|
278
306
|
result[tool.name] = (0, ai.tool)({
|
|
279
307
|
description: tool.description,
|
|
280
|
-
inputSchema: zodSchema
|
|
308
|
+
inputSchema: toLanguageModelSchema(zodSchema)
|
|
281
309
|
});
|
|
282
310
|
}
|
|
283
311
|
return result;
|
|
@@ -336,16 +364,19 @@ var BuiltInAgent = class BuiltInAgent extends _ag_ui_client.AbstractAgent {
|
|
|
336
364
|
supported: true,
|
|
337
365
|
clientProvided: true
|
|
338
366
|
},
|
|
339
|
-
transport: { streaming: true }
|
|
367
|
+
transport: { streaming: true },
|
|
368
|
+
humanInTheLoop: { interrupts: true }
|
|
340
369
|
};
|
|
341
|
-
|
|
370
|
+
const capabilities = isFactoryConfig(this.config) ? void 0 : this.config.capabilities;
|
|
371
|
+
if (!capabilities) return inferred;
|
|
342
372
|
return {
|
|
343
373
|
...inferred,
|
|
344
|
-
...
|
|
374
|
+
...capabilities
|
|
345
375
|
};
|
|
346
376
|
}
|
|
347
377
|
run(input) {
|
|
348
378
|
if (isFactoryConfig(this.config)) return this.runFactory(input, this.config);
|
|
379
|
+
const config = this.config;
|
|
349
380
|
if (this.abortController) throw new Error("Agent is already running. Call abortRun() first or create a new instance.");
|
|
350
381
|
this.abortController = new AbortController();
|
|
351
382
|
const abortController = this.abortController;
|
|
@@ -356,14 +387,14 @@ var BuiltInAgent = class BuiltInAgent extends _ag_ui_client.AbstractAgent {
|
|
|
356
387
|
runId: input.runId
|
|
357
388
|
};
|
|
358
389
|
subscriber.next(startEvent);
|
|
359
|
-
const model = resolveModel(
|
|
390
|
+
const model = resolveModel(config.model, config.apiKey);
|
|
360
391
|
let systemPrompt = void 0;
|
|
361
|
-
const hasPrompt = !!
|
|
392
|
+
const hasPrompt = !!config.prompt;
|
|
362
393
|
const hasContext = input.context && input.context.length > 0;
|
|
363
394
|
const hasState = input.state !== void 0 && input.state !== null && !(typeof input.state === "object" && Object.keys(input.state).length === 0);
|
|
364
395
|
if (hasPrompt || hasContext || hasState) {
|
|
365
396
|
const parts = [];
|
|
366
|
-
if (hasPrompt) parts.push(
|
|
397
|
+
if (hasPrompt) parts.push(config.prompt);
|
|
367
398
|
if (hasContext) {
|
|
368
399
|
parts.push("\n## Context from the application\n");
|
|
369
400
|
for (const ctx of input.context) parts.push(`${ctx.description}:\n${ctx.value}\n`);
|
|
@@ -375,16 +406,46 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
375
406
|
systemPrompt = parts.join("");
|
|
376
407
|
}
|
|
377
408
|
const messages = convertMessagesToVercelAISDKMessages(input.messages, {
|
|
378
|
-
forwardSystemMessages:
|
|
379
|
-
forwardDeveloperMessages:
|
|
409
|
+
forwardSystemMessages: config.forwardSystemMessages,
|
|
410
|
+
forwardDeveloperMessages: config.forwardDeveloperMessages
|
|
380
411
|
});
|
|
381
412
|
if (systemPrompt) messages.unshift({
|
|
382
413
|
role: "system",
|
|
383
414
|
content: systemPrompt
|
|
384
415
|
});
|
|
416
|
+
const resumeEntries = input.resume ?? [];
|
|
417
|
+
if (resumeEntries.length > 0) {
|
|
418
|
+
const toolNameById = /* @__PURE__ */ new Map();
|
|
419
|
+
for (const m of input.messages) {
|
|
420
|
+
if (m.role !== "assistant") continue;
|
|
421
|
+
for (const tc of m.toolCalls ?? []) if (tc.id && tc.function?.name) toolNameById.set(tc.id, tc.function.name);
|
|
422
|
+
}
|
|
423
|
+
const alreadyAnswered = /* @__PURE__ */ new Set();
|
|
424
|
+
for (const m of messages) {
|
|
425
|
+
if (m.role !== "tool" || !Array.isArray(m.content)) continue;
|
|
426
|
+
for (const part of m.content) if (part && typeof part === "object" && "toolCallId" in part) alreadyAnswered.add(part.toolCallId);
|
|
427
|
+
}
|
|
428
|
+
for (const entry of resumeEntries) {
|
|
429
|
+
if (alreadyAnswered.has(entry.interruptId)) continue;
|
|
430
|
+
const value = entry.status === "cancelled" ? { status: "cancelled" } : entry.payload ?? { status: "resolved" };
|
|
431
|
+
const toolResultMessage = {
|
|
432
|
+
role: "tool",
|
|
433
|
+
content: [{
|
|
434
|
+
type: "tool-result",
|
|
435
|
+
toolCallId: entry.interruptId,
|
|
436
|
+
toolName: toolNameById.get(entry.interruptId) ?? "",
|
|
437
|
+
output: {
|
|
438
|
+
type: "json",
|
|
439
|
+
value
|
|
440
|
+
}
|
|
441
|
+
}]
|
|
442
|
+
};
|
|
443
|
+
messages.push(toolResultMessage);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
385
446
|
let allTools = convertToolsToVercelAITools(input.tools);
|
|
386
|
-
if (
|
|
387
|
-
const configTools = convertToolDefinitionsToVercelAITools(
|
|
447
|
+
if (config.tools && config.tools.length > 0) {
|
|
448
|
+
const configTools = convertToolDefinitionsToVercelAITools(config.tools);
|
|
388
449
|
allTools = {
|
|
389
450
|
...allTools,
|
|
390
451
|
...configTools
|
|
@@ -394,23 +455,23 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
394
455
|
model,
|
|
395
456
|
messages,
|
|
396
457
|
tools: allTools,
|
|
397
|
-
toolChoice:
|
|
398
|
-
stopWhen:
|
|
399
|
-
maxOutputTokens:
|
|
400
|
-
temperature:
|
|
401
|
-
topP:
|
|
402
|
-
topK:
|
|
403
|
-
presencePenalty:
|
|
404
|
-
frequencyPenalty:
|
|
405
|
-
stopSequences:
|
|
406
|
-
seed:
|
|
407
|
-
providerOptions:
|
|
408
|
-
maxRetries:
|
|
458
|
+
toolChoice: config.toolChoice,
|
|
459
|
+
stopWhen: config.maxSteps ? (0, ai.stepCountIs)(config.maxSteps) : void 0,
|
|
460
|
+
maxOutputTokens: config.maxOutputTokens,
|
|
461
|
+
temperature: config.temperature,
|
|
462
|
+
topP: config.topP,
|
|
463
|
+
topK: config.topK,
|
|
464
|
+
presencePenalty: config.presencePenalty,
|
|
465
|
+
frequencyPenalty: config.frequencyPenalty,
|
|
466
|
+
stopSequences: config.stopSequences,
|
|
467
|
+
seed: config.seed,
|
|
468
|
+
providerOptions: config.providerOptions,
|
|
469
|
+
maxRetries: config.maxRetries
|
|
409
470
|
};
|
|
410
471
|
if (input.forwardedProps && typeof input.forwardedProps === "object") {
|
|
411
472
|
const props = input.forwardedProps;
|
|
412
473
|
if (props.model !== void 0 && this.canOverride("model")) {
|
|
413
|
-
if (typeof props.model === "string" || typeof props.model === "object") streamTextParams.model = resolveModel(props.model,
|
|
474
|
+
if (typeof props.model === "string" || typeof props.model === "object") streamTextParams.model = resolveModel(props.model, config.apiKey);
|
|
414
475
|
}
|
|
415
476
|
if (props.toolChoice !== void 0 && this.canOverride("toolChoice")) {
|
|
416
477
|
const toolChoice = props.toolChoice;
|
|
@@ -456,7 +517,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
456
517
|
...streamTextParams.tools,
|
|
457
518
|
AGUISendStateSnapshot: (0, ai.tool)({
|
|
458
519
|
description: "Replace the entire application state with a new snapshot",
|
|
459
|
-
inputSchema: zod.z.object({ snapshot: zod.z.any().describe("The complete new state object") }),
|
|
520
|
+
inputSchema: toLanguageModelSchema(zod.z.object({ snapshot: zod.z.any().describe("The complete new state object") })),
|
|
460
521
|
execute: async ({ snapshot }) => {
|
|
461
522
|
return {
|
|
462
523
|
success: true,
|
|
@@ -466,7 +527,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
466
527
|
}),
|
|
467
528
|
AGUISendStateDelta: (0, ai.tool)({
|
|
468
529
|
description: "Apply incremental updates to application state using JSON Patch operations",
|
|
469
|
-
inputSchema: zod.z.object({ delta: zod.z.array(zod.z.object({
|
|
530
|
+
inputSchema: toLanguageModelSchema(zod.z.object({ delta: zod.z.array(zod.z.object({
|
|
470
531
|
op: zod.z.enum([
|
|
471
532
|
"add",
|
|
472
533
|
"replace",
|
|
@@ -474,7 +535,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
474
535
|
]).describe("The operation to perform"),
|
|
475
536
|
path: zod.z.string().describe("JSON Pointer path (e.g., '/foo/bar')"),
|
|
476
537
|
value: zod.z.any().optional().describe("The value to set. Required for 'add' and 'replace' operations, ignored for 'remove'.")
|
|
477
|
-
})).describe("Array of JSON Patch operations") }),
|
|
538
|
+
})).describe("Array of JSON Patch operations") })),
|
|
478
539
|
execute: async ({ delta }) => {
|
|
479
540
|
return {
|
|
480
541
|
success: true,
|
|
@@ -483,14 +544,14 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
483
544
|
}
|
|
484
545
|
})
|
|
485
546
|
};
|
|
486
|
-
if (
|
|
547
|
+
if (config.mcpClients && config.mcpClients.length > 0) for (const client of config.mcpClients) {
|
|
487
548
|
const mcpTools = await client.tools();
|
|
488
549
|
streamTextParams.tools = {
|
|
489
550
|
...streamTextParams.tools,
|
|
490
551
|
...mcpTools
|
|
491
552
|
};
|
|
492
553
|
}
|
|
493
|
-
const allMcpServers = [...
|
|
554
|
+
const allMcpServers = [...config.mcpServers ?? []];
|
|
494
555
|
if (allMcpServers.length > 0) for (const serverConfig of allMcpServers) {
|
|
495
556
|
let transport;
|
|
496
557
|
if (serverConfig.type === "http") transport = new _modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(serverConfig.url), serverConfig.options);
|
|
@@ -519,6 +580,12 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
519
580
|
...streamTextParams,
|
|
520
581
|
abortSignal: abortController.signal
|
|
521
582
|
});
|
|
583
|
+
const interruptToolNames = new Set((isFactoryConfig(this.config) ? [] : this.config.tools ?? []).filter((t) => t.interrupt).map((t) => t.name));
|
|
584
|
+
const interruptToolMeta = new Map((isFactoryConfig(this.config) ? [] : this.config.tools ?? []).filter((t) => t.interrupt).map((t) => [t.name, {
|
|
585
|
+
reason: t.interruptReason ?? "tool_call",
|
|
586
|
+
message: t.interruptMessage
|
|
587
|
+
}]));
|
|
588
|
+
const pendingInterrupts = [];
|
|
522
589
|
const toolCallStates = /* @__PURE__ */ new Map();
|
|
523
590
|
const ensureToolCallState = (toolCallId) => {
|
|
524
591
|
let state = toolCallStates.get(toolCallId);
|
|
@@ -660,11 +727,25 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
660
727
|
};
|
|
661
728
|
subscriber.next(endEvent);
|
|
662
729
|
}
|
|
730
|
+
if (state.toolName && interruptToolNames.has(state.toolName)) {
|
|
731
|
+
const meta = interruptToolMeta.get(state.toolName);
|
|
732
|
+
pendingInterrupts.push({
|
|
733
|
+
id: toolCallId,
|
|
734
|
+
toolCallId,
|
|
735
|
+
reason: meta?.reason ?? "tool_call",
|
|
736
|
+
...meta?.message ? { message: meta.message } : {}
|
|
737
|
+
});
|
|
738
|
+
}
|
|
663
739
|
break;
|
|
664
740
|
}
|
|
665
741
|
case "tool-result": {
|
|
666
|
-
const
|
|
667
|
-
|
|
742
|
+
const toolName = "toolName" in part && part.toolName || toolCallStates.get(part.toolCallId)?.toolName || "";
|
|
743
|
+
if (toolName && interruptToolNames.has(toolName)) {
|
|
744
|
+
toolCallStates.delete(part.toolCallId);
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
const legacyPart = part;
|
|
748
|
+
const toolResult = "output" in part ? part.output : "result" in legacyPart ? legacyPart.result : null;
|
|
668
749
|
toolCallStates.delete(part.toolCallId);
|
|
669
750
|
if (toolName === "AGUISendStateSnapshot" && toolResult && typeof toolResult === "object") {
|
|
670
751
|
const snapshot = toolResult.snapshot;
|
|
@@ -705,7 +786,11 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
705
786
|
const finishedEvent = {
|
|
706
787
|
type: _ag_ui_client.EventType.RUN_FINISHED,
|
|
707
788
|
threadId: input.threadId,
|
|
708
|
-
runId: input.runId
|
|
789
|
+
runId: input.runId,
|
|
790
|
+
...pendingInterrupts.length > 0 ? { outcome: {
|
|
791
|
+
type: "interrupt",
|
|
792
|
+
interrupts: pendingInterrupts
|
|
793
|
+
} } : {}
|
|
709
794
|
};
|
|
710
795
|
subscriber.next(finishedEvent);
|
|
711
796
|
terminalEventEmitted = true;
|
|
@@ -714,7 +799,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
714
799
|
}
|
|
715
800
|
case "error": {
|
|
716
801
|
if (abortController.signal.aborted) break;
|
|
717
|
-
const err = part.error ?? part.message ?? part.cause;
|
|
802
|
+
const err = part.error ?? ("message" in part ? part.message : void 0) ?? ("cause" in part ? part.cause : void 0);
|
|
718
803
|
const runErrorEvent = {
|
|
719
804
|
type: _ag_ui_client.EventType.RUN_ERROR,
|
|
720
805
|
message: err instanceof Error ? err.message : typeof err === "string" ? err : `AI SDK stream error: ${JSON.stringify(part)}`,
|
|
@@ -735,7 +820,11 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
735
820
|
const finishedEvent = {
|
|
736
821
|
type: _ag_ui_client.EventType.RUN_FINISHED,
|
|
737
822
|
threadId: input.threadId,
|
|
738
|
-
runId: input.runId
|
|
823
|
+
runId: input.runId,
|
|
824
|
+
...pendingInterrupts.length > 0 ? { outcome: {
|
|
825
|
+
type: "interrupt",
|
|
826
|
+
interrupts: pendingInterrupts
|
|
827
|
+
} } : {}
|
|
739
828
|
};
|
|
740
829
|
subscriber.next(finishedEvent);
|
|
741
830
|
}
|
|
@@ -780,17 +869,40 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
780
869
|
const ctx = {
|
|
781
870
|
input,
|
|
782
871
|
abortController: controller,
|
|
783
|
-
abortSignal: controller.signal
|
|
872
|
+
abortSignal: controller.signal,
|
|
873
|
+
interrupt: async (interrupts) => {
|
|
874
|
+
const resume = input.resume ?? [];
|
|
875
|
+
const ids = new Set(interrupts.map((i) => i.id));
|
|
876
|
+
const matching = resume.filter((r) => ids.has(r.interruptId));
|
|
877
|
+
if (interrupts.length > 0 && matching.length === interrupts.length) return matching;
|
|
878
|
+
throw new InterruptSignal(interrupts);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
const answeredToolCallIds = new Set(input.messages.filter((m) => m.role === "tool").map((m) => m.toolCallId).filter((id) => typeof id === "string"));
|
|
882
|
+
const resumeToolMessages = (input.resume ?? []).filter((entry) => !answeredToolCallIds.has(entry.interruptId)).map((entry) => ({
|
|
883
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
884
|
+
role: "tool",
|
|
885
|
+
toolCallId: entry.interruptId,
|
|
886
|
+
content: JSON.stringify(entry.status === "cancelled" ? { status: "cancelled" } : entry.payload ?? { status: "resolved" })
|
|
887
|
+
}));
|
|
888
|
+
const factoryInput = resumeToolMessages.length > 0 && config.type !== "custom" ? {
|
|
889
|
+
...input,
|
|
890
|
+
messages: [...input.messages, ...resumeToolMessages]
|
|
891
|
+
} : input;
|
|
892
|
+
const factoryCtx = {
|
|
893
|
+
...ctx,
|
|
894
|
+
input: factoryInput
|
|
784
895
|
};
|
|
785
896
|
(async () => {
|
|
786
897
|
try {
|
|
787
898
|
let events;
|
|
899
|
+
const pendingInterrupts = [];
|
|
788
900
|
switch (config.type) {
|
|
789
901
|
case "aisdk":
|
|
790
|
-
events = require_aisdk.convertAISDKStream((await config.factory(
|
|
902
|
+
events = require_aisdk.convertAISDKStream((await config.factory(factoryCtx)).fullStream, controller.signal, pendingInterrupts);
|
|
791
903
|
break;
|
|
792
904
|
case "tanstack":
|
|
793
|
-
events = require_tanstack.convertTanStackStream(await config.factory(
|
|
905
|
+
events = require_tanstack.convertTanStackStream(await config.factory(factoryCtx), controller.signal, pendingInterrupts);
|
|
794
906
|
break;
|
|
795
907
|
case "custom":
|
|
796
908
|
events = await config.factory(ctx);
|
|
@@ -801,6 +913,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
801
913
|
}
|
|
802
914
|
}
|
|
803
915
|
for await (const event of events) subscriber.next(event);
|
|
916
|
+
if (pendingInterrupts.length > 0 && !controller.signal.aborted) throw new InterruptSignal(pendingInterrupts);
|
|
804
917
|
if (!controller.signal.aborted) {
|
|
805
918
|
const finishedEvent = {
|
|
806
919
|
type: _ag_ui_client.EventType.RUN_FINISHED,
|
|
@@ -811,7 +924,19 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
811
924
|
}
|
|
812
925
|
subscriber.complete();
|
|
813
926
|
} catch (error) {
|
|
814
|
-
if (
|
|
927
|
+
if (error instanceof InterruptSignal) {
|
|
928
|
+
const finishedEvent = {
|
|
929
|
+
type: _ag_ui_client.EventType.RUN_FINISHED,
|
|
930
|
+
threadId: input.threadId,
|
|
931
|
+
runId: input.runId,
|
|
932
|
+
outcome: {
|
|
933
|
+
type: "interrupt",
|
|
934
|
+
interrupts: error.interrupts
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
subscriber.next(finishedEvent);
|
|
938
|
+
subscriber.complete();
|
|
939
|
+
} else if (controller.signal.aborted) subscriber.complete();
|
|
815
940
|
else {
|
|
816
941
|
const runErrorEvent = {
|
|
817
942
|
type: _ag_ui_client.EventType.RUN_ERROR,
|
|
@@ -853,6 +978,7 @@ var BasicAgent = class extends BuiltInAgent {
|
|
|
853
978
|
//#endregion
|
|
854
979
|
exports.BasicAgent = BasicAgent;
|
|
855
980
|
exports.BuiltInAgent = BuiltInAgent;
|
|
981
|
+
exports.InterruptSignal = InterruptSignal;
|
|
856
982
|
exports.convertJsonSchemaToZodSchema = convertJsonSchemaToZodSchema;
|
|
857
983
|
exports.convertMessagesToVercelAISDKMessages = convertMessagesToVercelAISDKMessages;
|
|
858
984
|
exports.convertToolDefinitionsToVercelAITools = convertToolDefinitionsToVercelAITools;
|