@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.
Files changed (93) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +9 -3
  2. package/dist/esm/activities/chat/adapter.js +3 -1
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/index.d.ts +13 -4
  5. package/dist/esm/activities/chat/index.js +46 -18
  6. package/dist/esm/activities/chat/index.js.map +1 -1
  7. package/dist/esm/activities/chat/messages.js +1 -1
  8. package/dist/esm/activities/chat/messages.js.map +1 -1
  9. package/dist/esm/activities/chat/middleware/compose.js +2 -0
  10. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  11. package/dist/esm/activities/chat/middleware/types.d.ts +8 -7
  12. package/dist/esm/activities/chat/stream/processor.d.ts +8 -8
  13. package/dist/esm/activities/chat/stream/processor.js +29 -21
  14. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  15. package/dist/esm/activities/chat/stream/strategies.d.ts +3 -3
  16. package/dist/esm/activities/chat/stream/strategies.js +4 -4
  17. package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
  18. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +5 -0
  19. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/schema-converter.d.ts +1 -1
  21. package/dist/esm/activities/chat/tools/schema-converter.js +36 -35
  22. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  23. package/dist/esm/activities/chat/tools/tool-calls.d.ts +2 -2
  24. package/dist/esm/activities/chat/tools/tool-calls.js +17 -9
  25. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  26. package/dist/esm/activities/chat/tools/tool-definition.js +1 -1
  27. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  28. package/dist/esm/activities/generateAudio/adapter.js +3 -1
  29. package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
  30. package/dist/esm/activities/generateAudio/index.d.ts +5 -1
  31. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  32. package/dist/esm/activities/generateImage/adapter.js +3 -1
  33. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  34. package/dist/esm/activities/generateImage/index.js +5 -0
  35. package/dist/esm/activities/generateImage/index.js.map +1 -1
  36. package/dist/esm/activities/generateSpeech/adapter.js +3 -1
  37. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  38. package/dist/esm/activities/generateTranscription/adapter.js +3 -1
  39. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
  40. package/dist/esm/activities/generateVideo/adapter.js +3 -1
  41. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  42. package/dist/esm/activities/stream-generation-result.js +6 -2
  43. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  44. package/dist/esm/activities/summarize/adapter.js +3 -1
  45. package/dist/esm/activities/summarize/adapter.js.map +1 -1
  46. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +1 -1
  47. package/dist/esm/activities/summarize/chat-stream-summarize.js +5 -0
  48. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  49. package/dist/esm/activities/summarize/index.js.map +1 -1
  50. package/dist/esm/index.d.ts +3 -0
  51. package/dist/esm/index.js +4 -0
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/logger/internal-logger.js +2 -0
  54. package/dist/esm/logger/internal-logger.js.map +1 -1
  55. package/dist/esm/middlewares/content-guard.js +5 -4
  56. package/dist/esm/middlewares/content-guard.js.map +1 -1
  57. package/dist/esm/middlewares/otel.js +38 -16
  58. package/dist/esm/middlewares/otel.js.map +1 -1
  59. package/dist/esm/realtime/index.d.ts +1 -1
  60. package/dist/esm/realtime/index.js.map +1 -1
  61. package/dist/esm/system-prompts.d.ts +66 -0
  62. package/dist/esm/system-prompts.js +23 -0
  63. package/dist/esm/system-prompts.js.map +1 -0
  64. package/dist/esm/tools/provider-tool.d.ts +9 -0
  65. package/dist/esm/tools/provider-tool.js +7 -0
  66. package/dist/esm/tools/provider-tool.js.map +1 -0
  67. package/dist/esm/types.d.ts +22 -7
  68. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  69. package/dist/esm/utilities/chat-params.js.map +1 -1
  70. package/package.json +2 -2
  71. package/src/activities/chat/adapter.ts +11 -2
  72. package/src/activities/chat/index.ts +46 -27
  73. package/src/activities/chat/messages.ts +2 -1
  74. package/src/activities/chat/middleware/types.ts +14 -11
  75. package/src/activities/chat/stream/processor.ts +19 -19
  76. package/src/activities/chat/stream/strategies.ts +3 -3
  77. package/src/activities/chat/tools/schema-converter.ts +77 -52
  78. package/src/activities/chat/tools/tool-calls.ts +18 -11
  79. package/src/activities/chat/tools/tool-definition.ts +6 -1
  80. package/src/activities/generateAudio/index.ts +5 -4
  81. package/src/activities/generateImage/index.ts +5 -0
  82. package/src/activities/stream-generation-result.ts +11 -2
  83. package/src/activities/summarize/chat-stream-summarize.ts +5 -2
  84. package/src/activities/summarize/index.ts +2 -2
  85. package/src/index.ts +5 -0
  86. package/src/middlewares/content-guard.ts +11 -3
  87. package/src/middlewares/otel.ts +47 -16
  88. package/src/realtime/index.ts +1 -1
  89. package/src/system-prompts.ts +98 -0
  90. package/src/tools/provider-tool.ts +14 -0
  91. package/src/types.ts +28 -9
  92. package/src/utilities/ag-ui-wire.ts +2 -2
  93. 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 unknown 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 unknown 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,QAAuC,QAAQ;AACjD,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,QAAuC,QAAQ;AACjD,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;"}
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.19.1",
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.4"
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?: Array<
98
- | UIMessage
99
- | ModelMessage
100
- | ConstrainedModelMessage<{
101
- inputModalities: TAdapter['~types']['inputModalities']
102
- messageMetadataByModality: TAdapter['~types']['messageMetadataByModality']
103
- }>
104
- >
105
- /** System prompts to prepend to the conversation */
106
- systemPrompts?: TextOptions['systemPrompts']
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?: Array<
118
- | (Tool & { readonly '~toolKind'?: never })
119
- | ProviderTool<string, TAdapter['~types']['toolCapabilities'][number]>
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<string>
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<string>
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
- const allMiddleware = [
395
- devtoolsMiddleware(),
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
- finishEvt,
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<string>
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<string>
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
- promptTokens: number
261
- completionTokens: number
262
- totalTokens: number
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; convert to array and search from the end
649
- const ids = Array.from(this.activeMessageIds)
650
- for (let i = ids.length - 1; i >= 0; i--) {
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
- if (!this.messageStates.has(messageId)) {
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 unknown as Array<UIMessage>
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))