@tanstack/ai 0.15.0 → 0.17.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/esm/activities/chat/adapter.d.ts +20 -3
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +16 -6
- package/dist/esm/activities/chat/index.js +235 -9
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +4 -2
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +1 -0
- package/dist/esm/activities/chat/stream/message-updaters.js +3 -1
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.js +12 -4
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +5 -0
- package/dist/esm/activities/chat/tools/tool-calls.js +1 -3
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/error-payload.d.ts +0 -8
- package/dist/esm/activities/error-payload.js +20 -2
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.d.ts +2 -2
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.d.ts +2 -2
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/index.d.ts +1 -0
- package/dist/esm/activities/index.js +2 -0
- package/dist/esm/activities/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +0 -2
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.d.ts +4 -4
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +45 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +148 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -0
- package/dist/esm/activities/summarize/index.d.ts +1 -0
- package/dist/esm/activities/summarize/index.js +4 -2
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/types.d.ts +109 -10
- package/package.json +2 -2
- package/skills/ai-core/structured-outputs/SKILL.md +92 -1
- package/src/activities/chat/adapter.ts +25 -2
- package/src/activities/chat/index.ts +368 -26
- package/src/activities/chat/messages.ts +6 -0
- package/src/activities/chat/stream/message-updaters.ts +8 -0
- package/src/activities/chat/stream/processor.ts +12 -0
- package/src/activities/chat/stream/types.ts +5 -0
- package/src/activities/chat/tools/tool-calls.ts +1 -3
- package/src/activities/error-payload.ts +31 -2
- package/src/activities/generateImage/adapter.ts +8 -2
- package/src/activities/generateVideo/adapter.ts +8 -2
- package/src/activities/index.ts +5 -0
- package/src/activities/stream-generation-result.ts +4 -6
- package/src/activities/summarize/adapter.ts +8 -4
- package/src/activities/summarize/chat-stream-summarize.ts +238 -0
- package/src/activities/summarize/index.ts +12 -9
- package/src/types.ts +122 -10
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-stream-summarize.js","sources":["../../../../src/activities/summarize/chat-stream-summarize.ts"],"sourcesContent":["import { EventType } from '@ag-ui/core'\nimport { toRunErrorPayload } from '../error-payload'\nimport { BaseSummarizeAdapter } from './adapter'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n TextOptions,\n} from '../../types'\n\n/**\n * Minimal contract for a text adapter that supports `chatStream`. Lets\n * `ChatStreamSummarizeAdapter` work with any text adapter without coupling\n * to a specific implementation.\n *\n * The provider-options shape is intentionally `any` here — the wrapper only\n * forwards `modelOptions` straight through, so a text adapter with a richer\n * per-model options type (e.g. `ResolveProviderOptions<TModel>`) is still\n * acceptable. Summarize-level type safety is enforced via\n * `SummarizationOptions<TProviderOptions>` on the wrapper itself.\n */\nexport interface ChatStreamCapable {\n chatStream: (options: TextOptions<any>) => AsyncIterable<StreamChunk>\n}\n\n/**\n * Extract the per-model `modelOptions` type a text adapter accepts. Used by\n * provider summarize factories so their `modelOptions` IntelliSense matches\n * what the underlying text adapter actually understands.\n */\nexport type InferTextProviderOptions<TAdapter> = TAdapter extends {\n '~types': { providerOptions: infer P }\n}\n ? P extends object\n ? P\n : object\n : object\n\n/**\n * Summarize adapter that wraps any `ChatStreamCapable` text adapter and\n * prompts it for summarization. Not tied to any wire format.\n */\nexport class ChatStreamSummarizeAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, unknown>,\n> extends BaseSummarizeAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n private textAdapter: ChatStreamCapable\n\n constructor(\n textAdapter: ChatStreamCapable,\n model: TModel,\n name: string = 'chat-stream-summarize',\n ) {\n super({}, model)\n this.name = name\n this.textAdapter = textAdapter\n }\n\n async summarize(\n options: SummarizationOptions<TProviderOptions>,\n ): Promise<SummarizationResult> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n let summary = ''\n const id = this.generateId()\n let model = options.model\n let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\n\n options.logger.request(\n `activity=summarize provider=${this.name} model=${options.model} text-length=${options.text.length} maxLength=${options.maxLength ?? 'unset'}`,\n { provider: this.name, model: options.model },\n )\n\n try {\n for await (const chunk of this.textAdapter.chatStream(\n this.buildTextOptions(options, systemPrompt),\n )) {\n if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n if (chunk.content) {\n summary = chunk.content\n } else if (chunk.delta) {\n // Append delta only when present — a content-less chunk with no\n // delta would otherwise concat literal `'undefined'`.\n summary += chunk.delta\n }\n model = chunk.model || model\n }\n if (chunk.type === 'RUN_FINISHED') {\n if (chunk.usage) {\n usage = chunk.usage\n }\n }\n // Surface failures: the underlying chatStream emits RUN_ERROR instead\n // of throwing, so without this branch summarize() would return an\n // empty summary and pretend a failed run succeeded.\n if (chunk.type === 'RUN_ERROR') {\n const message =\n (chunk.error && typeof chunk.error.message === 'string'\n ? chunk.error.message\n : null) ?? 'Summarization failed'\n const code =\n chunk.error && typeof chunk.error.code === 'string'\n ? chunk.error.code\n : undefined\n const err = new Error(message)\n if (code) {\n ;(err as Error & { code?: string }).code = code\n }\n throw err\n }\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.summarize fatal`, {\n error: toRunErrorPayload(error, `${this.name}.summarize failed`),\n source: `${this.name}.summarize`,\n })\n throw error\n }\n\n return { id, model, summary, usage }\n }\n\n async *summarizeStream(\n options: SummarizationOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n options.logger.request(\n `activity=summarizeStream provider=${this.name} model=${options.model} text-length=${options.text.length} maxLength=${options.maxLength ?? 'unset'}`,\n { provider: this.name, model: options.model },\n )\n\n const id = this.generateId()\n let summary = ''\n let model = options.model\n let usage: SummarizationResult['usage'] = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n }\n\n try {\n for await (const chunk of this.textAdapter.chatStream(\n this.buildTextOptions(options, systemPrompt),\n )) {\n // Accumulate the same way `summarize()` does so consumers see deltas\n // AND the terminal `generation:result` event below carries the same\n // final summary that non-streaming returns.\n if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n if (chunk.content) {\n summary = chunk.content\n } else if (chunk.delta) {\n summary += chunk.delta\n }\n if (chunk.model) model = chunk.model\n }\n\n // Emit the GenerationClient-shaped result event just before the\n // terminal RUN_FINISHED so subscribers (useSummarize) populate\n // `result` before flipping `status` to success.\n if (chunk.type === 'RUN_FINISHED') {\n if (chunk.usage) usage = chunk.usage\n if (chunk.model) model = chunk.model\n yield {\n type: EventType.CUSTOM,\n name: 'generation:result',\n value: { id, model, summary, usage } satisfies SummarizationResult,\n model,\n timestamp: Date.now(),\n }\n }\n\n yield chunk\n }\n } catch (error: unknown) {\n options.logger.errors(`${this.name}.summarizeStream fatal`, {\n error: toRunErrorPayload(error, `${this.name}.summarizeStream failed`),\n source: `${this.name}.summarizeStream`,\n })\n throw error\n }\n }\n\n /**\n * Build the TextOptions passed to the underlying chatStream. Provider\n * `modelOptions` from the summarize call are forwarded as-is so knobs like\n * Anthropic cache headers, Gemini safety settings, or Ollama tuning params\n * still reach the wire layer.\n */\n protected buildTextOptions(\n options: SummarizationOptions<TProviderOptions>,\n systemPrompt: string,\n ): TextOptions<TProviderOptions> {\n return {\n model: options.model,\n messages: [{ role: 'user', content: options.text }],\n systemPrompts: [systemPrompt],\n maxTokens: options.maxLength,\n temperature: 0.3,\n modelOptions: options.modelOptions,\n logger: options.logger,\n }\n }\n\n protected buildSummarizationPrompt(\n options: SummarizationOptions<TProviderOptions>,\n ): string {\n let prompt = 'You are a professional summarizer. '\n\n switch (options.style) {\n case 'bullet-points':\n prompt += 'Provide a summary in bullet point format. '\n break\n case 'paragraph':\n prompt += 'Provide a summary in paragraph format. '\n break\n case 'concise':\n prompt += 'Provide a very concise summary in 1-2 sentences. '\n break\n default:\n prompt += 'Provide a clear and concise summary. '\n }\n\n if (options.focus && options.focus.length > 0) {\n prompt += `Focus on the following aspects: ${options.focus.join(', ')}. `\n }\n\n if (options.maxLength) {\n prompt += `Keep the summary under ${options.maxLength} tokens. `\n }\n\n return prompt\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -105,3 +105,4 @@ export declare function summarize<TAdapter extends SummarizeAdapter<string, obje
|
|
|
105
105
|
export declare function createSummarizeOptions<TAdapter extends SummarizeAdapter<string, object>, TStream extends boolean = false>(options: SummarizeActivityOptions<TAdapter, TStream>): SummarizeActivityOptions<TAdapter, TStream>;
|
|
106
106
|
export type { SummarizeAdapter, SummarizeAdapterConfig, AnySummarizeAdapter, } from './adapter.js';
|
|
107
107
|
export { BaseSummarizeAdapter } from './adapter.js';
|
|
108
|
+
export { ChatStreamSummarizeAdapter, type ChatStreamCapable, type InferTextProviderOptions, } from './chat-stream-summarize.js';
|
|
@@ -17,7 +17,7 @@ function summarize(options) {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
async function runSummarize(options) {
|
|
20
|
-
const { adapter, text, maxLength, style, focus } = options;
|
|
20
|
+
const { adapter, text, maxLength, style, focus, modelOptions } = options;
|
|
21
21
|
const model = adapter.model;
|
|
22
22
|
const requestId = createId("summarize");
|
|
23
23
|
const inputLength = text.length;
|
|
@@ -41,6 +41,7 @@ async function runSummarize(options) {
|
|
|
41
41
|
maxLength,
|
|
42
42
|
style,
|
|
43
43
|
focus,
|
|
44
|
+
modelOptions,
|
|
44
45
|
logger
|
|
45
46
|
};
|
|
46
47
|
try {
|
|
@@ -70,7 +71,7 @@ async function runSummarize(options) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
async function* runStreamingSummarize(options) {
|
|
73
|
-
const { adapter, text, maxLength, style, focus } = options;
|
|
74
|
+
const { adapter, text, maxLength, style, focus, modelOptions } = options;
|
|
74
75
|
const model = adapter.model;
|
|
75
76
|
const logger = resolveDebugOption(options.debug);
|
|
76
77
|
logger.request(`activity=summarize provider=${adapter.name}`, {
|
|
@@ -84,6 +85,7 @@ async function* runStreamingSummarize(options) {
|
|
|
84
85
|
maxLength,
|
|
85
86
|
style,
|
|
86
87
|
focus,
|
|
88
|
+
modelOptions,
|
|
87
89
|
logger
|
|
88
90
|
};
|
|
89
91
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/activities/summarize/index.ts"],"sourcesContent":["/**\n * Summarize Activity\n *\n * Generates summaries from text input.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { SummarizeAdapter } from './adapter'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'summarize' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from a SummarizeAdapter via ~types */\nexport type SummarizeProviderOptions<TAdapter> =\n TAdapter extends SummarizeAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the summarize activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The summarize adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface SummarizeActivityOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The summarize adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to summarize */\n text: string\n /** Maximum length of the summary (in words or characters, provider-dependent) */\n maxLength?: number\n /** Style of summary to generate */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics or aspects to focus on in the summary */\n focus?: Array<string>\n /** Provider-specific options */\n modelOptions?: SummarizeProviderOptions<TAdapter>\n /**\n * Whether to stream the summarization result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming output.\n * When false or not provided, returns a Promise<SummarizationResult>.\n *\n * @default false\n */\n stream?: TStream\n /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the summarize activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<SummarizationResult>\n */\nexport type SummarizeActivityResult<TStream extends boolean> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<SummarizationResult>\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Summarize activity - generates summaries from text.\n *\n * Supports both streaming and non-streaming modes.\n *\n * @example Basic summarization\n * ```ts\n * import { summarize } from '@tanstack/ai'\n * import { openaiSummarize } from '@tanstack/ai-openai'\n *\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...'\n * })\n *\n * console.log(result.summary)\n * ```\n *\n * @example Summarization with style\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * style: 'bullet-points',\n * maxLength: 100\n * })\n * ```\n *\n * @example Focused summarization\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long technical document...',\n * focus: ['key findings', 'methodology']\n * })\n * ```\n *\n * @example Streaming summarization\n * ```ts\n * for await (const chunk of summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * stream: true\n * })) {\n * if (chunk.type === 'content') {\n * process.stdout.write(chunk.delta)\n * }\n * }\n * ```\n */\nexport function summarize<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityResult<TStream> {\n const { stream } = options\n\n if (stream) {\n return runStreamingSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n true\n >,\n ) as SummarizeActivityResult<TStream>\n }\n\n return runSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n false\n >,\n ) as SummarizeActivityResult<TStream>\n}\n\n/**\n * Run non-streaming summarization\n */\nasync function runSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, false>,\n): Promise<SummarizationResult> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n const requestId = createId('summarize')\n const inputLength = text.length\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(options.debug)\n\n aiEventClient.emit('summarize:request:started', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n timestamp: startTime,\n })\n\n logger.request(`activity=summarize provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n inputLength,\n })\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n logger,\n }\n\n try {\n const result = await adapter.summarize(summarizeOptions)\n\n const duration = Date.now() - startTime\n const outputLength = result.summary.length\n\n aiEventClient.emit('summarize:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n outputLength,\n duration,\n timestamp: Date.now(),\n })\n\n logger.output(`activity=summarize length=${outputLength}`, {\n hasSummary: !!result.summary,\n outputLength,\n })\n\n return result\n } catch (error) {\n logger.errors('summarize activity failed', {\n error,\n source: 'summarize',\n })\n throw error\n }\n}\n\n/**\n * Run streaming summarization\n * Uses the adapter's native streaming if available, otherwise falls back\n * to non-streaming and yields the result as a single chunk.\n */\nasync function* runStreamingSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,\n): AsyncIterable<StreamChunk> {\n const { adapter, text, maxLength, style, focus } = options\n const model = adapter.model\n const logger: InternalLogger = resolveDebugOption(options.debug)\n\n logger.request(`activity=summarize provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n stream: true,\n })\n\n const summarizeOptions: SummarizationOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n logger,\n }\n\n try {\n // Use real streaming if the adapter supports it\n if (adapter.summarizeStream) {\n yield* adapter.summarizeStream(summarizeOptions)\n return\n }\n\n // Fall back to non-streaming — wrap result with streamGenerationResult\n yield* streamGenerationResult(() => adapter.summarize(summarizeOptions))\n } catch (error) {\n logger.errors('summarize activity failed', {\n error,\n source: 'summarize',\n })\n throw error\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the summarize() function without executing.\n */\nexport function createSummarizeOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n SummarizeAdapter,\n SummarizeAdapterConfig,\n AnySummarizeAdapter,\n} from './adapter'\nexport { BaseSummarizeAdapter } from './adapter'\n"],"names":[],"mappings":";;;AAwBO,MAAM,OAAO;AAyEpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAwDO,SAAS,UAId,SACkC;AAClC,QAAM,EAAE,WAAW;AAEnB,MAAI,QAAQ;AACV,WAAO;AAAA,MACL;AAAA,IAAA;AAAA,EAKJ;AAEA,SAAO;AAAA,IACL;AAAA,EAAA;AAKJ;AAKA,eAAe,aACb,SAC8B;AAC9B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,UAAU;AACnD,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,WAAW;AACtC,QAAM,cAAc,KAAK;AACzB,QAAM,YAAY,KAAK,IAAA;AACvB,QAAM,SAAyB,mBAAmB,QAAQ,KAAK;AAE/D,gBAAc,KAAK,6BAA6B;AAAA,IAC9C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EAAA,CACZ;AAED,SAAO,QAAQ,+BAA+B,QAAQ,IAAI,IAAI;AAAA,IAC5D,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,EAAA,CACD;AAED,QAAM,mBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,UAAU,gBAAgB;AAEvD,UAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,UAAM,eAAe,OAAO,QAAQ;AAEpC,kBAAc,KAAK,+BAA+B;AAAA,MAChD;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,WAAO,OAAO,6BAA6B,YAAY,IAAI;AAAA,MACzD,YAAY,CAAC,CAAC,OAAO;AAAA,MACrB;AAAA,IAAA,CACD;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,OAAO,6BAA6B;AAAA,MACzC;AAAA,MACA,QAAQ;AAAA,IAAA,CACT;AACD,UAAM;AAAA,EACR;AACF;AAOA,gBAAgB,sBACd,SAC4B;AAC5B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,UAAU;AACnD,QAAM,QAAQ,QAAQ;AACtB,QAAM,SAAyB,mBAAmB,QAAQ,KAAK;AAE/D,SAAO,QAAQ,+BAA+B,QAAQ,IAAI,IAAI;AAAA,IAC5D,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,QAAQ;AAAA,EAAA,CACT;AAED,QAAM,mBAAyC;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,MAAI;AAEF,QAAI,QAAQ,iBAAiB;AAC3B,aAAO,QAAQ,gBAAgB,gBAAgB;AAC/C;AAAA,IACF;AAGA,WAAO,uBAAuB,MAAM,QAAQ,UAAU,gBAAgB,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,WAAO,OAAO,6BAA6B;AAAA,MACzC;AAAA,MACA,QAAQ;AAAA,IAAA,CACT;AACD,UAAM;AAAA,EACR;AACF;AASO,SAAS,uBAId,SAC6C;AAC7C,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/activities/summarize/index.ts"],"sourcesContent":["/**\n * Summarize Activity\n *\n * Generates summaries from text input.\n * This is a self-contained module with implementation, types, and JSDoc.\n */\n\nimport { aiEventClient } from '@tanstack/ai-event-client'\nimport { streamGenerationResult } from '../stream-generation-result.js'\nimport { resolveDebugOption } from '../../logger/resolve'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { SummarizeAdapter } from './adapter'\nimport type { StreamChunk, SummarizationResult } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'summarize' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/** Extract provider options from a SummarizeAdapter via ~types */\nexport type SummarizeProviderOptions<TAdapter> =\n TAdapter extends SummarizeAdapter<any, any>\n ? TAdapter['~types']['providerOptions']\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the summarize activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The summarize adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface SummarizeActivityOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n> {\n /** The summarize adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** The text to summarize */\n text: string\n /** Maximum length of the summary (in words or characters, provider-dependent) */\n maxLength?: number\n /** Style of summary to generate */\n style?: 'bullet-points' | 'paragraph' | 'concise'\n /** Topics or aspects to focus on in the summary */\n focus?: Array<string>\n /** Provider-specific options */\n modelOptions?: SummarizeProviderOptions<TAdapter>\n /**\n * Whether to stream the summarization result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming output.\n * When false or not provided, returns a Promise<SummarizationResult>.\n *\n * @default false\n */\n stream?: TStream\n /**\n * Enable debug logging. Pass `true` to enable all categories, `false` to\n * silence everything including errors, or a `DebugConfig` object for granular\n * control and/or a custom `Logger`.\n */\n debug?: DebugOption\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the summarize activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<SummarizationResult>\n */\nexport type SummarizeActivityResult<TStream extends boolean> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<SummarizationResult>\n\n// ===========================\n// Helper Functions\n// ===========================\n\nfunction createId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ===========================\n// Activity Implementation\n// ===========================\n\n/**\n * Summarize activity - generates summaries from text.\n *\n * Supports both streaming and non-streaming modes.\n *\n * @example Basic summarization\n * ```ts\n * import { summarize } from '@tanstack/ai'\n * import { openaiSummarize } from '@tanstack/ai-openai'\n *\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...'\n * })\n *\n * console.log(result.summary)\n * ```\n *\n * @example Summarization with style\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * style: 'bullet-points',\n * maxLength: 100\n * })\n * ```\n *\n * @example Focused summarization\n * ```ts\n * const result = await summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long technical document...',\n * focus: ['key findings', 'methodology']\n * })\n * ```\n *\n * @example Streaming summarization\n * ```ts\n * for await (const chunk of summarize({\n * adapter: openaiSummarize('gpt-4o-mini'),\n * text: 'Long article text here...',\n * stream: true\n * })) {\n * if (chunk.type === 'content') {\n * process.stdout.write(chunk.delta)\n * }\n * }\n * ```\n */\nexport function summarize<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityResult<TStream> {\n const { stream } = options\n\n if (stream) {\n return runStreamingSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n true\n >,\n ) as SummarizeActivityResult<TStream>\n }\n\n return runSummarize(\n options as unknown as SummarizeActivityOptions<\n SummarizeAdapter<string, object>,\n false\n >,\n ) as SummarizeActivityResult<TStream>\n}\n\n/**\n * Run non-streaming summarization\n */\nasync function runSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, false>,\n): Promise<SummarizationResult> {\n const { adapter, text, maxLength, style, focus, modelOptions } = options\n const model = adapter.model\n const requestId = createId('summarize')\n const inputLength = text.length\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(options.debug)\n\n aiEventClient.emit('summarize:request:started', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n timestamp: startTime,\n })\n\n logger.request(`activity=summarize provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n inputLength,\n })\n\n const summarizeOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n modelOptions,\n logger,\n }\n\n try {\n const result = await adapter.summarize(summarizeOptions)\n\n const duration = Date.now() - startTime\n const outputLength = result.summary.length\n\n aiEventClient.emit('summarize:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n inputLength,\n outputLength,\n duration,\n timestamp: Date.now(),\n })\n\n logger.output(`activity=summarize length=${outputLength}`, {\n hasSummary: !!result.summary,\n outputLength,\n })\n\n return result\n } catch (error) {\n logger.errors('summarize activity failed', {\n error,\n source: 'summarize',\n })\n throw error\n }\n}\n\n/**\n * Run streaming summarization\n * Uses the adapter's native streaming if available, otherwise falls back\n * to non-streaming and yields the result as a single chunk.\n */\nasync function* runStreamingSummarize(\n options: SummarizeActivityOptions<SummarizeAdapter<string, object>, true>,\n): AsyncIterable<StreamChunk> {\n const { adapter, text, maxLength, style, focus, modelOptions } = options\n const model = adapter.model\n const logger: InternalLogger = resolveDebugOption(options.debug)\n\n logger.request(`activity=summarize provider=${adapter.name}`, {\n provider: adapter.name,\n model,\n stream: true,\n })\n\n const summarizeOptions = {\n model,\n text,\n maxLength,\n style,\n focus,\n modelOptions,\n logger,\n }\n\n try {\n // Use real streaming if the adapter supports it\n if (adapter.summarizeStream) {\n yield* adapter.summarizeStream(summarizeOptions)\n return\n }\n\n // Fall back to non-streaming — wrap result with streamGenerationResult\n yield* streamGenerationResult(() => adapter.summarize(summarizeOptions))\n } catch (error) {\n logger.errors('summarize activity failed', {\n error,\n source: 'summarize',\n })\n throw error\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the summarize() function without executing.\n */\nexport function createSummarizeOptions<\n TAdapter extends SummarizeAdapter<string, object>,\n TStream extends boolean = false,\n>(\n options: SummarizeActivityOptions<TAdapter, TStream>,\n): SummarizeActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n SummarizeAdapter,\n SummarizeAdapterConfig,\n AnySummarizeAdapter,\n} from './adapter'\nexport { BaseSummarizeAdapter } from './adapter'\nexport {\n ChatStreamSummarizeAdapter,\n type ChatStreamCapable,\n type InferTextProviderOptions,\n} from './chat-stream-summarize'\n"],"names":[],"mappings":";;;AAoBO,MAAM,OAAO;AAyEpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAwDO,SAAS,UAId,SACkC;AAClC,QAAM,EAAE,WAAW;AAEnB,MAAI,QAAQ;AACV,WAAO;AAAA,MACL;AAAA,IAAA;AAAA,EAKJ;AAEA,SAAO;AAAA,IACL;AAAA,EAAA;AAKJ;AAKA,eAAe,aACb,SAC8B;AAC9B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,OAAO,iBAAiB;AACjE,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,WAAW;AACtC,QAAM,cAAc,KAAK;AACzB,QAAM,YAAY,KAAK,IAAA;AACvB,QAAM,SAAyB,mBAAmB,QAAQ,KAAK;AAE/D,gBAAc,KAAK,6BAA6B;AAAA,IAC9C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EAAA,CACZ;AAED,SAAO,QAAQ,+BAA+B,QAAQ,IAAI,IAAI;AAAA,IAC5D,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,EAAA,CACD;AAED,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,UAAU,gBAAgB;AAEvD,UAAM,WAAW,KAAK,IAAA,IAAQ;AAC9B,UAAM,eAAe,OAAO,QAAQ;AAEpC,kBAAc,KAAK,+BAA+B;AAAA,MAChD;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,WAAO,OAAO,6BAA6B,YAAY,IAAI;AAAA,MACzD,YAAY,CAAC,CAAC,OAAO;AAAA,MACrB;AAAA,IAAA,CACD;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,OAAO,6BAA6B;AAAA,MACzC;AAAA,MACA,QAAQ;AAAA,IAAA,CACT;AACD,UAAM;AAAA,EACR;AACF;AAOA,gBAAgB,sBACd,SAC4B;AAC5B,QAAM,EAAE,SAAS,MAAM,WAAW,OAAO,OAAO,iBAAiB;AACjE,QAAM,QAAQ,QAAQ;AACtB,QAAM,SAAyB,mBAAmB,QAAQ,KAAK;AAE/D,SAAO,QAAQ,+BAA+B,QAAQ,IAAI,IAAI;AAAA,IAC5D,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,QAAQ;AAAA,EAAA,CACT;AAED,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,MAAI;AAEF,QAAI,QAAQ,iBAAiB;AAC3B,aAAO,QAAQ,gBAAgB,gBAAgB;AAC/C;AAAA,IACF;AAGA,WAAO,uBAAuB,MAAM,QAAQ,UAAU,gBAAgB,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,WAAO,OAAO,6BAA6B;AAAA,MACzC;AAAA,MACA,QAAQ;AAAA,IAAA,CACT;AACD,UAAM;AAAA,EACR;AACF;AASO,SAAS,uBAId,SAC6C;AAC7C,SAAO;AACT;"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -70,15 +70,17 @@ export type SchemaInput = StandardJSONSchemaV1<any, any> | JSONSchema;
|
|
|
70
70
|
* For plain JSONSchema, returns `any` since we can't infer types from JSON Schema at compile time.
|
|
71
71
|
*/
|
|
72
72
|
export type InferSchemaType<T> = T extends StandardJSONSchemaV1<infer TInput, unknown> ? TInput : unknown;
|
|
73
|
-
export interface ToolCall {
|
|
73
|
+
export interface ToolCall<TMetadata = unknown> {
|
|
74
74
|
id: string;
|
|
75
75
|
type: 'function';
|
|
76
76
|
function: {
|
|
77
77
|
name: string;
|
|
78
78
|
arguments: string;
|
|
79
79
|
};
|
|
80
|
-
/** Provider-specific metadata to carry through the tool call lifecycle
|
|
81
|
-
|
|
80
|
+
/** Provider-specific metadata to carry through the tool call lifecycle.
|
|
81
|
+
* Typed per-adapter via `TToolCallMetadata`. For example,
|
|
82
|
+
* `@tanstack/ai-gemini` sets this to `{ thoughtSignature?: string }`. */
|
|
83
|
+
metadata?: TMetadata;
|
|
82
84
|
}
|
|
83
85
|
/**
|
|
84
86
|
* Supported input modality types for multimodal content.
|
|
@@ -221,7 +223,7 @@ export interface TextPart<TMetadata = unknown> {
|
|
|
221
223
|
content: string;
|
|
222
224
|
metadata?: TMetadata;
|
|
223
225
|
}
|
|
224
|
-
export interface ToolCallPart {
|
|
226
|
+
export interface ToolCallPart<TMetadata = unknown> {
|
|
225
227
|
type: 'tool-call';
|
|
226
228
|
id: string;
|
|
227
229
|
name: string;
|
|
@@ -235,6 +237,9 @@ export interface ToolCallPart {
|
|
|
235
237
|
};
|
|
236
238
|
/** Tool execution output (for client tools or after approval) */
|
|
237
239
|
output?: any;
|
|
240
|
+
/** Provider-specific metadata that round-trips with the tool call.
|
|
241
|
+
* Typed per-adapter via `TToolCallMetadata`. */
|
|
242
|
+
metadata?: TMetadata;
|
|
238
243
|
}
|
|
239
244
|
export interface ToolResultPart {
|
|
240
245
|
type: 'tool-result';
|
|
@@ -740,7 +745,7 @@ export interface TextMessageEndEvent extends AGUITextMessageEndEvent {
|
|
|
740
745
|
* Emitted when a tool call starts.
|
|
741
746
|
*
|
|
742
747
|
* @ag-ui/core provides: `toolCallId`, `toolCallName`, `parentMessageId?`
|
|
743
|
-
* TanStack AI adds: `model?`, `toolName` (deprecated alias), `index?`, `
|
|
748
|
+
* TanStack AI adds: `model?`, `toolName` (deprecated alias), `index?`, `metadata?`
|
|
744
749
|
*/
|
|
745
750
|
export interface ToolCallStartEvent extends AGUIToolCallStartEvent {
|
|
746
751
|
/** Model identifier for multi-model support */
|
|
@@ -752,8 +757,11 @@ export interface ToolCallStartEvent extends AGUIToolCallStartEvent {
|
|
|
752
757
|
toolName: string;
|
|
753
758
|
/** Index for parallel tool calls */
|
|
754
759
|
index?: number;
|
|
755
|
-
/** Provider-specific metadata to carry into the ToolCall
|
|
756
|
-
|
|
760
|
+
/** Provider-specific metadata to carry into the ToolCall.
|
|
761
|
+
* Untyped at the event layer because events flow through a discriminated
|
|
762
|
+
* union that does not survive generics; adapters cast it to their typed
|
|
763
|
+
* `TToolCallMetadata` shape when emitting. */
|
|
764
|
+
metadata?: Record<string, unknown>;
|
|
757
765
|
}
|
|
758
766
|
/**
|
|
759
767
|
* Emitted when tool call arguments are streaming.
|
|
@@ -887,6 +895,95 @@ export interface CustomEvent extends AGUICustomEvent {
|
|
|
887
895
|
/** Model identifier for multi-model support */
|
|
888
896
|
model?: string;
|
|
889
897
|
}
|
|
898
|
+
/**
|
|
899
|
+
* Final event of a streaming structured-output run. Carries the validated
|
|
900
|
+
* `object` (typed as `T` after the orchestrator runs Standard Schema parsing),
|
|
901
|
+
* the `raw` JSON text that produced it, and — for thinking/reasoning models —
|
|
902
|
+
* the accumulated reasoning text. Adapters emit this with `T = unknown`; the
|
|
903
|
+
* chat orchestrator narrows to the schema's inferred type after validation.
|
|
904
|
+
*
|
|
905
|
+
* `reasoning` is `undefined` when the model produced none (most non-thinking
|
|
906
|
+
* models) and when the underlying adapter doesn't expose reasoning streams.
|
|
907
|
+
*
|
|
908
|
+
* `name` is a string literal so consumers can narrow directly:
|
|
909
|
+
*
|
|
910
|
+
* ```ts
|
|
911
|
+
* if (chunk.type === 'CUSTOM' && chunk.name === 'structured-output.complete') {
|
|
912
|
+
* chunk.value.object // typed as T
|
|
913
|
+
* }
|
|
914
|
+
* ```
|
|
915
|
+
*/
|
|
916
|
+
export interface StructuredOutputCompleteEvent<T = unknown> extends Omit<CustomEvent, 'name' | 'value'> {
|
|
917
|
+
name: 'structured-output.complete';
|
|
918
|
+
value: {
|
|
919
|
+
object: T;
|
|
920
|
+
raw: string;
|
|
921
|
+
reasoning?: string;
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Emitted when a server tool requires approval before execution. The agent
|
|
926
|
+
* loop yields this and pauses — `structured-output.complete` will not fire
|
|
927
|
+
* for that run. The shape is fixed by the orchestrator's tool-approval flow
|
|
928
|
+
* (see `buildApprovalChunks` in `activities/chat/index.ts`).
|
|
929
|
+
*/
|
|
930
|
+
export interface ApprovalRequestedEvent extends Omit<CustomEvent, 'name' | 'value'> {
|
|
931
|
+
name: 'approval-requested';
|
|
932
|
+
value: {
|
|
933
|
+
toolCallId: string;
|
|
934
|
+
toolName: string;
|
|
935
|
+
input: unknown;
|
|
936
|
+
approval: {
|
|
937
|
+
id: string;
|
|
938
|
+
needsApproval: true;
|
|
939
|
+
};
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Emitted when a client tool is invoked. The agent loop yields this and
|
|
944
|
+
* pauses to let the caller run the tool client-side — `structured-output.complete`
|
|
945
|
+
* will not fire for that run. Shape fixed by `buildClientToolChunks` in
|
|
946
|
+
* `activities/chat/index.ts`.
|
|
947
|
+
*/
|
|
948
|
+
export interface ToolInputAvailableEvent extends Omit<CustomEvent, 'name' | 'value'> {
|
|
949
|
+
name: 'tool-input-available';
|
|
950
|
+
value: {
|
|
951
|
+
toolCallId: string;
|
|
952
|
+
toolName: string;
|
|
953
|
+
input: unknown;
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Public type for streams returned by `chat({ outputSchema, stream: true })`.
|
|
958
|
+
*
|
|
959
|
+
* Yields all standard `StreamChunk` lifecycle events plus the three tagged
|
|
960
|
+
* `CUSTOM` events the orchestrator can emit through this path:
|
|
961
|
+
* - `structured-output.complete` — terminal event with typed `value.object: T`
|
|
962
|
+
* - `approval-requested` — server tool needs approval (pauses the run)
|
|
963
|
+
* - `tool-input-available` — client tool invocation (pauses the run)
|
|
964
|
+
*
|
|
965
|
+
* Each variant has a literal `name`, so a single discriminated narrow gives
|
|
966
|
+
* you a typed `value` with no helper or cast:
|
|
967
|
+
*
|
|
968
|
+
* ```ts
|
|
969
|
+
* for await (const chunk of stream) {
|
|
970
|
+
* if (chunk.type === 'CUSTOM' && chunk.name === 'structured-output.complete') {
|
|
971
|
+
* chunk.value.object // typed as T
|
|
972
|
+
* } else if (chunk.type === 'CUSTOM' && chunk.name === 'approval-requested') {
|
|
973
|
+
* chunk.value.toolCallId // typed as string
|
|
974
|
+
* }
|
|
975
|
+
* }
|
|
976
|
+
* ```
|
|
977
|
+
*
|
|
978
|
+
* Caveat: tools can emit arbitrary user-defined custom events via the
|
|
979
|
+
* `emitCustomEvent(name, value)` context API. Those flow through this stream
|
|
980
|
+
* at runtime but are intentionally absent from this type — including a bare
|
|
981
|
+
* `CustomEvent` (whose `value: any` would poison the union) would collapse
|
|
982
|
+
* `chunk.value` back to `any` after the narrow. If you rely on
|
|
983
|
+
* `emitCustomEvent` plus `outputSchema + stream: true`, branch on `CUSTOM`
|
|
984
|
+
* outside the literal-`name` narrows or cast explicitly.
|
|
985
|
+
*/
|
|
986
|
+
export type StructuredOutputStream<T = unknown> = AsyncIterable<Exclude<StreamChunk, CustomEvent> | StructuredOutputCompleteEvent<T> | ApprovalRequestedEvent | ToolInputAvailableEvent>;
|
|
890
987
|
/**
|
|
891
988
|
* Emitted when reasoning starts for a message.
|
|
892
989
|
*
|
|
@@ -968,12 +1065,14 @@ export interface TextCompletionChunk {
|
|
|
968
1065
|
totalTokens: number;
|
|
969
1066
|
};
|
|
970
1067
|
}
|
|
971
|
-
export interface SummarizationOptions {
|
|
1068
|
+
export interface SummarizationOptions<TProviderOptions extends object = Record<string, unknown>> {
|
|
972
1069
|
model: string;
|
|
973
1070
|
text: string;
|
|
974
1071
|
maxLength?: number;
|
|
975
1072
|
style?: 'bullet-points' | 'paragraph' | 'concise';
|
|
976
1073
|
focus?: Array<string>;
|
|
1074
|
+
/** Provider-specific options forwarded by the summarize() activity. */
|
|
1075
|
+
modelOptions?: TProviderOptions;
|
|
977
1076
|
/**
|
|
978
1077
|
* Internal logger threaded from the summarize() entry point. Adapters must
|
|
979
1078
|
* call logger.request() before the SDK call and logger.errors() in catch blocks.
|
|
@@ -994,7 +1093,7 @@ export interface SummarizationResult {
|
|
|
994
1093
|
* Options for image generation.
|
|
995
1094
|
* These are the common options supported across providers.
|
|
996
1095
|
*/
|
|
997
|
-
export interface ImageGenerationOptions<TProviderOptions extends object = object, TSize extends string = string> {
|
|
1096
|
+
export interface ImageGenerationOptions<TProviderOptions extends object = object, TSize extends string | undefined = string> {
|
|
998
1097
|
/** The model to use for image generation */
|
|
999
1098
|
model: string;
|
|
1000
1099
|
/** Text description of the desired image(s) */
|
|
@@ -1102,7 +1201,7 @@ export interface AudioGenerationResult {
|
|
|
1102
1201
|
*
|
|
1103
1202
|
* @experimental Video generation is an experimental feature and may change.
|
|
1104
1203
|
*/
|
|
1105
|
-
export interface VideoGenerationOptions<TProviderOptions extends object = object, TSize extends string = string> {
|
|
1204
|
+
export interface VideoGenerationOptions<TProviderOptions extends object = object, TSize extends string | undefined = string> {
|
|
1106
1205
|
/** The model to use for video generation */
|
|
1107
1206
|
model: string;
|
|
1108
1207
|
/** Text description of the desired video */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
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.49",
|
|
57
57
|
"partial-json": "^0.1.7",
|
|
58
|
-
"@tanstack/ai-event-client": "0.
|
|
58
|
+
"@tanstack/ai-event-client": "0.3.1"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@opentelemetry/api": ">=1.9.0"
|
|
@@ -4,7 +4,9 @@ description: >
|
|
|
4
4
|
Type-safe JSON schema responses from LLMs using outputSchema on chat().
|
|
5
5
|
Supports Zod, ArkType, and Valibot schemas. The adapter handles
|
|
6
6
|
provider-specific strategies transparently — never configure structured
|
|
7
|
-
output at the provider level.
|
|
7
|
+
output at the provider level. Pass stream:true alongside outputSchema for
|
|
8
|
+
incremental JSON deltas + a terminal validated object via the
|
|
9
|
+
`structured-output.complete` event. convertSchemaToJsonSchema() for manual
|
|
8
10
|
schema conversion.
|
|
9
11
|
type: sub-skill
|
|
10
12
|
library: tanstack-ai
|
|
@@ -46,6 +48,8 @@ const stream = chat({
|
|
|
46
48
|
|
|
47
49
|
When `outputSchema` is provided, `chat()` returns `Promise<InferSchemaType<TSchema>>` instead of `AsyncIterable<StreamChunk>`. The result is fully typed based on the schema.
|
|
48
50
|
|
|
51
|
+
Adding `stream: true` switches the return to `StructuredOutputStream<InferSchemaType<TSchema>>` — incremental JSON deltas plus a terminal validated object. See **Pattern 3** below.
|
|
52
|
+
|
|
49
53
|
## Core Patterns
|
|
50
54
|
|
|
51
55
|
### Pattern 1: Basic structured output with Zod
|
|
@@ -128,8 +132,94 @@ console.log(company.employees[0].role)
|
|
|
128
132
|
console.log(company.financials?.revenue)
|
|
129
133
|
```
|
|
130
134
|
|
|
135
|
+
### Pattern 3: Streaming structured output
|
|
136
|
+
|
|
137
|
+
Pass `stream: true` alongside `outputSchema` to receive incremental JSON deltas while the model generates, plus a final validated typed object. Useful for streaming partial UI (progress views, typewriter previews, partially-filled forms).
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { chat } from '@tanstack/ai'
|
|
141
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
142
|
+
import { z } from 'zod'
|
|
143
|
+
|
|
144
|
+
const PersonSchema = z.object({
|
|
145
|
+
name: z.string(),
|
|
146
|
+
age: z.number(),
|
|
147
|
+
email: z.string().email(),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const stream = chat({
|
|
151
|
+
adapter: openaiText('gpt-5.2'),
|
|
152
|
+
messages: [
|
|
153
|
+
{ role: 'user', content: 'Extract: John Doe is 30, john@example.com' },
|
|
154
|
+
],
|
|
155
|
+
outputSchema: PersonSchema,
|
|
156
|
+
stream: true,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
let raw = ''
|
|
160
|
+
for await (const chunk of stream) {
|
|
161
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
|
|
162
|
+
// Partial JSON text — drive progress UI only. Do NOT JSON.parse.
|
|
163
|
+
raw += chunk.delta
|
|
164
|
+
} else if (
|
|
165
|
+
chunk.type === 'CUSTOM' &&
|
|
166
|
+
chunk.name === 'structured-output.complete'
|
|
167
|
+
) {
|
|
168
|
+
// Terminal event. `chunk.value.object` is fully validated and typed
|
|
169
|
+
// against the schema you passed in — no helper or cast required.
|
|
170
|
+
chunk.value.object.name // string
|
|
171
|
+
chunk.value.object.age // number
|
|
172
|
+
chunk.value.reasoning // string | undefined (thinking models only)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The terminal event is a `CUSTOM` chunk: `{ type: 'CUSTOM', name: 'structured-output.complete', value: { object: T, raw: string, reasoning?: string } }`. The return type of `chat({ outputSchema, stream: true })` carries `T` through to the terminal event, so a plain discriminated narrow (`chunk.type === 'CUSTOM' && chunk.name === 'structured-output.complete'`) is enough — no type guard helper needed.
|
|
178
|
+
|
|
179
|
+
**Adapter coverage for streaming:**
|
|
180
|
+
|
|
181
|
+
| Adapter | `outputSchema` + `stream: true` |
|
|
182
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
183
|
+
| `@tanstack/ai-openai` | Native single-request stream (Responses API) |
|
|
184
|
+
| `@tanstack/ai-openrouter` | Native single-request stream |
|
|
185
|
+
| `@tanstack/ai-grok` | Native single-request stream (Chat Completions) |
|
|
186
|
+
| `@tanstack/ai-groq` | Native single-request stream (Chat Completions) |
|
|
187
|
+
| All other adapters (anthropic, gemini, ollama, …) | Fallback: runs non-streaming `structuredOutput`, emits one `structured-output.complete` event |
|
|
188
|
+
|
|
189
|
+
The consumer code is identical across providers — always read the final object off `structured-output.complete`. You only see incremental deltas when the adapter implements `structuredOutputStream` natively.
|
|
190
|
+
|
|
131
191
|
## Common Mistakes
|
|
132
192
|
|
|
193
|
+
### HIGH: Parsing streaming JSON deltas yourself
|
|
194
|
+
|
|
195
|
+
When using `chat({ outputSchema, stream: true })`, the `TEXT_MESSAGE_CONTENT` chunks contain _partial_ JSON fragments — they are not valid JSON until the stream completes. Always read the validated object from the terminal `structured-output.complete` event. Validation runs once, on the complete payload.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// WRONG -- partial JSON, throws SyntaxError mid-stream, no schema validation
|
|
199
|
+
for await (const chunk of stream) {
|
|
200
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
|
|
201
|
+
const obj = JSON.parse(chunk.delta) // ❌ partial, invalid
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// CORRECT -- accumulate deltas only for UX progress; trust the terminal event
|
|
206
|
+
let raw = ''
|
|
207
|
+
for await (const chunk of stream) {
|
|
208
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
|
|
209
|
+
raw += chunk.delta // optional: render a "streaming JSON" preview
|
|
210
|
+
} else if (
|
|
211
|
+
chunk.type === 'CUSTOM' &&
|
|
212
|
+
chunk.name === 'structured-output.complete'
|
|
213
|
+
) {
|
|
214
|
+
const result = chunk.value.object // ✅ typed and validated
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
If you need progressive _parsed_ state (e.g. show fields as they arrive), use a partial-JSON parser on the accumulated `raw` string at render time — but do NOT treat the result as schema-validated; only the terminal event is.
|
|
220
|
+
|
|
221
|
+
Source: maintainer interview
|
|
222
|
+
|
|
133
223
|
### HIGH: Trying to implement provider-specific structured output strategies
|
|
134
224
|
|
|
135
225
|
The adapter already handles provider differences (OpenAI uses `response_format`, Anthropic uses tool-based extraction, Gemini uses `responseSchema`). Never configure this yourself.
|
|
@@ -201,3 +291,4 @@ Source: maintainer interview
|
|
|
201
291
|
## Cross-References
|
|
202
292
|
|
|
203
293
|
- See also: ai-core/adapter-configuration/SKILL.md -- Adapter handles structured output strategy transparently
|
|
294
|
+
- See also: ai-core/chat-experience/SKILL.md -- Consuming `StreamChunk` events on the client (the streaming variant uses the same chunk model plus the terminal `structured-output.complete` custom event)
|
|
@@ -54,6 +54,7 @@ export interface StructuredOutputResult<T = unknown> {
|
|
|
54
54
|
* - TInputModalities: Supported input modalities for this model (already resolved)
|
|
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
|
+
* - TToolCallMetadata: Metadata type that round-trips with tool calls (e.g. Gemini's `thoughtSignature`)
|
|
57
58
|
*/
|
|
58
59
|
export interface TextAdapter<
|
|
59
60
|
TModel extends string,
|
|
@@ -61,6 +62,7 @@ export interface TextAdapter<
|
|
|
61
62
|
TInputModalities extends ReadonlyArray<Modality>,
|
|
62
63
|
TMessageMetadataByModality extends DefaultMessageMetadataByModality,
|
|
63
64
|
TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,
|
|
65
|
+
TToolCallMetadata = unknown,
|
|
64
66
|
> {
|
|
65
67
|
/** Discriminator for adapter kind */
|
|
66
68
|
readonly kind: 'text'
|
|
@@ -77,6 +79,7 @@ export interface TextAdapter<
|
|
|
77
79
|
inputModalities: TInputModalities
|
|
78
80
|
messageMetadataByModality: TMessageMetadataByModality
|
|
79
81
|
toolCapabilities: TToolCapabilities
|
|
82
|
+
toolCallMetadata: TToolCallMetadata
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
/**
|
|
@@ -97,13 +100,30 @@ export interface TextAdapter<
|
|
|
97
100
|
structuredOutput: (
|
|
98
101
|
options: StructuredOutputOptions<TProviderOptions>,
|
|
99
102
|
) => Promise<StructuredOutputResult<unknown>>
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stream structured output using the provider's native streaming structured
|
|
106
|
+
* output API (stream + response_format json_schema in a single request).
|
|
107
|
+
*
|
|
108
|
+
* Optional — adapters without native streaming JSON omit this method and the
|
|
109
|
+
* activity layer synthesizes a stream around the non-streaming
|
|
110
|
+
* `structuredOutput` call.
|
|
111
|
+
*
|
|
112
|
+
* Implementations must emit standard AG-UI lifecycle events (RUN_STARTED,
|
|
113
|
+
* TEXT_MESSAGE_*, RUN_FINISHED) carrying raw JSON text deltas, plus a final
|
|
114
|
+
* `CUSTOM` event named `structured-output.complete` whose `value` is
|
|
115
|
+
* `{ object, raw, reasoning? }`.
|
|
116
|
+
*/
|
|
117
|
+
structuredOutputStream?: (
|
|
118
|
+
options: StructuredOutputOptions<TProviderOptions>,
|
|
119
|
+
) => AsyncIterable<StreamChunk>
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
/**
|
|
103
123
|
* A TextAdapter with any/unknown type parameters.
|
|
104
124
|
* Useful as a constraint in generic functions and interfaces.
|
|
105
125
|
*/
|
|
106
|
-
export type AnyTextAdapter = TextAdapter<any, any, any, any, any>
|
|
126
|
+
export type AnyTextAdapter = TextAdapter<any, any, any, any, any, any>
|
|
107
127
|
|
|
108
128
|
/**
|
|
109
129
|
* Abstract base class for text adapters.
|
|
@@ -117,12 +137,14 @@ export abstract class BaseTextAdapter<
|
|
|
117
137
|
TInputModalities extends ReadonlyArray<Modality>,
|
|
118
138
|
TMessageMetadataByModality extends DefaultMessageMetadataByModality,
|
|
119
139
|
TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,
|
|
140
|
+
TToolCallMetadata = unknown,
|
|
120
141
|
> implements TextAdapter<
|
|
121
142
|
TModel,
|
|
122
143
|
TProviderOptions,
|
|
123
144
|
TInputModalities,
|
|
124
145
|
TMessageMetadataByModality,
|
|
125
|
-
TToolCapabilities
|
|
146
|
+
TToolCapabilities,
|
|
147
|
+
TToolCallMetadata
|
|
126
148
|
> {
|
|
127
149
|
readonly kind = 'text' as const
|
|
128
150
|
abstract readonly name: string
|
|
@@ -134,6 +156,7 @@ export abstract class BaseTextAdapter<
|
|
|
134
156
|
inputModalities: TInputModalities
|
|
135
157
|
messageMetadataByModality: TMessageMetadataByModality
|
|
136
158
|
toolCapabilities: TToolCapabilities
|
|
159
|
+
toolCallMetadata: TToolCallMetadata
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
protected config: TextAdapterConfig
|