@tanstack/ai 0.19.1 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/adapter.d.ts +9 -3
- package/dist/esm/activities/chat/adapter.js +3 -1
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +13 -4
- package/dist/esm/activities/chat/index.js +46 -18
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +1 -1
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.js +2 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +8 -7
- package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
- package/dist/esm/activities/chat/stream/processor.js +29 -21
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
- package/dist/esm/activities/chat/stream/strategies.js +4 -4
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +36 -35
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +3 -1
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +5 -1
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +3 -1
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +5 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +3 -1
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +3 -1
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +3 -1
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +6 -2
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +3 -1
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +2 -0
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/middlewares/content-guard.js +5 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/otel.js +38 -16
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +1 -1
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/system-prompts.d.ts +66 -0
- package/dist/esm/system-prompts.js +23 -0
- package/dist/esm/system-prompts.js.map +1 -0
- package/dist/esm/tools/provider-tool.d.ts +9 -0
- package/dist/esm/tools/provider-tool.js +7 -0
- package/dist/esm/tools/provider-tool.js.map +1 -0
- package/dist/esm/types.d.ts +22 -7
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +2 -2
- package/src/activities/chat/adapter.ts +11 -2
- package/src/activities/chat/index.ts +46 -27
- package/src/activities/chat/messages.ts +2 -1
- package/src/activities/chat/middleware/types.ts +14 -11
- package/src/activities/chat/stream/processor.ts +19 -19
- package/src/activities/chat/stream/strategies.ts +3 -3
- package/src/activities/chat/tools/schema-converter.ts +77 -52
- package/src/activities/chat/tools/tool-calls.ts +18 -11
- package/src/activities/chat/tools/tool-definition.ts +6 -1
- package/src/activities/generateAudio/index.ts +5 -4
- package/src/activities/generateImage/index.ts +5 -0
- package/src/activities/stream-generation-result.ts +11 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -2
- package/src/activities/summarize/index.ts +2 -2
- package/src/index.ts +5 -0
- package/src/middlewares/content-guard.ts +11 -3
- package/src/middlewares/otel.ts +47 -16
- package/src/realtime/index.ts +1 -1
- package/src/system-prompts.ts +98 -0
- package/src/tools/provider-tool.ts +14 -0
- package/src/types.ts +28 -9
- package/src/utilities/ag-ui-wire.ts +2 -2
- package/src/utilities/chat-params.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type { JSONSchema, ModelMessage, Tool, UIMessage } from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n context: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: parsed.context,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as
|
|
1
|
+
{"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type { JSONSchema, ModelMessage, Tool, UIMessage } from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n context: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: parsed.context,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools(\n serverTools: ReadonlyArray<Tool>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool> = [...serverTools]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool)\n }\n return merged\n}\n"],"names":[],"mappings":";AAIA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MASvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,EAAA,CACjB;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKa;AACb,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAsB,CAAC,GAAG,WAAW;AAC3C,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGT;AAAA,EACX;AACA,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Core TanStack AI library - Open source AI SDK",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@ag-ui/core": "^0.0.52",
|
|
57
57
|
"partial-json": "^0.1.7",
|
|
58
|
-
"@tanstack/ai-event-client": "0.3.
|
|
58
|
+
"@tanstack/ai-event-client": "0.3.6"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@opentelemetry/api": ">=1.9.0"
|
|
@@ -55,6 +55,10 @@ export interface StructuredOutputResult<T = unknown> {
|
|
|
55
55
|
* - TMessageMetadata: Metadata types for content parts (already resolved)
|
|
56
56
|
* - TToolCapabilities: Tuple of tool-kind strings supported by this model, resolved from `supports.tools`
|
|
57
57
|
* - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)
|
|
58
|
+
* - TSystemPromptMetadata: Provider-typed metadata accepted on each
|
|
59
|
+
* `systemPrompts[i]` entry (e.g. Anthropic `cache_control`). Defaults to
|
|
60
|
+
* `never` — adapters without per-prompt metadata reject the `metadata`
|
|
61
|
+
* field at the call site.
|
|
58
62
|
*/
|
|
59
63
|
export interface TextAdapter<
|
|
60
64
|
TModel extends string,
|
|
@@ -63,6 +67,7 @@ export interface TextAdapter<
|
|
|
63
67
|
TMessageMetadataByModality extends DefaultMessageMetadataByModality,
|
|
64
68
|
TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,
|
|
65
69
|
TToolCallMetadata = unknown,
|
|
70
|
+
TSystemPromptMetadata = never,
|
|
66
71
|
> {
|
|
67
72
|
/** Discriminator for adapter kind */
|
|
68
73
|
readonly kind: 'text'
|
|
@@ -80,6 +85,7 @@ export interface TextAdapter<
|
|
|
80
85
|
messageMetadataByModality: TMessageMetadataByModality
|
|
81
86
|
toolCapabilities: TToolCapabilities
|
|
82
87
|
toolCallMetadata: TToolCallMetadata
|
|
88
|
+
systemPromptMetadata: TSystemPromptMetadata
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/**
|
|
@@ -123,7 +129,7 @@ export interface TextAdapter<
|
|
|
123
129
|
* A TextAdapter with any/unknown type parameters.
|
|
124
130
|
* Useful as a constraint in generic functions and interfaces.
|
|
125
131
|
*/
|
|
126
|
-
export type AnyTextAdapter = TextAdapter<any, any, any, any, any, any>
|
|
132
|
+
export type AnyTextAdapter = TextAdapter<any, any, any, any, any, any, any>
|
|
127
133
|
|
|
128
134
|
/**
|
|
129
135
|
* Abstract base class for text adapters.
|
|
@@ -138,13 +144,15 @@ export abstract class BaseTextAdapter<
|
|
|
138
144
|
TMessageMetadataByModality extends DefaultMessageMetadataByModality,
|
|
139
145
|
TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,
|
|
140
146
|
TToolCallMetadata = unknown,
|
|
147
|
+
TSystemPromptMetadata = never,
|
|
141
148
|
> implements TextAdapter<
|
|
142
149
|
TModel,
|
|
143
150
|
TProviderOptions,
|
|
144
151
|
TInputModalities,
|
|
145
152
|
TMessageMetadataByModality,
|
|
146
153
|
TToolCapabilities,
|
|
147
|
-
TToolCallMetadata
|
|
154
|
+
TToolCallMetadata,
|
|
155
|
+
TSystemPromptMetadata
|
|
148
156
|
> {
|
|
149
157
|
readonly kind = 'text' as const
|
|
150
158
|
abstract readonly name: string
|
|
@@ -157,6 +165,7 @@ export abstract class BaseTextAdapter<
|
|
|
157
165
|
messageMetadataByModality: TMessageMetadataByModality
|
|
158
166
|
toolCapabilities: TToolCapabilities
|
|
159
167
|
toolCallMetadata: TToolCallMetadata
|
|
168
|
+
systemPromptMetadata: TSystemPromptMetadata
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
protected config: TextAdapterConfig
|
|
@@ -56,6 +56,7 @@ import type {
|
|
|
56
56
|
ChatMiddlewareContext,
|
|
57
57
|
ChatMiddlewarePhase,
|
|
58
58
|
} from './middleware/types'
|
|
59
|
+
import type { SystemPrompt } from '../../system-prompts'
|
|
59
60
|
import type { InternalLogger } from '../../logger/internal-logger'
|
|
60
61
|
import type { DebugOption } from '../../logger/types'
|
|
61
62
|
import type { ProviderTool } from '../../tools/provider-tool'
|
|
@@ -94,16 +95,28 @@ export interface TextActivityOptions<
|
|
|
94
95
|
*
|
|
95
96
|
* The three shapes can be mixed in a single array (e.g., when forwarding a wire payload that includes both anchor UIMessages and AG-UI fan-out ModelMessages).
|
|
96
97
|
*/
|
|
97
|
-
messages?:
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
98
|
+
messages?:
|
|
99
|
+
| Array<
|
|
100
|
+
| UIMessage
|
|
101
|
+
| ModelMessage
|
|
102
|
+
| ConstrainedModelMessage<{
|
|
103
|
+
inputModalities: TAdapter['~types']['inputModalities']
|
|
104
|
+
messageMetadataByModality: TAdapter['~types']['messageMetadataByModality']
|
|
105
|
+
}>
|
|
106
|
+
>
|
|
107
|
+
| undefined
|
|
108
|
+
/**
|
|
109
|
+
* System prompts to prepend to the conversation.
|
|
110
|
+
*
|
|
111
|
+
* Accepts plain strings or `{ content, metadata }` objects. The `metadata`
|
|
112
|
+
* field is typed by the adapter — Anthropic narrows it to
|
|
113
|
+
* `AnthropicSystemPromptMetadata` (with `cache_control` for prompt
|
|
114
|
+
* caching), providers without per-prompt metadata reject the field
|
|
115
|
+
* entirely.
|
|
116
|
+
*/
|
|
117
|
+
systemPrompts?:
|
|
118
|
+
| Array<SystemPrompt<TAdapter['~types']['systemPromptMetadata']>>
|
|
119
|
+
| undefined
|
|
107
120
|
/**
|
|
108
121
|
* Tools for function calling (auto-executed when called).
|
|
109
122
|
*
|
|
@@ -114,10 +127,12 @@ export interface TextActivityOptions<
|
|
|
114
127
|
* `supports.tools` list. Passing an unsupported tool produces a
|
|
115
128
|
* compile-time error on the array element.
|
|
116
129
|
*/
|
|
117
|
-
tools?:
|
|
118
|
-
|
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
tools?:
|
|
131
|
+
| Array<
|
|
132
|
+
| (Tool & { readonly '~toolKind'?: never })
|
|
133
|
+
| ProviderTool<string, TAdapter['~types']['toolCapabilities'][number]>
|
|
134
|
+
>
|
|
135
|
+
| undefined
|
|
121
136
|
/** Controls the randomness of the output. Higher values make output more random. Range: [0.0, 2.0] */
|
|
122
137
|
temperature?: TextOptions['temperature']
|
|
123
138
|
/** Nucleus sampling parameter. The model considers tokens with topP probability mass. */
|
|
@@ -275,7 +290,7 @@ interface TextEngineConfig<
|
|
|
275
290
|
TParams extends TextOptions<any, any> = TextOptions<any>,
|
|
276
291
|
> {
|
|
277
292
|
adapter: TAdapter
|
|
278
|
-
systemPrompts?: Array<
|
|
293
|
+
systemPrompts?: Array<SystemPrompt>
|
|
279
294
|
params: TParams
|
|
280
295
|
middleware?: Array<ChatMiddleware>
|
|
281
296
|
context?: unknown
|
|
@@ -290,7 +305,7 @@ class TextEngine<
|
|
|
290
305
|
> {
|
|
291
306
|
private readonly adapter: TAdapter
|
|
292
307
|
private params: TParams
|
|
293
|
-
private systemPrompts: Array<
|
|
308
|
+
private systemPrompts: Array<SystemPrompt>
|
|
294
309
|
private tools: Array<Tool>
|
|
295
310
|
private readonly loopStrategy: AgentLoopStrategy
|
|
296
311
|
private toolCallManager: ToolCallManager
|
|
@@ -312,7 +327,7 @@ class TextEngine<
|
|
|
312
327
|
[]
|
|
313
328
|
private currentThinkingContent = ''
|
|
314
329
|
private currentThinkingSignature = ''
|
|
315
|
-
private eventOptions?: Record<string, unknown>
|
|
330
|
+
private eventOptions?: Record<string, unknown> | undefined
|
|
316
331
|
private eventToolNames?: Array<string>
|
|
317
332
|
private finishedEvent: RunFinishedEvent | null = null
|
|
318
333
|
private earlyTermination = false
|
|
@@ -323,16 +338,16 @@ class TextEngine<
|
|
|
323
338
|
private readonly initialClientToolResults: Map<string, any>
|
|
324
339
|
|
|
325
340
|
// AG-UI protocol IDs
|
|
326
|
-
private threadId: string
|
|
327
|
-
private runIdOverride?: string
|
|
328
|
-
private parentRunIdOverride?: string
|
|
341
|
+
private readonly threadId: string
|
|
342
|
+
private readonly runIdOverride?: string
|
|
343
|
+
private readonly parentRunIdOverride?: string
|
|
329
344
|
|
|
330
345
|
// Middleware support
|
|
331
346
|
private readonly middlewareRunner: MiddlewareRunner
|
|
332
347
|
private readonly middlewareCtx: ChatMiddlewareContext
|
|
333
348
|
private readonly deferredPromises: Array<Promise<unknown>> = []
|
|
334
349
|
private abortReason?: string
|
|
335
|
-
private middlewareAbortController?: AbortController
|
|
350
|
+
private readonly middlewareAbortController?: AbortController
|
|
336
351
|
private terminalHookCalled = false
|
|
337
352
|
|
|
338
353
|
private readonly logger: InternalLogger
|
|
@@ -391,8 +406,11 @@ class TextEngine<
|
|
|
391
406
|
// handleStreamChunk processes raw chunks BEFORE middleware, so internal
|
|
392
407
|
// state management sees extended fields (finishReason, delta, toolCallName, etc.).
|
|
393
408
|
// The strip middleware ensures the yielded public stream is AG-UI spec-compliant.
|
|
394
|
-
|
|
395
|
-
|
|
409
|
+
// `devtoolsMiddleware()` returns a structurally compatible
|
|
410
|
+
// `DevtoolsChatMiddleware` (defined in `@tanstack/ai-event-client` to
|
|
411
|
+
// avoid a circular dep). Cast it to `ChatMiddleware` for the runner.
|
|
412
|
+
const allMiddleware: Array<ChatMiddleware> = [
|
|
413
|
+
devtoolsMiddleware() as ChatMiddleware,
|
|
396
414
|
...(config.middleware || []),
|
|
397
415
|
stripToSpecMiddleware(),
|
|
398
416
|
]
|
|
@@ -692,6 +710,7 @@ class TextEngine<
|
|
|
692
710
|
}
|
|
693
711
|
|
|
694
712
|
private handleStreamChunk(chunk: StreamChunk): void {
|
|
713
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.
|
|
695
714
|
switch (chunk.type) {
|
|
696
715
|
// AG-UI Events
|
|
697
716
|
case 'TEXT_MESSAGE_CONTENT':
|
|
@@ -990,11 +1009,10 @@ class TextEngine<
|
|
|
990
1009
|
return true
|
|
991
1010
|
})
|
|
992
1011
|
|
|
993
|
-
if (undiscoveredLazyResults.length > 0) {
|
|
994
|
-
const finishEvt = this.finishedEvent!
|
|
1012
|
+
if (undiscoveredLazyResults.length > 0 && this.finishedEvent) {
|
|
995
1013
|
for (const chunk of this.buildToolResultChunks(
|
|
996
1014
|
undiscoveredLazyResults,
|
|
997
|
-
|
|
1015
|
+
this.finishedEvent,
|
|
998
1016
|
)) {
|
|
999
1017
|
yield* this.pipeThroughMiddleware(chunk)
|
|
1000
1018
|
}
|
|
@@ -1691,8 +1709,9 @@ async function* runStreamingText(
|
|
|
1691
1709
|
function runNonStreamingText(
|
|
1692
1710
|
options: TextActivityOptions<AnyTextAdapter, undefined, false>,
|
|
1693
1711
|
): Promise<string> {
|
|
1694
|
-
// Run the streaming text and collect all text using streamToText
|
|
1712
|
+
// Run the streaming text and collect all text using streamToText.
|
|
1695
1713
|
const stream = runStreamingText(
|
|
1714
|
+
// eslint-disable-next-line no-restricted-syntax -- generic-stream remap: caller is non-streaming (false), but runStreamingText is invoked internally to collect text; concrete `false`→`true` literals don't structurally overlap.
|
|
1696
1715
|
options as unknown as TextActivityOptions<AnyTextAdapter, undefined, true>,
|
|
1697
1716
|
)
|
|
1698
1717
|
|
|
@@ -463,12 +463,13 @@ export function modelMessagesToUIMessages(
|
|
|
463
463
|
if (msg.role === 'tool') {
|
|
464
464
|
// Tool result - merge into the last assistant message if possible
|
|
465
465
|
if (
|
|
466
|
+
msg.toolCallId !== undefined &&
|
|
466
467
|
currentAssistantMessage &&
|
|
467
468
|
currentAssistantMessage.role === 'assistant'
|
|
468
469
|
) {
|
|
469
470
|
currentAssistantMessage.parts.push({
|
|
470
471
|
type: 'tool-result',
|
|
471
|
-
toolCallId: msg.toolCallId
|
|
472
|
+
toolCallId: msg.toolCallId,
|
|
472
473
|
content: getTextContent(msg.content),
|
|
473
474
|
state: 'complete',
|
|
474
475
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelMessage, StreamChunk, Tool, ToolCall } from '../../../types'
|
|
2
|
+
import type { SystemPrompt } from '../../../system-prompts'
|
|
2
3
|
|
|
3
4
|
// ===========================
|
|
4
5
|
// Middleware Context
|
|
@@ -74,13 +75,13 @@ export interface ChatMiddlewareContext {
|
|
|
74
75
|
// --- Config-derived info (may update per-iteration via onConfig) ---
|
|
75
76
|
|
|
76
77
|
/** System prompts configured for this chat */
|
|
77
|
-
systemPrompts: Array<
|
|
78
|
+
systemPrompts: Array<SystemPrompt>
|
|
78
79
|
/** Names of configured tools, if any */
|
|
79
80
|
toolNames?: Array<string>
|
|
80
81
|
/** Flattened generation options (temperature, topP, maxTokens, metadata) */
|
|
81
|
-
options?: Record<string, unknown>
|
|
82
|
+
options?: Record<string, unknown> | undefined
|
|
82
83
|
/** Provider-specific model options */
|
|
83
|
-
modelOptions?: Record<string, unknown>
|
|
84
|
+
modelOptions?: Record<string, unknown> | undefined
|
|
84
85
|
|
|
85
86
|
// --- Computed info ---
|
|
86
87
|
|
|
@@ -115,13 +116,13 @@ export interface ChatMiddlewareContext {
|
|
|
115
116
|
*/
|
|
116
117
|
export interface ChatMiddlewareConfig {
|
|
117
118
|
messages: Array<ModelMessage>
|
|
118
|
-
systemPrompts: Array<
|
|
119
|
+
systemPrompts: Array<SystemPrompt>
|
|
119
120
|
tools: Array<Tool>
|
|
120
121
|
temperature?: number
|
|
121
122
|
topP?: number
|
|
122
123
|
maxTokens?: number
|
|
123
|
-
metadata?: Record<string, unknown>
|
|
124
|
-
modelOptions?: Record<string, unknown>
|
|
124
|
+
metadata?: Record<string, unknown> | undefined
|
|
125
|
+
modelOptions?: Record<string, unknown> | undefined
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// ===========================
|
|
@@ -256,11 +257,13 @@ export interface FinishInfo {
|
|
|
256
257
|
/** Final accumulated text content */
|
|
257
258
|
content: string
|
|
258
259
|
/** Final usage totals, if available */
|
|
259
|
-
usage?:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
usage?:
|
|
261
|
+
| {
|
|
262
|
+
promptTokens: number
|
|
263
|
+
completionTokens: number
|
|
264
|
+
totalTokens: number
|
|
265
|
+
}
|
|
266
|
+
| undefined
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
/**
|
|
@@ -133,25 +133,25 @@ export interface StreamProcessorOptions {
|
|
|
133
133
|
* @see docs/chat-architecture.md#adapter-contract — What this class expects from adapters
|
|
134
134
|
*/
|
|
135
135
|
export class StreamProcessor {
|
|
136
|
-
private chunkStrategy: ChunkStrategy
|
|
137
|
-
private events: StreamProcessorEvents
|
|
138
|
-
private jsonParser: { parse: (jsonString: string) => any }
|
|
136
|
+
private readonly chunkStrategy: ChunkStrategy
|
|
137
|
+
private readonly events: StreamProcessorEvents
|
|
138
|
+
private readonly jsonParser: { parse: (jsonString: string) => any }
|
|
139
139
|
private recordingEnabled: boolean
|
|
140
140
|
|
|
141
141
|
// Message state
|
|
142
142
|
private messages: Array<UIMessage> = []
|
|
143
143
|
|
|
144
144
|
// Per-message stream state
|
|
145
|
-
private messageStates: Map<string, MessageStreamState> = new Map()
|
|
146
|
-
private activeMessageIds: Set<string> = new Set()
|
|
147
|
-
private toolCallToMessage: Map<string, string> = new Map()
|
|
145
|
+
private readonly messageStates: Map<string, MessageStreamState> = new Map()
|
|
146
|
+
private readonly activeMessageIds: Set<string> = new Set()
|
|
147
|
+
private readonly toolCallToMessage: Map<string, string> = new Map()
|
|
148
148
|
private pendingManualMessageId: string | null = null
|
|
149
149
|
private pendingThinkingStepId: string | null = null
|
|
150
150
|
|
|
151
|
-
private structuredMessageIds: Set<string> = new Set()
|
|
151
|
+
private readonly structuredMessageIds: Set<string> = new Set()
|
|
152
152
|
|
|
153
153
|
// Run tracking (for concurrent run safety)
|
|
154
|
-
private activeRuns = new Set<string>()
|
|
154
|
+
private readonly activeRuns = new Set<string>()
|
|
155
155
|
|
|
156
156
|
// Shared stream state
|
|
157
157
|
private finishReason: string | null = null
|
|
@@ -480,6 +480,7 @@ export class StreamProcessor {
|
|
|
480
480
|
// Cast needed: @ag-ui/core Zod passthrough types add `& { [k: string]: unknown }`
|
|
481
481
|
// which prevents TypeScript from narrowing the `type` discriminant in switch.
|
|
482
482
|
const c = chunk as StreamChunk & { type: string }
|
|
483
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.
|
|
483
484
|
switch (c.type) {
|
|
484
485
|
// AG-UI Events
|
|
485
486
|
case 'TEXT_MESSAGE_START':
|
|
@@ -645,10 +646,9 @@ export class StreamProcessor {
|
|
|
645
646
|
* Used as fallback for events that don't include a messageId.
|
|
646
647
|
*/
|
|
647
648
|
private getActiveAssistantMessageId(): string | null {
|
|
648
|
-
// Set iteration is insertion-order;
|
|
649
|
-
const ids = Array.from(this.activeMessageIds)
|
|
650
|
-
for (
|
|
651
|
-
const id = ids[i]!
|
|
649
|
+
// Set iteration is insertion-order; reverse-iterate to search from the end
|
|
650
|
+
const ids = Array.from(this.activeMessageIds).reverse()
|
|
651
|
+
for (const id of ids) {
|
|
652
652
|
const state = this.messageStates.get(id)
|
|
653
653
|
if (state && state.role === 'assistant') {
|
|
654
654
|
return id
|
|
@@ -679,8 +679,8 @@ export class StreamProcessor {
|
|
|
679
679
|
// Try active assistant message
|
|
680
680
|
const activeId = this.getActiveAssistantMessageId()
|
|
681
681
|
if (activeId) {
|
|
682
|
-
const state = this.getMessageState(activeId)
|
|
683
|
-
return { messageId: activeId, state }
|
|
682
|
+
const state = this.getMessageState(activeId)
|
|
683
|
+
if (state) return { messageId: activeId, state }
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
// Check if a message with preferredId already exists (reconnect/resume case).
|
|
@@ -781,10 +781,10 @@ export class StreamProcessor {
|
|
|
781
781
|
const existingMsg = this.messages.find((m) => m.id === messageId)
|
|
782
782
|
if (existingMsg) {
|
|
783
783
|
this.activeMessageIds.add(messageId)
|
|
784
|
-
|
|
784
|
+
const existingState = this.messageStates.get(messageId)
|
|
785
|
+
if (!existingState) {
|
|
785
786
|
this.createMessageState(messageId, uiRole)
|
|
786
787
|
} else {
|
|
787
|
-
const existingState = this.messageStates.get(messageId)!
|
|
788
788
|
// If tool calls happened since last text, this TEXT_MESSAGE_START
|
|
789
789
|
// signals a new text segment — reset segment accumulation
|
|
790
790
|
if (existingState.hasToolCallsSinceTextStart) {
|
|
@@ -845,7 +845,7 @@ export class StreamProcessor {
|
|
|
845
845
|
): void {
|
|
846
846
|
this.resetStreamState()
|
|
847
847
|
// AG-UI Message[] is compatible with UIMessage[] at runtime
|
|
848
|
-
this.messages = [...chunk.messages] as
|
|
848
|
+
this.messages = [...chunk.messages] as Array<UIMessage>
|
|
849
849
|
this.emitMessagesChange()
|
|
850
850
|
}
|
|
851
851
|
|
|
@@ -1365,7 +1365,7 @@ export class StreamProcessor {
|
|
|
1365
1365
|
state.currentThinkingStepId = stepId
|
|
1366
1366
|
}
|
|
1367
1367
|
|
|
1368
|
-
const previous = state.thinkingSteps.get(stepId)
|
|
1368
|
+
const previous = state.thinkingSteps.get(stepId) ?? ''
|
|
1369
1369
|
let nextThinking = previous
|
|
1370
1370
|
|
|
1371
1371
|
// Prefer delta over content
|
|
@@ -1900,7 +1900,7 @@ export function createReplayStream(
|
|
|
1900
1900
|
recording: ChunkRecording,
|
|
1901
1901
|
): AsyncIterable<StreamChunk> {
|
|
1902
1902
|
return {
|
|
1903
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1903
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- async generator required by AsyncIterable contract; body has no await
|
|
1904
1904
|
async *[Symbol.asyncIterator]() {
|
|
1905
1905
|
for (const { chunk } of recording.chunks) {
|
|
1906
1906
|
yield chunk
|
|
@@ -20,7 +20,7 @@ export class ImmediateStrategy implements ChunkStrategy {
|
|
|
20
20
|
* Useful for natural text flow in UI
|
|
21
21
|
*/
|
|
22
22
|
export class PunctuationStrategy implements ChunkStrategy {
|
|
23
|
-
private punctuation = /[.,!?;:\n]/
|
|
23
|
+
private readonly punctuation = /[.,!?;:\n]/
|
|
24
24
|
|
|
25
25
|
shouldEmit(chunk: string, _accumulated: string): boolean {
|
|
26
26
|
return this.punctuation.test(chunk)
|
|
@@ -34,7 +34,7 @@ export class PunctuationStrategy implements ChunkStrategy {
|
|
|
34
34
|
export class BatchStrategy implements ChunkStrategy {
|
|
35
35
|
private chunkCount = 0
|
|
36
36
|
|
|
37
|
-
constructor(private batchSize: number = 5) {}
|
|
37
|
+
constructor(private readonly batchSize: number = 5) {}
|
|
38
38
|
|
|
39
39
|
shouldEmit(_chunk: string, _accumulated: string): boolean {
|
|
40
40
|
this.chunkCount++
|
|
@@ -66,7 +66,7 @@ export class WordBoundaryStrategy implements ChunkStrategy {
|
|
|
66
66
|
* Emits if ANY strategy says to emit
|
|
67
67
|
*/
|
|
68
68
|
export class CompositeStrategy implements ChunkStrategy {
|
|
69
|
-
constructor(private strategies: Array<ChunkStrategy>) {}
|
|
69
|
+
constructor(private readonly strategies: Array<ChunkStrategy>) {}
|
|
70
70
|
|
|
71
71
|
shouldEmit(chunk: string, accumulated: string): boolean {
|
|
72
72
|
return this.strategies.some((s) => s.shouldEmit(chunk, accumulated))
|