@copilotkit/runtime 1.75.2 → 1.77.0
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 +21 -9
- package/dist/agent/converters/aisdk.cjs.map +1 -1
- package/dist/agent/converters/aisdk.d.cts.map +1 -1
- package/dist/agent/converters/aisdk.d.mts.map +1 -1
- package/dist/agent/converters/aisdk.mjs +21 -10
- package/dist/agent/converters/aisdk.mjs.map +1 -1
- package/dist/agent/converters/tanstack.cjs +8 -6
- package/dist/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs +9 -7
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/agent/converters/usage.cjs +8 -2
- package/dist/agent/converters/usage.cjs.map +1 -1
- package/dist/agent/converters/usage.d.cts +3 -1
- package/dist/agent/converters/usage.d.cts.map +1 -1
- package/dist/agent/converters/usage.d.mts +3 -1
- package/dist/agent/converters/usage.d.mts.map +1 -1
- package/dist/agent/converters/usage.mjs +8 -2
- package/dist/agent/converters/usage.mjs.map +1 -1
- package/dist/agent/index.cjs +127 -34
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +18 -6
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +18 -6
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +129 -36
- package/dist/agent/index.mjs.map +1 -1
- package/dist/agent/mcp-tool-names.cjs +84 -0
- package/dist/agent/mcp-tool-names.cjs.map +1 -0
- package/dist/agent/mcp-tool-names.mjs +81 -0
- package/dist/agent/mcp-tool-names.mjs.map +1 -0
- package/dist/lib/runtime/copilot-runtime.cjs +20 -9
- 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 +20 -9
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/package.cjs +7 -8
- package/dist/package.mjs +7 -8
- package/dist/v2/runtime/core/fetch-handler.cjs +16 -1
- package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.mjs +16 -1
- package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.cjs +8 -0
- package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.mjs +8 -0
- package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
- package/dist/v2/runtime/core/hooks.cjs.map +1 -1
- package/dist/v2/runtime/core/hooks.d.cts +3 -0
- package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
- package/dist/v2/runtime/core/hooks.d.mts +3 -0
- package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
- package/dist/v2/runtime/core/hooks.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.cjs +1 -0
- package/dist/v2/runtime/endpoints/single-route-helpers.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/single-route-helpers.mjs +1 -0
- package/dist/v2/runtime/endpoints/single-route-helpers.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-trajectory-connect.cjs +41 -0
- package/dist/v2/runtime/handlers/handle-trajectory-connect.cjs.map +1 -0
- package/dist/v2/runtime/handlers/handle-trajectory-connect.mjs +40 -0
- package/dist/v2/runtime/handlers/handle-trajectory-connect.mjs.map +1 -0
- package/dist/v2/runtime/handlers/intelligence/connect.cjs +1 -0
- package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/connect.mjs +1 -0
- package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs +90 -4
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs +90 -4
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.cjs +20 -0
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +10 -0
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +10 -0
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +20 -0
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/trajectories.cjs +80 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.cjs.map +1 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.d.cts +12 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.d.cts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.d.mts +12 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.d.mts.map +1 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.mjs +76 -0
- package/dist/v2/runtime/intelligence-platform/trajectories.mjs.map +1 -0
- package/dist/v2/runtime/runner/in-memory.cjs +16 -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 +16 -1
- package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
- package/dist/v2/runtime/runner/intelligence.cjs +5 -2
- 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 +6 -3
- package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
- package/package.json +7 -10
|
@@ -2,7 +2,7 @@ import "reflect-metadata";
|
|
|
2
2
|
import { createStateEventNormalizer } from "../state-delta.mjs";
|
|
3
3
|
import { aggregateRunUsage, collectStandardRunFinishedDetails, getNonEmptyString, getTokenCount, isRecord } from "./usage.mjs";
|
|
4
4
|
import { randomUUID } from "@copilotkit/shared";
|
|
5
|
-
import { EventType } from "@ag-ui/client";
|
|
5
|
+
import { EventType, contentToText } from "@ag-ui/client";
|
|
6
6
|
|
|
7
7
|
//#region src/agent/converters/tanstack.ts
|
|
8
8
|
/**
|
|
@@ -42,6 +42,7 @@ function convertUserContent(content) {
|
|
|
42
42
|
mimeType: source.mimeType
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
+
else if (source.type === "file") console.warn(`[CopilotKit] Dropping a ${partType} part that references a provider file handle: it is not a URL or inline data, so it cannot be sent to the model here.`);
|
|
45
46
|
else if (source.type === "url") parts.push({
|
|
46
47
|
type: partType,
|
|
47
48
|
source: {
|
|
@@ -132,7 +133,7 @@ function convertInputToTanStackAI(input) {
|
|
|
132
133
|
const messages = input.messages.filter((m) => chatRoles.has(m.role)).map((m) => {
|
|
133
134
|
const msg = {
|
|
134
135
|
role: m.role,
|
|
135
|
-
content: m.role === "user" ? convertUserContent(m.content) : typeof m.content === "string" ? m.content : null
|
|
136
|
+
content: m.role === "user" ? convertUserContent(m.content) : m.role === "tool" ? contentToText(m.content) : typeof m.content === "string" ? m.content : null
|
|
136
137
|
};
|
|
137
138
|
if (m.role === "assistant" && "toolCalls" in m && m.toolCalls) msg.toolCalls = m.toolCalls.map((tc) => ({
|
|
138
139
|
id: tc.id,
|
|
@@ -336,16 +337,17 @@ async function* convertTanStackStream(stream, abortSignal, pendingInterrupts, in
|
|
|
336
337
|
/** Normalizes legacy and standard TanStack usage into AG-UI token usage. */
|
|
337
338
|
function collectTanStackRunFinishedDetails(event, details) {
|
|
338
339
|
if (!details) return;
|
|
339
|
-
if (typeof event.finishReason === "string") details.finishReason = event.finishReason;
|
|
340
340
|
const fallbackIdentity = {
|
|
341
341
|
provider: getNonEmptyString(event.provider),
|
|
342
342
|
model: getNonEmptyString(event.model)
|
|
343
343
|
};
|
|
344
|
+
collectStandardRunFinishedDetails(event, details, fallbackIdentity);
|
|
345
|
+
if (typeof event.finishReason === "string") details.metadata = {
|
|
346
|
+
...details.metadata,
|
|
347
|
+
finishReason: event.finishReason
|
|
348
|
+
};
|
|
344
349
|
const usage = event.usage;
|
|
345
|
-
if (Array.isArray(usage))
|
|
346
|
-
collectStandardRunFinishedDetails(event, details, fallbackIdentity);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
350
|
+
if (Array.isArray(usage)) return;
|
|
349
351
|
if (!isRecord(usage)) return;
|
|
350
352
|
const promptDetails = isRecord(usage.promptTokensDetails) ? usage.promptTokensDetails : {};
|
|
351
353
|
const completionDetails = isRecord(usage.completionTokensDetails) ? usage.completionTokensDetails : {};
|
|
@@ -1 +1 @@
|
|
|
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\";\nimport { createStateEventNormalizer } from \"../state-delta\";\nimport {\n aggregateRunUsage,\n collectStandardRunFinishedDetails,\n getNonEmptyString,\n getTokenCount,\n isRecord,\n} from \"./usage\";\nimport type { AgentRunFinishedDetails } from \"./usage\";\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 initialState?: unknown,\n runFinishedDetails?: AgentRunFinishedDetails,\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 const normalizeStateEvent = createStateEventNormalizer(initialState);\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_FINISHED\") {\n collectTanStackRunFinishedDetails(raw, runFinishedDetails);\n continue;\n }\n if (type === \"RUN_STARTED\") continue;\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 for (const event of normalizeStateEvent(stateSnapshotEvent)) {\n yield event;\n }\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 for (const event of normalizeStateEvent(stateDeltaEvent)) {\n yield event;\n }\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\n/** Normalizes legacy and standard TanStack usage into AG-UI token usage. */\nfunction collectTanStackRunFinishedDetails(\n event: Record<string, unknown>,\n details?: AgentRunFinishedDetails,\n): void {\n if (!details) return;\n\n if (typeof event.finishReason === \"string\") {\n details.finishReason = event.finishReason;\n }\n\n const fallbackIdentity = {\n provider: getNonEmptyString(event.provider),\n model: getNonEmptyString(event.model),\n };\n const usage = event.usage;\n\n if (Array.isArray(usage)) {\n collectStandardRunFinishedDetails(event, details, fallbackIdentity);\n return;\n }\n\n if (!isRecord(usage)) return;\n\n const promptDetails = isRecord(usage.promptTokensDetails)\n ? usage.promptTokensDetails\n : {};\n const completionDetails = isRecord(usage.completionTokensDetails)\n ? usage.completionTokensDetails\n : {};\n aggregateRunUsage(details, [\n {\n ...fallbackIdentity,\n inputTokens: getTokenCount(usage.promptTokens),\n outputTokens: getTokenCount(usage.completionTokens),\n totalTokens: getTokenCount(usage.totalTokens),\n reasoningTokens: getTokenCount(completionDetails.reasoningTokens),\n cachedInputTokens: getTokenCount(promptDetails.cachedTokens),\n },\n ]);\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 { contentToText, EventType } from \"@ag-ui/client\";\nimport { randomUUID } from \"@copilotkit/shared\";\nimport { createStateEventNormalizer } from \"../state-delta\";\nimport {\n aggregateRunUsage,\n collectStandardRunFinishedDetails,\n getNonEmptyString,\n getTokenCount,\n isRecord,\n} from \"./usage\";\nimport type { AgentRunFinishedDetails } from \"./usage\";\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 === \"file\") {\n // AG-UI 1.0 provider file handle: not a URL or inline data.\n console.warn(\n `[CopilotKit] Dropping a ${partType} part that references a provider file handle: it is not a URL or inline data, so it cannot be sent to the model here.`,\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 : m.role === \"tool\"\n ? // A tool result is a string or a list of parts; TanStack takes\n // text here, so the text parts are concatenated.\n contentToText(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 initialState?: unknown,\n runFinishedDetails?: AgentRunFinishedDetails,\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 const normalizeStateEvent = createStateEventNormalizer(initialState);\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_FINISHED\") {\n collectTanStackRunFinishedDetails(raw, runFinishedDetails);\n continue;\n }\n if (type === \"RUN_STARTED\") continue;\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 for (const event of normalizeStateEvent(stateSnapshotEvent)) {\n yield event;\n }\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 for (const event of normalizeStateEvent(stateDeltaEvent)) {\n yield event;\n }\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\n/** Normalizes legacy and standard TanStack usage into AG-UI token usage. */\nfunction collectTanStackRunFinishedDetails(\n event: Record<string, unknown>,\n details?: AgentRunFinishedDetails,\n): void {\n if (!details) return;\n\n const fallbackIdentity = {\n provider: getNonEmptyString(event.provider),\n model: getNonEmptyString(event.model),\n };\n collectStandardRunFinishedDetails(event, details, fallbackIdentity);\n\n // TanStack's native finish reason becomes AG-UI terminal metadata.\n if (typeof event.finishReason === \"string\") {\n details.metadata = {\n ...details.metadata,\n finishReason: event.finishReason,\n };\n }\n\n const usage = event.usage;\n\n if (Array.isArray(usage)) {\n return;\n }\n\n if (!isRecord(usage)) return;\n\n const promptDetails = isRecord(usage.promptTokensDetails)\n ? usage.promptTokensDetails\n : {};\n const completionDetails = isRecord(usage.completionTokensDetails)\n ? usage.completionTokensDetails\n : {};\n aggregateRunUsage(details, [\n {\n ...fallbackIdentity,\n inputTokens: getTokenCount(usage.promptTokens),\n outputTokens: getTokenCount(usage.completionTokens),\n totalTokens: getTokenCount(usage.totalTokens),\n reasoningTokens: getTokenCount(completionDetails.reasoningTokens),\n cachedInputTokens: getTokenCount(promptDetails.cachedTokens),\n },\n ]);\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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"}
|
|
@@ -30,9 +30,15 @@ function aggregateRunUsage(details, entries) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/** Keeps terminal metadata and the last supplied string reason; accumulates usage. */
|
|
34
34
|
function collectStandardRunFinishedDetails(event, details, fallbackIdentity = {}) {
|
|
35
|
-
|
|
35
|
+
const previousFinishReason = details.metadata?.finishReason;
|
|
36
|
+
if (isRecord(event.metadata)) details.metadata = { ...event.metadata };
|
|
37
|
+
else delete details.metadata;
|
|
38
|
+
if (typeof details.metadata?.finishReason !== "string" && typeof previousFinishReason === "string") details.metadata = {
|
|
39
|
+
...details.metadata,
|
|
40
|
+
finishReason: previousFinishReason
|
|
41
|
+
};
|
|
36
42
|
if (!Array.isArray(event.usage)) return;
|
|
37
43
|
aggregateRunUsage(details, event.usage.flatMap((entry) => {
|
|
38
44
|
if (!isRecord(entry)) return [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.cjs","names":[],"sources":["../../../src/agent/converters/usage.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"usage.cjs","names":[],"sources":["../../../src/agent/converters/usage.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\n\nexport interface AgentRunUsage {\n provider?: string;\n model?: string;\n inputTokens?: number;\n outputTokens?: number;\n totalTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AgentRunFinishedDetails {\n metadata?: BaseEvent[\"metadata\"];\n usage?: AgentRunUsage[];\n}\n\nexport const tokenCountKeys = [\n \"inputTokens\",\n \"outputTokens\",\n \"totalTokens\",\n \"reasoningTokens\",\n \"cachedInputTokens\",\n] as const;\n\n/** Returns a token count only when it is a safe, non-negative integer. */\nexport function getTokenCount(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0\n ? value\n : undefined;\n}\n\n/** Adds usage entries to a run, grouping and summing matching identities. */\nexport function aggregateRunUsage(\n details: AgentRunFinishedDetails,\n entries: AgentRunUsage[],\n): void {\n for (const entry of entries) {\n const hasCount = tokenCountKeys.some((key) => entry[key] !== undefined);\n if (!hasCount) continue;\n\n details.usage ??= [];\n const existing = details.usage.find(\n (candidate) =>\n candidate.provider === entry.provider &&\n candidate.model === entry.model,\n );\n\n if (!existing) {\n details.usage.push({ ...entry });\n continue;\n }\n\n for (const key of tokenCountKeys) {\n const value = entry[key];\n if (value === undefined) continue;\n\n const sum = (existing[key] ?? 0) + value;\n if (Number.isSafeInteger(sum)) {\n existing[key] = sum;\n }\n }\n }\n}\n\n/** Keeps terminal metadata and the last supplied string reason; accumulates usage. */\nexport function collectStandardRunFinishedDetails(\n event: Record<string, unknown>,\n details: AgentRunFinishedDetails,\n fallbackIdentity: { provider?: string; model?: string } = {},\n): void {\n const previousFinishReason = details.metadata?.finishReason;\n if (isRecord(event.metadata)) {\n details.metadata = { ...event.metadata };\n } else {\n delete details.metadata;\n }\n // Match the previous top-level finishReason behavior: only a new string\n // replaces the reason, while other metadata comes from the latest event.\n if (\n typeof details.metadata?.finishReason !== \"string\" &&\n typeof previousFinishReason === \"string\"\n ) {\n details.metadata = {\n ...details.metadata,\n finishReason: previousFinishReason,\n };\n }\n\n if (!Array.isArray(event.usage)) return;\n\n aggregateRunUsage(\n details,\n event.usage.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n\n const normalized: AgentRunUsage = {\n provider:\n getNonEmptyString(entry.provider) ?? fallbackIdentity.provider,\n model: getNonEmptyString(entry.model) ?? fallbackIdentity.model,\n };\n for (const key of tokenCountKeys) {\n normalized[key] = getTokenCount(entry[key]);\n }\n return [normalized];\n }),\n );\n}\n\n/** Narrows an unknown value to a string-keyed object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** Returns a non-empty string identity without changing its value. */\nexport function getNonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n"],"mappings":";;;AAiBA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACD;;AAGD,SAAgB,cAAc,OAAoC;AAChE,QAAO,OAAO,UAAU,YAAY,OAAO,cAAc,MAAM,IAAI,SAAS,IACxE,QACA;;;AAIN,SAAgB,kBACd,SACA,SACM;AACN,MAAK,MAAM,SAAS,SAAS;AAE3B,MAAI,CADa,eAAe,MAAM,QAAQ,MAAM,SAAS,OAAU,CACxD;AAEf,UAAQ,UAAU,EAAE;EACpB,MAAM,WAAW,QAAQ,MAAM,MAC5B,cACC,UAAU,aAAa,MAAM,YAC7B,UAAU,UAAU,MAAM,MAC7B;AAED,MAAI,CAAC,UAAU;AACb,WAAQ,MAAM,KAAK,EAAE,GAAG,OAAO,CAAC;AAChC;;AAGF,OAAK,MAAM,OAAO,gBAAgB;GAChC,MAAM,QAAQ,MAAM;AACpB,OAAI,UAAU,OAAW;GAEzB,MAAM,OAAO,SAAS,QAAQ,KAAK;AACnC,OAAI,OAAO,cAAc,IAAI,CAC3B,UAAS,OAAO;;;;;AAOxB,SAAgB,kCACd,OACA,SACA,mBAA0D,EAAE,EACtD;CACN,MAAM,uBAAuB,QAAQ,UAAU;AAC/C,KAAI,SAAS,MAAM,SAAS,CAC1B,SAAQ,WAAW,EAAE,GAAG,MAAM,UAAU;KAExC,QAAO,QAAQ;AAIjB,KACE,OAAO,QAAQ,UAAU,iBAAiB,YAC1C,OAAO,yBAAyB,SAEhC,SAAQ,WAAW;EACjB,GAAG,QAAQ;EACX,cAAc;EACf;AAGH,KAAI,CAAC,MAAM,QAAQ,MAAM,MAAM,CAAE;AAEjC,mBACE,SACA,MAAM,MAAM,SAAS,UAAU;AAC7B,MAAI,CAAC,SAAS,MAAM,CAAE,QAAO,EAAE;EAE/B,MAAM,aAA4B;GAChC,UACE,kBAAkB,MAAM,SAAS,IAAI,iBAAiB;GACxD,OAAO,kBAAkB,MAAM,MAAM,IAAI,iBAAiB;GAC3D;AACD,OAAK,MAAM,OAAO,eAChB,YAAW,OAAO,cAAc,MAAM,KAAK;AAE7C,SAAO,CAAC,WAAW;GACnB,CACH;;;AAIH,SAAgB,SAAS,OAAkD;AACzE,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;;AAI7E,SAAgB,kBAAkB,OAAoC;AACpE,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
|
+
import { BaseEvent } from "@ag-ui/client";
|
|
3
|
+
|
|
2
4
|
//#region src/agent/converters/usage.d.ts
|
|
3
5
|
interface AgentRunUsage {
|
|
4
6
|
provider?: string;
|
|
@@ -10,7 +12,7 @@ interface AgentRunUsage {
|
|
|
10
12
|
cachedInputTokens?: number;
|
|
11
13
|
}
|
|
12
14
|
interface AgentRunFinishedDetails {
|
|
13
|
-
|
|
15
|
+
metadata?: BaseEvent["metadata"];
|
|
14
16
|
usage?: AgentRunUsage[];
|
|
15
17
|
}
|
|
16
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.cts","names":[],"sources":["../../../src/agent/converters/usage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"usage.d.cts","names":[],"sources":["../../../src/agent/converters/usage.ts"],"mappings":";;;;UAEiB,aAAA;EACf,QAAA;EACA,KAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;AAAA;AAAA,UAGe,uBAAA;EACf,QAAA,GAAW,SAAA;EACX,KAAA,GAAQ,aAAA;AAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
|
+
import { BaseEvent } from "@ag-ui/client";
|
|
3
|
+
|
|
2
4
|
//#region src/agent/converters/usage.d.ts
|
|
3
5
|
interface AgentRunUsage {
|
|
4
6
|
provider?: string;
|
|
@@ -10,7 +12,7 @@ interface AgentRunUsage {
|
|
|
10
12
|
cachedInputTokens?: number;
|
|
11
13
|
}
|
|
12
14
|
interface AgentRunFinishedDetails {
|
|
13
|
-
|
|
15
|
+
metadata?: BaseEvent["metadata"];
|
|
14
16
|
usage?: AgentRunUsage[];
|
|
15
17
|
}
|
|
16
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.mts","names":[],"sources":["../../../src/agent/converters/usage.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"usage.d.mts","names":[],"sources":["../../../src/agent/converters/usage.ts"],"mappings":";;;;UAEiB,aAAA;EACf,QAAA;EACA,KAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;AAAA;AAAA,UAGe,uBAAA;EACf,QAAA,GAAW,SAAA;EACX,KAAA,GAAQ,aAAA;AAAA"}
|
|
@@ -29,9 +29,15 @@ function aggregateRunUsage(details, entries) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/** Keeps terminal metadata and the last supplied string reason; accumulates usage. */
|
|
33
33
|
function collectStandardRunFinishedDetails(event, details, fallbackIdentity = {}) {
|
|
34
|
-
|
|
34
|
+
const previousFinishReason = details.metadata?.finishReason;
|
|
35
|
+
if (isRecord(event.metadata)) details.metadata = { ...event.metadata };
|
|
36
|
+
else delete details.metadata;
|
|
37
|
+
if (typeof details.metadata?.finishReason !== "string" && typeof previousFinishReason === "string") details.metadata = {
|
|
38
|
+
...details.metadata,
|
|
39
|
+
finishReason: previousFinishReason
|
|
40
|
+
};
|
|
35
41
|
if (!Array.isArray(event.usage)) return;
|
|
36
42
|
aggregateRunUsage(details, event.usage.flatMap((entry) => {
|
|
37
43
|
if (!isRecord(entry)) return [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.mjs","names":[],"sources":["../../../src/agent/converters/usage.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"usage.mjs","names":[],"sources":["../../../src/agent/converters/usage.ts"],"sourcesContent":["import type { BaseEvent } from \"@ag-ui/client\";\n\nexport interface AgentRunUsage {\n provider?: string;\n model?: string;\n inputTokens?: number;\n outputTokens?: number;\n totalTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AgentRunFinishedDetails {\n metadata?: BaseEvent[\"metadata\"];\n usage?: AgentRunUsage[];\n}\n\nexport const tokenCountKeys = [\n \"inputTokens\",\n \"outputTokens\",\n \"totalTokens\",\n \"reasoningTokens\",\n \"cachedInputTokens\",\n] as const;\n\n/** Returns a token count only when it is a safe, non-negative integer. */\nexport function getTokenCount(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0\n ? value\n : undefined;\n}\n\n/** Adds usage entries to a run, grouping and summing matching identities. */\nexport function aggregateRunUsage(\n details: AgentRunFinishedDetails,\n entries: AgentRunUsage[],\n): void {\n for (const entry of entries) {\n const hasCount = tokenCountKeys.some((key) => entry[key] !== undefined);\n if (!hasCount) continue;\n\n details.usage ??= [];\n const existing = details.usage.find(\n (candidate) =>\n candidate.provider === entry.provider &&\n candidate.model === entry.model,\n );\n\n if (!existing) {\n details.usage.push({ ...entry });\n continue;\n }\n\n for (const key of tokenCountKeys) {\n const value = entry[key];\n if (value === undefined) continue;\n\n const sum = (existing[key] ?? 0) + value;\n if (Number.isSafeInteger(sum)) {\n existing[key] = sum;\n }\n }\n }\n}\n\n/** Keeps terminal metadata and the last supplied string reason; accumulates usage. */\nexport function collectStandardRunFinishedDetails(\n event: Record<string, unknown>,\n details: AgentRunFinishedDetails,\n fallbackIdentity: { provider?: string; model?: string } = {},\n): void {\n const previousFinishReason = details.metadata?.finishReason;\n if (isRecord(event.metadata)) {\n details.metadata = { ...event.metadata };\n } else {\n delete details.metadata;\n }\n // Match the previous top-level finishReason behavior: only a new string\n // replaces the reason, while other metadata comes from the latest event.\n if (\n typeof details.metadata?.finishReason !== \"string\" &&\n typeof previousFinishReason === \"string\"\n ) {\n details.metadata = {\n ...details.metadata,\n finishReason: previousFinishReason,\n };\n }\n\n if (!Array.isArray(event.usage)) return;\n\n aggregateRunUsage(\n details,\n event.usage.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n\n const normalized: AgentRunUsage = {\n provider:\n getNonEmptyString(entry.provider) ?? fallbackIdentity.provider,\n model: getNonEmptyString(entry.model) ?? fallbackIdentity.model,\n };\n for (const key of tokenCountKeys) {\n normalized[key] = getTokenCount(entry[key]);\n }\n return [normalized];\n }),\n );\n}\n\n/** Narrows an unknown value to a string-keyed object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** Returns a non-empty string identity without changing its value. */\nexport function getNonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n"],"mappings":";;AAiBA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACD;;AAGD,SAAgB,cAAc,OAAoC;AAChE,QAAO,OAAO,UAAU,YAAY,OAAO,cAAc,MAAM,IAAI,SAAS,IACxE,QACA;;;AAIN,SAAgB,kBACd,SACA,SACM;AACN,MAAK,MAAM,SAAS,SAAS;AAE3B,MAAI,CADa,eAAe,MAAM,QAAQ,MAAM,SAAS,OAAU,CACxD;AAEf,UAAQ,UAAU,EAAE;EACpB,MAAM,WAAW,QAAQ,MAAM,MAC5B,cACC,UAAU,aAAa,MAAM,YAC7B,UAAU,UAAU,MAAM,MAC7B;AAED,MAAI,CAAC,UAAU;AACb,WAAQ,MAAM,KAAK,EAAE,GAAG,OAAO,CAAC;AAChC;;AAGF,OAAK,MAAM,OAAO,gBAAgB;GAChC,MAAM,QAAQ,MAAM;AACpB,OAAI,UAAU,OAAW;GAEzB,MAAM,OAAO,SAAS,QAAQ,KAAK;AACnC,OAAI,OAAO,cAAc,IAAI,CAC3B,UAAS,OAAO;;;;;AAOxB,SAAgB,kCACd,OACA,SACA,mBAA0D,EAAE,EACtD;CACN,MAAM,uBAAuB,QAAQ,UAAU;AAC/C,KAAI,SAAS,MAAM,SAAS,CAC1B,SAAQ,WAAW,EAAE,GAAG,MAAM,UAAU;KAExC,QAAO,QAAQ;AAIjB,KACE,OAAO,QAAQ,UAAU,iBAAiB,YAC1C,OAAO,yBAAyB,SAEhC,SAAQ,WAAW;EACjB,GAAG,QAAQ;EACX,cAAc;EACf;AAGH,KAAI,CAAC,MAAM,QAAQ,MAAM,MAAM,CAAE;AAEjC,mBACE,SACA,MAAM,MAAM,SAAS,UAAU;AAC7B,MAAI,CAAC,SAAS,MAAM,CAAE,QAAO,EAAE;EAE/B,MAAM,aAA4B;GAChC,UACE,kBAAkB,MAAM,SAAS,IAAI,iBAAiB;GACxD,OAAO,kBAAkB,MAAM,MAAM,IAAI,iBAAiB;GAC3D;AACD,OAAK,MAAM,OAAO,eAChB,YAAW,OAAO,cAAc,MAAM,KAAK;AAE7C,SAAO,CAAC,WAAW;GACnB,CACH;;;AAIH,SAAgB,SAAS,OAAkD;AACzE,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;;AAI7E,SAAgB,kBAAkB,OAAoC;AACpE,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ"}
|
package/dist/agent/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ const require_aisdk = require('./converters/aisdk.cjs');
|
|
|
6
6
|
const require_tanstack = require('./converters/tanstack.cjs');
|
|
7
7
|
const require_registry = require('../v2/runtime/intelligence-platform/skill-registry/registry.cjs');
|
|
8
8
|
const require_learned_skills = require('./learned-skills.cjs');
|
|
9
|
+
const require_mcp_tool_names = require('./mcp-tool-names.cjs');
|
|
9
10
|
let _ai_sdk_openai = require("@ai-sdk/openai");
|
|
10
11
|
let _copilotkit_shared = require("@copilotkit/shared");
|
|
11
12
|
let _ai_sdk_google = require("@ai-sdk/google");
|
|
@@ -27,6 +28,64 @@ let _modelcontextprotocol_sdk_client_streamableHttp_js = require("@modelcontextp
|
|
|
27
28
|
* @param apiKey - Optional API key to use instead of environment variables
|
|
28
29
|
* @returns LanguageModel instance
|
|
29
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* An AG-UI tool result as the AI SDK's tool result output.
|
|
33
|
+
*
|
|
34
|
+
* Providers hold one response per tool call, and the Google adapter emits one
|
|
35
|
+
* functionResponse per text entry of a content list, so all of a result's text
|
|
36
|
+
* is collected into a single entry: text parts run together, and a
|
|
37
|
+
* URL-referenced part contributes the URL it carries on a line of its own —
|
|
38
|
+
* the bytes are not here to hand over, and the reference is the content the
|
|
39
|
+
* tool actually returned. A result with no inline media is that text alone.
|
|
40
|
+
* One with inline media is a content list: the text entry first, when there is
|
|
41
|
+
* any, then each media part with its bytes and media type. A media-only result
|
|
42
|
+
* is media alone: a placeholder text would be content the tool never returned.
|
|
43
|
+
* Which adapters can place media inside a tool response is theirs to decide.
|
|
44
|
+
* A provider file handle (`file` source) is neither bytes nor a URL, and this
|
|
45
|
+
* path cannot hand it to the provider, so the part is dropped with a warning.
|
|
46
|
+
* A result whose parts were all dropped is the empty string, as AG-UI 1.0
|
|
47
|
+
* requires: the call must still be answered.
|
|
48
|
+
*/
|
|
49
|
+
function warnDroppedFileSource(what) {
|
|
50
|
+
console.warn(`[CopilotKit] Dropping a ${what} that references a provider file handle: it is not a URL or inline data, so it cannot be sent to the model here.`);
|
|
51
|
+
}
|
|
52
|
+
function toolResultOutput(content) {
|
|
53
|
+
if (typeof content === "string") return {
|
|
54
|
+
type: "text",
|
|
55
|
+
value: content
|
|
56
|
+
};
|
|
57
|
+
const segments = [];
|
|
58
|
+
const media = [];
|
|
59
|
+
let open = false;
|
|
60
|
+
for (const part of content) if (part.type === "text") {
|
|
61
|
+
if (open) segments[segments.length - 1] += part.text;
|
|
62
|
+
else segments.push(part.text);
|
|
63
|
+
open = true;
|
|
64
|
+
} else if (part.source.type === "file") warnDroppedFileSource(`${part.type} part in a tool result`);
|
|
65
|
+
else if (part.source.type === "url") {
|
|
66
|
+
segments.push(part.source.value);
|
|
67
|
+
open = false;
|
|
68
|
+
} else {
|
|
69
|
+
media.push({
|
|
70
|
+
type: "media",
|
|
71
|
+
data: part.source.value,
|
|
72
|
+
mediaType: part.source.mimeType
|
|
73
|
+
});
|
|
74
|
+
open = false;
|
|
75
|
+
}
|
|
76
|
+
const text = segments.join("\n");
|
|
77
|
+
if (media.length === 0) return {
|
|
78
|
+
type: "text",
|
|
79
|
+
value: text
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
type: "content",
|
|
83
|
+
value: [...text.length > 0 ? [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text
|
|
86
|
+
}] : [], ...media]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
30
89
|
function resolveModel(spec, apiKey) {
|
|
31
90
|
if (typeof spec !== "string") return spec;
|
|
32
91
|
const parts = spec.replace("/", ":").trim().split(":");
|
|
@@ -151,6 +210,7 @@ function convertUserMessageContent(content) {
|
|
|
151
210
|
image: source.value,
|
|
152
211
|
mediaType: source.mimeType
|
|
153
212
|
});
|
|
213
|
+
else if (source.type === "file") warnDroppedFileSource("image part");
|
|
154
214
|
else if (source.type === "url") try {
|
|
155
215
|
parts.push({
|
|
156
216
|
type: "image",
|
|
@@ -172,6 +232,7 @@ function convertUserMessageContent(content) {
|
|
|
172
232
|
data: source.value,
|
|
173
233
|
mediaType: source.mimeType
|
|
174
234
|
});
|
|
235
|
+
else if (source.type === "file") warnDroppedFileSource(`${part.type} part`);
|
|
175
236
|
else if (source.type === "url") try {
|
|
176
237
|
parts.push({
|
|
177
238
|
type: "file",
|
|
@@ -277,10 +338,7 @@ function convertMessagesToVercelAISDKMessages(messages, options = {}) {
|
|
|
277
338
|
type: "tool-result",
|
|
278
339
|
toolCallId: message.toolCallId,
|
|
279
340
|
toolName,
|
|
280
|
-
output:
|
|
281
|
-
type: "text",
|
|
282
|
-
value: message.content
|
|
283
|
-
}
|
|
341
|
+
output: toolResultOutput(message.content)
|
|
284
342
|
}]
|
|
285
343
|
};
|
|
286
344
|
result.push(toolMsg);
|
|
@@ -458,7 +516,8 @@ var BuiltInAgent = class BuiltInAgent extends _ag_ui_client.AbstractAgent {
|
|
|
458
516
|
const startEvent = {
|
|
459
517
|
type: _ag_ui_client.EventType.RUN_STARTED,
|
|
460
518
|
threadId: input.threadId,
|
|
461
|
-
runId: input.runId
|
|
519
|
+
runId: input.runId,
|
|
520
|
+
protocolVersion: _ag_ui_client.PROTOCOL_VERSION
|
|
462
521
|
};
|
|
463
522
|
subscriber.next(startEvent);
|
|
464
523
|
const model = resolveModel(config.model, config.apiKey);
|
|
@@ -627,16 +686,19 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
627
686
|
}
|
|
628
687
|
})
|
|
629
688
|
};
|
|
630
|
-
|
|
689
|
+
const mcpToolSources = [];
|
|
690
|
+
const userMcpClients = config.mcpClients ?? [];
|
|
691
|
+
for (const [index, client] of userMcpClients.entries()) {
|
|
631
692
|
const mcpTools = await client.tools();
|
|
632
693
|
abortController.signal.throwIfAborted();
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
};
|
|
694
|
+
mcpToolSources.push({
|
|
695
|
+
label: `mcp${index + 1}`,
|
|
696
|
+
tools: mcpTools
|
|
697
|
+
});
|
|
637
698
|
}
|
|
638
699
|
const allMcpServers = [...config.mcpServers ?? []];
|
|
639
|
-
if (allMcpServers.length > 0) for (const serverConfig of allMcpServers) {
|
|
700
|
+
if (allMcpServers.length > 0) for (const [index, serverConfig] of allMcpServers.entries()) {
|
|
701
|
+
const label = serverConfig.name ? require_mcp_tool_names.toToolNamePrefix(serverConfig.name) : `mcp${userMcpClients.length + index + 1}`;
|
|
640
702
|
let transport;
|
|
641
703
|
if (serverConfig.type === "http") transport = new _modelcontextprotocol_sdk_client_streamableHttp_js.StreamableHTTPClientTransport(new URL(serverConfig.url), serverConfig.options);
|
|
642
704
|
else if (serverConfig.type === "sse") {
|
|
@@ -661,16 +723,17 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
661
723
|
try {
|
|
662
724
|
const mcpTools = await mcpClient.tools();
|
|
663
725
|
abortController.signal.throwIfAborted();
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
};
|
|
726
|
+
mcpToolSources.push({
|
|
727
|
+
label,
|
|
728
|
+
tools: mcpTools
|
|
729
|
+
});
|
|
668
730
|
} catch (err) {
|
|
669
731
|
abortController.signal.throwIfAborted();
|
|
670
732
|
console.error(`[CopilotKit] MCP server ${serverConfig.url} tools() failed — skipping its tools for this run:`, err);
|
|
671
733
|
}
|
|
672
734
|
}
|
|
673
735
|
}
|
|
736
|
+
if (mcpToolSources.length > 0) streamTextParams.tools = require_mcp_tool_names.mergeMCPTools(streamTextParams.tools ?? {}, mcpToolSources);
|
|
674
737
|
if (this.skillRegistry) {
|
|
675
738
|
require_learned_skills.assertNoSkillToolConflicts(streamTextParams.tools ?? {});
|
|
676
739
|
streamTextParams.tools = {
|
|
@@ -711,7 +774,8 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
711
774
|
const abortEndEvent = {
|
|
712
775
|
type: _ag_ui_client.EventType.RUN_FINISHED,
|
|
713
776
|
threadId: input.threadId,
|
|
714
|
-
runId: input.runId
|
|
777
|
+
runId: input.runId,
|
|
778
|
+
...input.protocolVersion !== void 0 ? { outcome: { type: "cancelled" } } : {}
|
|
715
779
|
};
|
|
716
780
|
subscriber.next(abortEndEvent);
|
|
717
781
|
terminalEventEmitted = true;
|
|
@@ -719,8 +783,7 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
719
783
|
break;
|
|
720
784
|
}
|
|
721
785
|
case "reasoning-start": {
|
|
722
|
-
|
|
723
|
-
reasoningMessageId = !providedId || providedId === "0" || /^(txt|reasoning|msg)-0$/.test(providedId) ? (0, _copilotkit_shared.randomUUID)() : providedId;
|
|
786
|
+
reasoningMessageId = require_aisdk.resolveStreamPartMessageId("id" in part ? part.id : void 0);
|
|
724
787
|
const reasoningStartEvent = {
|
|
725
788
|
type: _ag_ui_client.EventType.REASONING_START,
|
|
726
789
|
messageId: reasoningMessageId
|
|
@@ -776,11 +839,9 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
776
839
|
break;
|
|
777
840
|
}
|
|
778
841
|
case "tool-input-end": break;
|
|
779
|
-
case "text-start":
|
|
780
|
-
|
|
781
|
-
messageId = !providedId || providedId === "0" || /^(txt|reasoning|msg)-0$/.test(providedId) ? (0, _copilotkit_shared.randomUUID)() : providedId;
|
|
842
|
+
case "text-start":
|
|
843
|
+
messageId = require_aisdk.resolveStreamPartMessageId("id" in part ? part.id : void 0);
|
|
782
844
|
break;
|
|
783
|
-
}
|
|
784
845
|
case "text-delta": {
|
|
785
846
|
const textDelta = "text" in part ? part.text : "";
|
|
786
847
|
const textEvent = {
|
|
@@ -1011,7 +1072,8 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
1011
1072
|
const startEvent = {
|
|
1012
1073
|
type: _ag_ui_client.EventType.RUN_STARTED,
|
|
1013
1074
|
threadId: input.threadId,
|
|
1014
|
-
runId: input.runId
|
|
1075
|
+
runId: input.runId,
|
|
1076
|
+
protocolVersion: _ag_ui_client.PROTOCOL_VERSION
|
|
1015
1077
|
};
|
|
1016
1078
|
subscriber.next(startEvent);
|
|
1017
1079
|
const ctx = {
|
|
@@ -1026,20 +1088,47 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
1026
1088
|
throw new InterruptSignal(interrupts);
|
|
1027
1089
|
}
|
|
1028
1090
|
};
|
|
1029
|
-
const answeredToolCallIds = new Set(input.messages.filter((m) => m.role === "tool").map((m) => m.toolCallId).filter((id) => typeof id === "string"));
|
|
1030
|
-
const resumeToolMessages = (input.resume ?? []).filter((entry) => !answeredToolCallIds.has(entry.interruptId)).map((entry) => ({
|
|
1031
|
-
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1032
|
-
role: "tool",
|
|
1033
|
-
toolCallId: entry.interruptId,
|
|
1034
|
-
content: JSON.stringify(entry.status === "cancelled" ? { status: "cancelled" } : entry.payload ?? { status: "resolved" })
|
|
1035
|
-
}));
|
|
1036
|
-
const factoryInput = resumeToolMessages.length > 0 && config.type !== "custom" ? {
|
|
1037
|
-
...input,
|
|
1038
|
-
messages: [...input.messages, ...resumeToolMessages]
|
|
1039
|
-
} : input;
|
|
1040
1091
|
(async () => {
|
|
1041
1092
|
const runFinishedDetails = {};
|
|
1042
1093
|
try {
|
|
1094
|
+
const toolCallIds = new Set(input.messages.flatMap((message) => message.role === "assistant" ? (message.toolCalls ?? []).map((call) => call.id) : []));
|
|
1095
|
+
const answeredToolMessages = input.messages.filter((message) => message.role === "tool");
|
|
1096
|
+
const answeredToolCallIds = new Set(answeredToolMessages.map((message) => message.toolCallId));
|
|
1097
|
+
const nativeResume = config.type === "custom" ? [] : input.resume ?? [];
|
|
1098
|
+
const resumeToolMessages = [];
|
|
1099
|
+
const decisionsByCall = /* @__PURE__ */ new Map();
|
|
1100
|
+
for (const entry of nativeResume) {
|
|
1101
|
+
if (!toolCallIds.has(entry.interruptId)) continue;
|
|
1102
|
+
const content = JSON.stringify(entry.status === "cancelled" ? { status: "cancelled" } : entry.payload ?? { status: "resolved" });
|
|
1103
|
+
if (decisionsByCall.has(entry.interruptId)) {
|
|
1104
|
+
if (decisionsByCall.get(entry.interruptId) !== content) throw new Error(`Conflicting decisions for resumed tool call ${entry.interruptId}`);
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
decisionsByCall.set(entry.interruptId, content);
|
|
1108
|
+
if (answeredToolCallIds.has(entry.interruptId)) continue;
|
|
1109
|
+
resumeToolMessages.push({
|
|
1110
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
1111
|
+
role: "tool",
|
|
1112
|
+
toolCallId: entry.interruptId,
|
|
1113
|
+
content
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
const resumedIds = new Set(nativeResume.map((entry) => entry.interruptId));
|
|
1117
|
+
const resumedResults = new Map([...answeredToolMessages, ...resumeToolMessages].filter((message) => resumedIds.has(message.toolCallId)).map((message) => [message.toolCallId, message.content]));
|
|
1118
|
+
for (const message of resumeToolMessages) {
|
|
1119
|
+
const event = {
|
|
1120
|
+
type: _ag_ui_client.EventType.TOOL_CALL_RESULT,
|
|
1121
|
+
messageId: message.id,
|
|
1122
|
+
toolCallId: message.toolCallId,
|
|
1123
|
+
role: "tool",
|
|
1124
|
+
content: message.content
|
|
1125
|
+
};
|
|
1126
|
+
subscriber.next(event);
|
|
1127
|
+
}
|
|
1128
|
+
const factoryInput = resumeToolMessages.length > 0 && config.type !== "custom" ? {
|
|
1129
|
+
...input,
|
|
1130
|
+
messages: [...input.messages, ...resumeToolMessages]
|
|
1131
|
+
} : input;
|
|
1043
1132
|
const learnedSkills = await require_learned_skills.prepareLearnedSkills(this.skillRegistry, controller.signal);
|
|
1044
1133
|
controller.signal.throwIfAborted();
|
|
1045
1134
|
const factoryCtx = {
|
|
@@ -1066,6 +1155,10 @@ This is state from the application that you can edit by calling AGUISendStateSna
|
|
|
1066
1155
|
}
|
|
1067
1156
|
}
|
|
1068
1157
|
for await (const event of events) {
|
|
1158
|
+
if (event.type === _ag_ui_client.EventType.TOOL_CALL_RESULT && "toolCallId" in event && typeof event.toolCallId === "string" && "content" in event && resumedResults.has(event.toolCallId)) {
|
|
1159
|
+
if (resumedResults.get(event.toolCallId) !== event.content) throw new Error(`Conflicting result for resumed tool call ${event.toolCallId}`);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1069
1162
|
if (config.type === "custom" && event.type === _ag_ui_client.EventType.RUN_FINISHED) {
|
|
1070
1163
|
customRunFinishedEvent = event;
|
|
1071
1164
|
require_usage.collectStandardRunFinishedDetails(event, runFinishedDetails);
|