@tanstack/ai 0.32.0 → 0.34.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.
Files changed (64) hide show
  1. package/dist/esm/activities/chat/index.js +47 -20
  2. package/dist/esm/activities/chat/index.js.map +1 -1
  3. package/dist/esm/activities/chat/middleware/types.d.ts +7 -0
  4. package/dist/esm/activities/chat/tools/lazy-tool-manager.d.ts +25 -1
  5. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +26 -2
  6. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  7. package/dist/esm/activities/chat/tools/schema-converter.d.ts +13 -0
  8. package/dist/esm/activities/chat/tools/schema-converter.js +61 -33
  9. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  10. package/dist/esm/activities/generateAudio/index.d.ts +7 -0
  11. package/dist/esm/activities/generateAudio/index.js +26 -1
  12. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  13. package/dist/esm/activities/generateImage/index.d.ts +7 -0
  14. package/dist/esm/activities/generateImage/index.js +26 -1
  15. package/dist/esm/activities/generateImage/index.js.map +1 -1
  16. package/dist/esm/activities/generateSpeech/index.d.ts +7 -0
  17. package/dist/esm/activities/generateSpeech/index.js +26 -1
  18. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  19. package/dist/esm/activities/generateTranscription/index.d.ts +7 -0
  20. package/dist/esm/activities/generateTranscription/index.js +26 -1
  21. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  22. package/dist/esm/activities/generateVideo/index.d.ts +9 -0
  23. package/dist/esm/activities/generateVideo/index.js +52 -2
  24. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  25. package/dist/esm/activities/middleware/index.d.ts +2 -0
  26. package/dist/esm/activities/middleware/run.d.ts +20 -0
  27. package/dist/esm/activities/middleware/run.js +42 -0
  28. package/dist/esm/activities/middleware/run.js.map +1 -0
  29. package/dist/esm/activities/middleware/types.d.ts +118 -0
  30. package/dist/esm/index.d.ts +2 -0
  31. package/dist/esm/index.js +2 -0
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/middlewares/otel.d.ts +8 -2
  34. package/dist/esm/middlewares/otel.js +145 -95
  35. package/dist/esm/middlewares/otel.js.map +1 -1
  36. package/dist/esm/middlewares/usage-attributes.d.ts +24 -0
  37. package/dist/esm/middlewares/usage-attributes.js +43 -0
  38. package/dist/esm/middlewares/usage-attributes.js.map +1 -0
  39. package/dist/esm/types.d.ts +7 -7
  40. package/dist/esm/utilities/errors.d.ts +13 -0
  41. package/dist/esm/utilities/errors.js +22 -0
  42. package/dist/esm/utilities/errors.js.map +1 -0
  43. package/dist/esm/utilities/numbers.d.ts +8 -0
  44. package/dist/esm/utilities/numbers.js +12 -0
  45. package/dist/esm/utilities/numbers.js.map +1 -0
  46. package/package.json +3 -2
  47. package/src/activities/chat/index.ts +125 -35
  48. package/src/activities/chat/middleware/types.ts +7 -0
  49. package/src/activities/chat/tools/lazy-tool-manager.ts +46 -4
  50. package/src/activities/chat/tools/schema-converter.ts +146 -93
  51. package/src/activities/generateAudio/index.ts +42 -1
  52. package/src/activities/generateImage/index.ts +42 -1
  53. package/src/activities/generateSpeech/index.ts +42 -1
  54. package/src/activities/generateTranscription/index.ts +42 -1
  55. package/src/activities/generateVideo/index.ts +88 -2
  56. package/src/activities/middleware/index.ts +20 -0
  57. package/src/activities/middleware/run.ts +88 -0
  58. package/src/activities/middleware/types.ts +173 -0
  59. package/src/index.ts +19 -0
  60. package/src/middlewares/otel.ts +195 -120
  61. package/src/middlewares/usage-attributes.ts +65 -0
  62. package/src/types.ts +7 -7
  63. package/src/utilities/errors.ts +29 -0
  64. package/src/utilities/numbers.ts +15 -0
@@ -54,6 +54,13 @@ export interface ChatMiddlewareContext<TContext = unknown> {
54
54
  * after the terminal hook (onFinish/onAbort/onError).
55
55
  */
56
56
  defer: (promise: Promise<unknown>) => void;
57
+ /**
58
+ * Which activity this context describes — always `'chat'`. Present so the
59
+ * chat context structurally satisfies the base `GenerationMiddlewareContext`,
60
+ * letting an observe-only middleware authored against the base (e.g.
61
+ * `otelMiddleware`) run on both chat and media activities.
62
+ */
63
+ activity: 'chat';
57
64
  /** Provider name (e.g., 'openai', 'anthropic') */
58
65
  provider: string;
59
66
  /** Model identifier (e.g., 'gpt-4o') */
@@ -1,4 +1,12 @@
1
- import { Tool } from '../../../types.js';
1
+ import { AnyTool, Tool } from '../../../types.js';
2
+ /**
3
+ * Name of the synthetic tool the LLM calls to discover lazy tools.
4
+ *
5
+ * Exported so callers building custom message-compaction / history-trimming
6
+ * logic can reference the discovery tool by constant instead of hard-coding
7
+ * the string (which is an internal contract that could change).
8
+ */
9
+ export declare const DISCOVERY_TOOL_NAME = "__lazy__tool__discovery__";
2
10
  /**
3
11
  * Manages lazy tool discovery for the chat agent loop.
4
12
  *
@@ -31,6 +39,22 @@ export declare class LazyToolManager {
31
39
  * Resets the hasNewDiscoveries flag.
32
40
  */
33
41
  getActiveTools(): Array<Tool>;
42
+ /**
43
+ * Returns the tools that should be available for *execution* this turn.
44
+ *
45
+ * This is the advertised set (`getActiveTools()`, passed in as `activeTools`)
46
+ * plus the discovery tool when a pending call references it but it is no
47
+ * longer advertised. Once every lazy tool has been discovered the discovery
48
+ * tool is dropped from the advertised set, but a model may still re-request
49
+ * discovery (long context / hallucination); keeping it executable lets that
50
+ * call return the schemas again instead of failing with "Unknown tool".
51
+ *
52
+ * The advertised set is intentionally left unchanged — only execution lookup
53
+ * is widened. Operates on the already-built `activeTools`: it must NOT call
54
+ * `getActiveTools()`, which would reset `hasNewDiscoveries` before the
55
+ * post-execution refresh check in the agent loop.
56
+ */
57
+ getExecutableTools(activeTools: ReadonlyArray<AnyTool>, pendingToolCallNames: ReadonlyArray<string>): ReadonlyArray<AnyTool>;
34
58
  /**
35
59
  * Returns whether new tools have been discovered since the last getActiveTools() call.
36
60
  */
@@ -45,6 +45,27 @@ class LazyToolManager {
45
45
  }
46
46
  return active;
47
47
  }
48
+ /**
49
+ * Returns the tools that should be available for *execution* this turn.
50
+ *
51
+ * This is the advertised set (`getActiveTools()`, passed in as `activeTools`)
52
+ * plus the discovery tool when a pending call references it but it is no
53
+ * longer advertised. Once every lazy tool has been discovered the discovery
54
+ * tool is dropped from the advertised set, but a model may still re-request
55
+ * discovery (long context / hallucination); keeping it executable lets that
56
+ * call return the schemas again instead of failing with "Unknown tool".
57
+ *
58
+ * The advertised set is intentionally left unchanged — only execution lookup
59
+ * is widened. Operates on the already-built `activeTools`: it must NOT call
60
+ * `getActiveTools()`, which would reset `hasNewDiscoveries` before the
61
+ * post-execution refresh check in the agent loop.
62
+ */
63
+ getExecutableTools(activeTools, pendingToolCallNames) {
64
+ if (this.discoveryTool && pendingToolCallNames.includes(DISCOVERY_TOOL_NAME) && !activeTools.some((t) => t.name === DISCOVERY_TOOL_NAME)) {
65
+ return [...activeTools, this.discoveryTool];
66
+ }
67
+ return activeTools;
68
+ }
48
69
  /**
49
70
  * Returns whether new tools have been discovered since the last getActiveTools() call.
50
71
  */
@@ -135,8 +156,10 @@ class LazyToolManager {
135
156
  for (const name of args.toolNames) {
136
157
  const tool = lazyToolMap.get(name);
137
158
  if (tool) {
138
- manager.discoveredTools.add(name);
139
- manager.hasNewDiscoveries = true;
159
+ if (!manager.discoveredTools.has(name)) {
160
+ manager.discoveredTools.add(name);
161
+ manager.hasNewDiscoveries = true;
162
+ }
140
163
  const jsonSchema = tool.inputSchema ? convertSchemaToJsonSchema(tool.inputSchema) : void 0;
141
164
  tools.push({
142
165
  name: tool.name,
@@ -159,6 +182,7 @@ class LazyToolManager {
159
182
  }
160
183
  }
161
184
  export {
185
+ DISCOVERY_TOOL_NAME,
162
186
  LazyToolManager
163
187
  };
164
188
  //# sourceMappingURL=lazy-tool-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-tool-manager.js","sources":["../../../../../src/activities/chat/tools/lazy-tool-manager.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from './schema-converter'\nimport type { Tool } from '../../../types'\n\nconst DISCOVERY_TOOL_NAME = '__lazy__tool__discovery__'\n\n/**\n * Manages lazy tool discovery for the chat agent loop.\n *\n * Lazy tools are not sent to the LLM initially. Instead, a synthetic\n * \"discovery tool\" is provided that lets the LLM discover lazy tools\n * by name, receiving their full descriptions and schemas on demand.\n */\nexport class LazyToolManager {\n private readonly eagerTools: ReadonlyArray<Tool>\n private readonly lazyToolMap: Map<string, Tool>\n private readonly discoveredTools: Set<string>\n private hasNewDiscoveries: boolean\n private readonly discoveryTool: Tool | null\n\n constructor(\n tools: ReadonlyArray<Tool>,\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ) {\n const eager: Array<Tool> = []\n this.lazyToolMap = new Map()\n this.discoveredTools = new Set()\n this.hasNewDiscoveries = false\n\n // Separate tools into eager and lazy\n for (const tool of tools) {\n if (tool.lazy) {\n this.lazyToolMap.set(tool.name, tool)\n } else {\n eager.push(tool)\n }\n }\n this.eagerTools = eager\n\n // If no lazy tools, no discovery tool needed\n if (this.lazyToolMap.size === 0) {\n this.discoveryTool = null\n return\n }\n\n // Scan message history to pre-populate discoveredTools\n this.scanMessageHistory(messages)\n\n // Create the synthetic discovery tool\n this.discoveryTool = this.createDiscoveryTool()\n }\n\n /**\n * Returns the set of tools that should be sent to the LLM:\n * eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain).\n * Resets the hasNewDiscoveries flag.\n */\n getActiveTools(): Array<Tool> {\n this.hasNewDiscoveries = false\n\n const active: Array<Tool> = [...this.eagerTools]\n\n // Add discovered lazy tools\n for (const name of this.discoveredTools) {\n const tool = this.lazyToolMap.get(name)\n if (tool) {\n active.push(tool)\n }\n }\n\n // Add discovery tool if there are still undiscovered lazy tools\n if (\n this.discoveryTool &&\n this.discoveredTools.size < this.lazyToolMap.size\n ) {\n active.push(this.discoveryTool)\n }\n\n return active\n }\n\n /**\n * Returns whether new tools have been discovered since the last getActiveTools() call.\n */\n hasNewlyDiscoveredTools(): boolean {\n return this.hasNewDiscoveries\n }\n\n /**\n * Returns true if the given name is a lazy tool that has not yet been discovered.\n */\n isUndiscoveredLazyTool(name: string): boolean {\n return this.lazyToolMap.has(name) && !this.discoveredTools.has(name)\n }\n\n /**\n * Returns a helpful error message for when an undiscovered lazy tool is called.\n */\n getUndiscoveredToolError(name: string): string {\n return `Error: Tool '${name}' must be discovered first. Call ${DISCOVERY_TOOL_NAME} with toolNames: ['${name}'] to discover it.`\n }\n\n /**\n * Scans message history to find previously discovered lazy tools.\n * Looks for assistant messages with discovery tool calls and their\n * corresponding tool result messages.\n */\n private scanMessageHistory(\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ): void {\n // Collect tool call IDs for discovery tool invocations\n const discoveryCallIds = new Set<string>()\n\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n if (tc.function.name === DISCOVERY_TOOL_NAME) {\n discoveryCallIds.add(tc.id)\n }\n }\n }\n }\n\n if (discoveryCallIds.size === 0) return\n\n // Find corresponding tool result messages\n for (const msg of messages) {\n if (\n msg.role === 'tool' &&\n msg.toolCallId &&\n discoveryCallIds.has(msg.toolCallId)\n ) {\n try {\n const content =\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content)\n const parsed = JSON.parse(content)\n if (parsed && Array.isArray(parsed.tools)) {\n for (const tool of parsed.tools) {\n if (\n tool &&\n typeof tool.name === 'string' &&\n this.lazyToolMap.has(tool.name)\n ) {\n this.discoveredTools.add(tool.name)\n }\n }\n }\n } catch {\n // Malformed JSON — skip gracefully\n }\n }\n }\n }\n\n /**\n * Creates the synthetic discovery tool that the LLM can call\n * to discover lazy tools' descriptions and schemas.\n */\n private createDiscoveryTool(): Tool {\n const undiscoveredNames = (): Array<string> => {\n const names: Array<string> = []\n for (const [name] of this.lazyToolMap) {\n if (!this.discoveredTools.has(name)) {\n names.push(name)\n }\n }\n return names\n }\n\n const lazyToolMap = this.lazyToolMap\n\n // Build the static description with all lazy tool names\n const allLazyNames = Array.from(this.lazyToolMap.keys())\n const description = `You have access to additional tools that can be discovered. Available tools: [${allLazyNames.join(', ')}]. Call this tool with a list of tool names to discover their full descriptions and argument schemas before using them.`\n\n // Use the arrow function to capture `this` context\n const manager = this\n\n return {\n name: DISCOVERY_TOOL_NAME,\n description,\n inputSchema: {\n type: 'object',\n properties: {\n toolNames: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'List of tool names to discover. Each name must match one of the available tools.',\n },\n },\n required: ['toolNames'],\n },\n execute: (args: { toolNames: Array<string> }) => {\n const tools: Array<{\n name: string\n description: string\n inputSchema?: any\n }> = []\n const errors: Array<string> = []\n\n for (const name of args.toolNames) {\n const tool = lazyToolMap.get(name)\n if (tool) {\n manager.discoveredTools.add(name)\n manager.hasNewDiscoveries = true\n const jsonSchema = tool.inputSchema\n ? convertSchemaToJsonSchema(tool.inputSchema)\n : undefined\n tools.push({\n name: tool.name,\n description: tool.description,\n ...(jsonSchema ? { inputSchema: jsonSchema } : {}),\n })\n } else {\n errors.push(\n `Unknown tool: '${name}'. Available tools: [${undiscoveredNames().join(', ')}]`,\n )\n }\n }\n\n const result: {\n tools: typeof tools\n errors?: Array<string>\n } = { tools }\n\n if (errors.length > 0) {\n result.errors = errors\n }\n\n return result\n },\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lazy-tool-manager.js","sources":["../../../../../src/activities/chat/tools/lazy-tool-manager.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from './schema-converter'\nimport type { AnyTool, Tool } from '../../../types'\n\n/**\n * Name of the synthetic tool the LLM calls to discover lazy tools.\n *\n * Exported so callers building custom message-compaction / history-trimming\n * logic can reference the discovery tool by constant instead of hard-coding\n * the string (which is an internal contract that could change).\n */\nexport const DISCOVERY_TOOL_NAME = '__lazy__tool__discovery__'\n\n/**\n * Manages lazy tool discovery for the chat agent loop.\n *\n * Lazy tools are not sent to the LLM initially. Instead, a synthetic\n * \"discovery tool\" is provided that lets the LLM discover lazy tools\n * by name, receiving their full descriptions and schemas on demand.\n */\nexport class LazyToolManager {\n private readonly eagerTools: ReadonlyArray<Tool>\n private readonly lazyToolMap: Map<string, Tool>\n private readonly discoveredTools: Set<string>\n private hasNewDiscoveries: boolean\n private readonly discoveryTool: Tool | null\n\n constructor(\n tools: ReadonlyArray<Tool>,\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ) {\n const eager: Array<Tool> = []\n this.lazyToolMap = new Map()\n this.discoveredTools = new Set()\n this.hasNewDiscoveries = false\n\n // Separate tools into eager and lazy\n for (const tool of tools) {\n if (tool.lazy) {\n this.lazyToolMap.set(tool.name, tool)\n } else {\n eager.push(tool)\n }\n }\n this.eagerTools = eager\n\n // If no lazy tools, no discovery tool needed\n if (this.lazyToolMap.size === 0) {\n this.discoveryTool = null\n return\n }\n\n // Scan message history to pre-populate discoveredTools\n this.scanMessageHistory(messages)\n\n // Create the synthetic discovery tool\n this.discoveryTool = this.createDiscoveryTool()\n }\n\n /**\n * Returns the set of tools that should be sent to the LLM:\n * eager tools + discovered lazy tools + discovery tool (if undiscovered tools remain).\n * Resets the hasNewDiscoveries flag.\n */\n getActiveTools(): Array<Tool> {\n this.hasNewDiscoveries = false\n\n const active: Array<Tool> = [...this.eagerTools]\n\n // Add discovered lazy tools\n for (const name of this.discoveredTools) {\n const tool = this.lazyToolMap.get(name)\n if (tool) {\n active.push(tool)\n }\n }\n\n // Add discovery tool if there are still undiscovered lazy tools\n if (\n this.discoveryTool &&\n this.discoveredTools.size < this.lazyToolMap.size\n ) {\n active.push(this.discoveryTool)\n }\n\n return active\n }\n\n /**\n * Returns the tools that should be available for *execution* this turn.\n *\n * This is the advertised set (`getActiveTools()`, passed in as `activeTools`)\n * plus the discovery tool when a pending call references it but it is no\n * longer advertised. Once every lazy tool has been discovered the discovery\n * tool is dropped from the advertised set, but a model may still re-request\n * discovery (long context / hallucination); keeping it executable lets that\n * call return the schemas again instead of failing with \"Unknown tool\".\n *\n * The advertised set is intentionally left unchanged — only execution lookup\n * is widened. Operates on the already-built `activeTools`: it must NOT call\n * `getActiveTools()`, which would reset `hasNewDiscoveries` before the\n * post-execution refresh check in the agent loop.\n */\n getExecutableTools(\n activeTools: ReadonlyArray<AnyTool>,\n pendingToolCallNames: ReadonlyArray<string>,\n ): ReadonlyArray<AnyTool> {\n if (\n this.discoveryTool &&\n pendingToolCallNames.includes(DISCOVERY_TOOL_NAME) &&\n !activeTools.some((t) => t.name === DISCOVERY_TOOL_NAME)\n ) {\n return [...activeTools, this.discoveryTool]\n }\n return activeTools\n }\n\n /**\n * Returns whether new tools have been discovered since the last getActiveTools() call.\n */\n hasNewlyDiscoveredTools(): boolean {\n return this.hasNewDiscoveries\n }\n\n /**\n * Returns true if the given name is a lazy tool that has not yet been discovered.\n */\n isUndiscoveredLazyTool(name: string): boolean {\n return this.lazyToolMap.has(name) && !this.discoveredTools.has(name)\n }\n\n /**\n * Returns a helpful error message for when an undiscovered lazy tool is called.\n */\n getUndiscoveredToolError(name: string): string {\n return `Error: Tool '${name}' must be discovered first. Call ${DISCOVERY_TOOL_NAME} with toolNames: ['${name}'] to discover it.`\n }\n\n /**\n * Scans message history to find previously discovered lazy tools.\n * Looks for assistant messages with discovery tool calls and their\n * corresponding tool result messages.\n */\n private scanMessageHistory(\n messages: ReadonlyArray<{\n role: string\n content?: any\n toolCalls?: Array<{\n id: string\n type: string\n function: { name: string; arguments: string }\n }>\n toolCallId?: string\n }>,\n ): void {\n // Collect tool call IDs for discovery tool invocations\n const discoveryCallIds = new Set<string>()\n\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n if (tc.function.name === DISCOVERY_TOOL_NAME) {\n discoveryCallIds.add(tc.id)\n }\n }\n }\n }\n\n if (discoveryCallIds.size === 0) return\n\n // Find corresponding tool result messages\n for (const msg of messages) {\n if (\n msg.role === 'tool' &&\n msg.toolCallId &&\n discoveryCallIds.has(msg.toolCallId)\n ) {\n try {\n const content =\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content)\n const parsed = JSON.parse(content)\n if (parsed && Array.isArray(parsed.tools)) {\n for (const tool of parsed.tools) {\n if (\n tool &&\n typeof tool.name === 'string' &&\n this.lazyToolMap.has(tool.name)\n ) {\n this.discoveredTools.add(tool.name)\n }\n }\n }\n } catch {\n // Malformed JSON — skip gracefully\n }\n }\n }\n }\n\n /**\n * Creates the synthetic discovery tool that the LLM can call\n * to discover lazy tools' descriptions and schemas.\n */\n private createDiscoveryTool(): Tool {\n const undiscoveredNames = (): Array<string> => {\n const names: Array<string> = []\n for (const [name] of this.lazyToolMap) {\n if (!this.discoveredTools.has(name)) {\n names.push(name)\n }\n }\n return names\n }\n\n const lazyToolMap = this.lazyToolMap\n\n // Build the static description with all lazy tool names\n const allLazyNames = Array.from(this.lazyToolMap.keys())\n const description = `You have access to additional tools that can be discovered. Available tools: [${allLazyNames.join(', ')}]. Call this tool with a list of tool names to discover their full descriptions and argument schemas before using them.`\n\n // Use the arrow function to capture `this` context\n const manager = this\n\n return {\n name: DISCOVERY_TOOL_NAME,\n description,\n inputSchema: {\n type: 'object',\n properties: {\n toolNames: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'List of tool names to discover. Each name must match one of the available tools.',\n },\n },\n required: ['toolNames'],\n },\n execute: (args: { toolNames: Array<string> }) => {\n const tools: Array<{\n name: string\n description: string\n inputSchema?: any\n }> = []\n const errors: Array<string> = []\n\n for (const name of args.toolNames) {\n const tool = lazyToolMap.get(name)\n if (tool) {\n // Only flag a refresh for genuinely new discoveries. Re-requesting\n // an already-discovered tool still returns its schema below (the\n // model asked for it), but must not trigger a redundant tool-list\n // refresh + continue in the agent loop.\n if (!manager.discoveredTools.has(name)) {\n manager.discoveredTools.add(name)\n manager.hasNewDiscoveries = true\n }\n const jsonSchema = tool.inputSchema\n ? convertSchemaToJsonSchema(tool.inputSchema)\n : undefined\n tools.push({\n name: tool.name,\n description: tool.description,\n ...(jsonSchema ? { inputSchema: jsonSchema } : {}),\n })\n } else {\n errors.push(\n `Unknown tool: '${name}'. Available tools: [${undiscoveredNames().join(', ')}]`,\n )\n }\n }\n\n const result: {\n tools: typeof tools\n errors?: Array<string>\n } = { tools }\n\n if (errors.length > 0) {\n result.errors = errors\n }\n\n return result\n },\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { StandardJSONSchemaV1, StandardSchemaV1 } from '@standard-schema/spec';
2
+ import { NullWideningMap } from '@tanstack/ai-utils';
2
3
  import { JSONSchema, SchemaInput } from '../../../types.js';
3
4
  /**
4
5
  * Check if a value is a Standard JSON Schema compliant schema.
@@ -86,6 +87,18 @@ export interface ConvertSchemaOptions {
86
87
  * ```
87
88
  */
88
89
  export declare function convertSchemaToJsonSchema(schema: SchemaInput | undefined, options?: ConvertSchemaOptions): JSONSchema | undefined;
90
+ /**
91
+ * Convert a schema for structured output AND capture the {@link NullWideningMap}
92
+ * recording every `null` the strict-mode widening synthesized. The map lets the
93
+ * caller undo that widening on the provider's response (via `undoNullWidening`)
94
+ * before validating against the original schema — optional fields read back as
95
+ * absent while genuine `.nullable()` nulls survive. The map is `undefined` when
96
+ * the schema isn't a widenable object or when no field needed widening.
97
+ */
98
+ export declare function convertSchemaForStructuredOutput(schema: SchemaInput | undefined): {
99
+ jsonSchema: JSONSchema | undefined;
100
+ nullWideningMap: NullWideningMap | undefined;
101
+ };
89
102
  /**
90
103
  * Validates data against a Standard Schema compliant schema.
91
104
  *
@@ -20,74 +20,84 @@ function isStandardJSONSchema(schema) {
20
20
  function isStandardSchema(schema) {
21
21
  return isPropertyCarrier(schema) && "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && "version" in schema["~standard"] && schema["~standard"].version === 1 && "validate" in schema["~standard"] && typeof schema["~standard"].validate === "function";
22
22
  }
23
+ function pruneMap(map) {
24
+ return Object.keys(map).length > 0 ? map : void 0;
25
+ }
23
26
  function makeStructuredOutputCompatible(schema, originalRequired = []) {
24
27
  const result = { ...schema };
28
+ const map = {};
25
29
  if (result.type === "object" && result.properties) {
26
30
  const properties = { ...result.properties };
27
31
  const allPropertyNames = Object.keys(properties);
32
+ const propertyMaps = {};
28
33
  for (const propName of allPropertyNames) {
29
34
  const prop = properties[propName];
30
35
  if (!prop) continue;
31
36
  const wasOptional = !originalRequired.includes(propName);
37
+ let widenedHere = false;
38
+ let childMap;
32
39
  if (prop.type === "object" && prop.properties) {
33
- const transformed = makeStructuredOutputCompatible(
34
- prop,
35
- prop.required || []
36
- );
37
- properties[propName] = wasOptional ? { ...transformed, type: ["object", "null"] } : transformed;
40
+ const nested = makeStructuredOutputCompatible(prop, prop.required || []);
41
+ properties[propName] = wasOptional ? { ...nested.schema, type: ["object", "null"] } : nested.schema;
42
+ widenedHere = wasOptional;
43
+ childMap = nested.nullWidening;
38
44
  } else if (prop.type === "array" && prop.items) {
39
45
  const items = Array.isArray(prop.items) ? prop.items[0] : prop.items;
40
- const transformed = {
46
+ const nestedItems = items ? makeStructuredOutputCompatible(items, items.required || []) : void 0;
47
+ properties[propName] = {
41
48
  ...prop,
42
- items: items ? makeStructuredOutputCompatible(items, items.required || []) : prop.items
49
+ items: nestedItems ? nestedItems.schema : prop.items,
50
+ ...wasOptional ? { type: ["array", "null"] } : {}
43
51
  };
44
- properties[propName] = wasOptional ? { ...transformed, type: ["array", "null"] } : transformed;
52
+ widenedHere = wasOptional;
53
+ childMap = nestedItems?.nullWidening ? { items: nestedItems.nullWidening } : void 0;
45
54
  } else if (wasOptional) {
46
55
  if (prop.type && !Array.isArray(prop.type)) {
47
- properties[propName] = {
48
- ...prop,
49
- type: [prop.type, "null"]
50
- };
56
+ properties[propName] = { ...prop, type: [prop.type, "null"] };
57
+ widenedHere = true;
51
58
  } else if (Array.isArray(prop.type) && !prop.type.includes("null")) {
52
- properties[propName] = {
53
- ...prop,
54
- type: [...prop.type, "null"]
55
- };
59
+ properties[propName] = { ...prop, type: [...prop.type, "null"] };
60
+ widenedHere = true;
56
61
  }
57
62
  }
63
+ if (widenedHere || childMap) {
64
+ propertyMaps[propName] = {
65
+ ...childMap ?? {},
66
+ ...widenedHere ? { widened: true } : {}
67
+ };
68
+ }
58
69
  }
59
70
  result.properties = properties;
60
71
  result.required = allPropertyNames;
61
72
  result.additionalProperties = false;
73
+ if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps;
62
74
  }
63
75
  if (result.type === "array" && result.items) {
64
76
  const items = Array.isArray(result.items) ? result.items[0] : result.items;
65
77
  if (items) {
66
- result.items = makeStructuredOutputCompatible(items, items.required || []);
78
+ const nestedItems = makeStructuredOutputCompatible(
79
+ items,
80
+ items.required || []
81
+ );
82
+ result.items = nestedItems.schema;
83
+ if (nestedItems.nullWidening) map.items = nestedItems.nullWidening;
67
84
  }
68
85
  }
69
- return result;
86
+ return { schema: result, nullWidening: pruneMap(map) };
70
87
  }
71
- function convertSchemaToJsonSchema(schema, options = {}) {
72
- if (!schema) return void 0;
73
- const { forStructuredOutput = false } = options;
88
+ function toTypedJsonSchema(schema) {
74
89
  if (isStandardJSONSchema(schema)) {
75
90
  const jsonSchema = schema["~standard"].jsonSchema.input({
76
91
  target: "draft-07"
77
92
  });
78
- let result = toJsonSchema(jsonSchema);
79
- if ("properties" in result && !result.type) {
80
- result.type = "object";
81
- }
93
+ const result = toJsonSchema(jsonSchema);
94
+ if ("properties" in result && !result.type) result.type = "object";
82
95
  if (result.type === "object" && !("properties" in result)) {
83
96
  result.properties = {};
84
97
  }
85
98
  if (result.type === "object" && !("required" in result)) {
86
99
  result.required = [];
87
100
  }
88
- if (forStructuredOutput) {
89
- result = makeStructuredOutputCompatible(result, result.required || []);
90
- }
91
101
  return result;
92
102
  }
93
103
  if (isStandardSchema(schema)) {
@@ -95,14 +105,31 @@ function convertSchemaToJsonSchema(schema, options = {}) {
95
105
  "Schema is a Standard Schema validator but does not expose a JSON Schema converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, or wrap a Valibot schema with `toStandardJsonSchema()` from `@valibot/to-json-schema` before passing it as `outputSchema`."
96
106
  );
97
107
  }
98
- if (typeof schema !== "object") {
108
+ if (typeof schema !== "object") return schema;
109
+ return toJsonSchema(schema);
110
+ }
111
+ function convertSchemaToJsonSchema(schema, options = {}) {
112
+ if (!schema) return void 0;
113
+ const { forStructuredOutput = false } = options;
114
+ if (!forStructuredOutput && !isStandardJSONSchema(schema) && !isStandardSchema(schema)) {
99
115
  return schema;
100
116
  }
101
- if (forStructuredOutput) {
102
- const typedView = toJsonSchema(schema);
103
- return makeStructuredOutputCompatible(typedView, typedView.required || []);
117
+ const base = toTypedJsonSchema(schema);
118
+ if (!base || typeof base !== "object") return base;
119
+ if (!forStructuredOutput) return base;
120
+ return makeStructuredOutputCompatible(base, base.required || []).schema;
121
+ }
122
+ function convertSchemaForStructuredOutput(schema) {
123
+ if (!schema) return { jsonSchema: void 0, nullWideningMap: void 0 };
124
+ const base = toTypedJsonSchema(schema);
125
+ if (!base || typeof base !== "object") {
126
+ return { jsonSchema: base, nullWideningMap: void 0 };
104
127
  }
105
- return schema;
128
+ const { schema: jsonSchema, nullWidening } = makeStructuredOutputCompatible(
129
+ base,
130
+ base.required || []
131
+ );
132
+ return { jsonSchema, nullWideningMap: nullWidening };
106
133
  }
107
134
  class StandardSchemaValidationError extends Error {
108
135
  name = "StandardSchemaValidationError";
@@ -131,6 +158,7 @@ function parseWithStandardSchema(schema, data) {
131
158
  }
132
159
  export {
133
160
  StandardSchemaValidationError,
161
+ convertSchemaForStructuredOutput,
134
162
  convertSchemaToJsonSchema,
135
163
  isStandardJSONSchema,
136
164
  isStandardSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"schema-converter.js","sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Whether a value can carry a `~standard` property. Most schema libraries\n * (Zod, Valibot) return plain objects, but ArkType's `type()` returns a\n * *callable function* with `~standard` attached — so `typeof` must accept\n * both `'object'` and `'function'` or ArkType schemas are missed entirely\n * (issue #276).\n */\nfunction isPropertyCarrier(schema: unknown): schema is Record<string, unknown> {\n return (\n (typeof schema === 'object' || typeof schema === 'function') &&\n schema !== null\n )\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n if (!isPropertyCarrier(schema) || !('~standard' in schema)) return false\n\n const standard = schema['~standard']\n if (\n typeof standard !== 'object' ||\n standard === null ||\n !('version' in standard) ||\n standard.version !== 1 ||\n !('jsonSchema' in standard) ||\n typeof standard.jsonSchema !== 'object' ||\n standard.jsonSchema === null ||\n !('input' in standard.jsonSchema)\n ) {\n return false\n }\n\n return typeof standard.jsonSchema.input === 'function'\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n isPropertyCarrier(schema) &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with OpenAI structured output\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): JSONSchema {\n const result: JSONSchema = { ...schema }\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const transformed = makeStructuredOutputCompatible(\n prop,\n prop.required || [],\n )\n properties[propName] = wasOptional\n ? { ...transformed, type: ['object', 'null'] }\n : transformed\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const transformed: JSONSchema = {\n ...prop,\n items: items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : prop.items,\n }\n properties[propName] = wasOptional\n ? { ...transformed, type: ['array', 'null'] }\n : transformed\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = {\n ...prop,\n type: [prop.type, 'null'],\n }\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = {\n ...prop,\n type: [...prop.type, 'null'],\n }\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n result.items = makeStructuredOutputCompatible(items, items.required || [])\n }\n }\n\n return result\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // If it's a Standard JSON Schema compliant schema, use the standard interface\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n\n // Rebuild structurally so the typed JSONSchema view is acquired without\n // a `Record<string, unknown> as JSONSchema` cast; `toJsonSchema()` also\n // drops the `$schema` key which LLM providers don't need.\n let result: JSONSchema = toJsonSchema(jsonSchema)\n\n // Ensure object schemas always have type: \"object\"\n // If it has properties (even empty), it should be an object type\n if ('properties' in result && !result.type) {\n result.type = 'object'\n }\n\n // Ensure properties exists for object types (even if empty)\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n\n // Ensure required exists for object types (even if empty array)\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n\n // Apply structured output transformation if requested\n if (forStructuredOutput) {\n result = makeStructuredOutputCompatible(result, result.required || [])\n }\n\n return result\n }\n\n // Detect Standard Schema validators (Zod, ArkType, Valibot, …) that don't\n // expose a `~standard.jsonSchema` converter. These would otherwise fall\n // through to the JSONSchema pass-through below and ship `{ '~standard': … }`\n // straight to the LLM provider, producing an opaque downstream error. Fail\n // fast with actionable guidance instead.\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n // If it's not a Standard JSON Schema, assume it's already a JSONSchema and pass through\n // Still apply structured output transformation if requested\n\n // At this branch, `schema` is the plain `JSONSchema` arm of `SchemaInput`\n // (the two `~standard` arms were handled above). When no transformation\n // is requested we pass the schema through by reference to preserve\n // identity for callers that compare via `===`.\n if (typeof schema !== 'object') {\n // The SchemaInput union is object-shaped on every arm; if we ever hit a\n // non-object here, propagate it untouched and let the downstream\n // provider error loudly rather than silently widen.\n return schema\n }\n\n if (forStructuredOutput) {\n // Build a typed view structurally so we don't need a SchemaInput→JSONSchema\n // cast on the transformation path.\n const typedView = toJsonSchema(schema)\n return makeStructuredOutputCompatible(typedView, typedView.required || [])\n }\n\n return schema\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Error thrown when Standard Schema validation fails. Carries the original\n * `issues` array so consumers (middleware `onError`, callers catching from\n * `chat({ outputSchema })`) can programmatically inspect each failure.\n */\nexport class StandardSchemaValidationError extends Error {\n override readonly name = 'StandardSchemaValidationError'\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Validation failed: ${issues\n .map((i) => i.message || 'Validation failed')\n .join(', ')}`,\n )\n this.issues = issues\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws StandardSchemaValidationError if validation fails; Error if the\n * schema only supports async validation.\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n throw new StandardSchemaValidationError(result.issues)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-converter.js","sources":["../../../../../src/activities/chat/tools/schema-converter.ts"],"sourcesContent":["import type {\n StandardJSONSchemaV1,\n StandardSchemaV1,\n} from '@standard-schema/spec'\nimport type { NullWideningMap } from '@tanstack/ai-utils'\nimport type { JSONSchema, SchemaInput } from '../../../types'\n\n/**\n * Build a JSONSchema object from any plain key/value source. The `JSONSchema`\n * interface's `[key: string]: any` index signature makes every property\n * assignable through bracket access without a type cast — copying keys here\n * lets us narrow either `Record<string, unknown>` (returned by\n * `~standard.jsonSchema.input()`) or a `JSONSchema` (from the SchemaInput\n * pass-through arm) into the typed view used by the rest of this module.\n *\n * Accepts `object` so callers don't need a cast when narrowing from union\n * types like `SchemaInput`.\n */\nfunction toJsonSchema(obj: object): JSONSchema {\n const result: JSONSchema = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '$schema') continue // not needed by LLM providers\n result[key] = value\n }\n return result\n}\n\n/**\n * Whether a value can carry a `~standard` property. Most schema libraries\n * (Zod, Valibot) return plain objects, but ArkType's `type()` returns a\n * *callable function* with `~standard` attached — so `typeof` must accept\n * both `'object'` and `'function'` or ArkType schemas are missed entirely\n * (issue #276).\n */\nfunction isPropertyCarrier(schema: unknown): schema is Record<string, unknown> {\n return (\n (typeof schema === 'object' || typeof schema === 'function') &&\n schema !== null\n )\n}\n\n/**\n * Check if a value is a Standard JSON Schema compliant schema.\n * Standard JSON Schema compliant libraries (Zod v4+, ArkType, Valibot with toStandardJsonSchema, etc.)\n * implement the '~standard' property with jsonSchema converter methods.\n */\nexport function isStandardJSONSchema(\n schema: unknown,\n): schema is StandardJSONSchemaV1 {\n if (!isPropertyCarrier(schema) || !('~standard' in schema)) return false\n\n const standard = schema['~standard']\n if (\n typeof standard !== 'object' ||\n standard === null ||\n !('version' in standard) ||\n standard.version !== 1 ||\n !('jsonSchema' in standard) ||\n typeof standard.jsonSchema !== 'object' ||\n standard.jsonSchema === null ||\n !('input' in standard.jsonSchema)\n ) {\n return false\n }\n\n return typeof standard.jsonSchema.input === 'function'\n}\n\n/**\n * Check if a value is a Standard Schema compliant schema (for validation).\n * Standard Schema compliant libraries implement the '~standard' property with a validate function.\n */\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n isPropertyCarrier(schema) &&\n '~standard' in schema &&\n typeof schema['~standard'] === 'object' &&\n schema['~standard'] !== null &&\n 'version' in schema['~standard'] &&\n schema['~standard'].version === 1 &&\n 'validate' in schema['~standard'] &&\n typeof schema['~standard'].validate === 'function'\n )\n}\n\n/**\n * Result of {@link makeStructuredOutputCompatible}: the strict-ready schema plus\n * a {@link NullWideningMap} recording every position where a `null` was\n * synthesized, so the response can be un-widened before validation without\n * re-deriving (or guessing) which nulls were synthetic.\n */\ninterface StructuredOutputConversion {\n schema: JSONSchema\n nullWidening: NullWideningMap | undefined\n}\n\n/** Drop an empty map to `undefined` so leaf/no-op subtrees don't litter it. */\nfunction pruneMap(map: NullWideningMap): NullWideningMap | undefined {\n return Object.keys(map).length > 0 ? map : undefined\n}\n\n/**\n * Transform a JSON schema to be compatible with OpenAI's structured output requirements.\n * OpenAI requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n *\n * Alongside the transformed schema it returns a {@link NullWideningMap} marking\n * exactly the positions where `null` was added, so `undoNullWidening` can strip\n * those synthesized nulls (and only those) from the provider's response.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema + the null-widening map for the round trip\n */\nfunction makeStructuredOutputCompatible(\n schema: JSONSchema,\n originalRequired: Array<string> = [],\n): StructuredOutputConversion {\n const result: JSONSchema = { ...schema }\n const map: NullWideningMap = {}\n\n // Handle object types\n if (result.type === 'object' && result.properties) {\n const properties: Record<string, JSONSchema> = { ...result.properties }\n const allPropertyNames = Object.keys(properties)\n const propertyMaps: Record<string, NullWideningMap> = {}\n\n // Transform each property\n for (const propName of allPropertyNames) {\n const prop = properties[propName]\n if (!prop) continue\n const wasOptional = !originalRequired.includes(propName)\n // `null` synthesized AT this property (the field itself can come back null).\n let widenedHere = false\n // Map describing widened positions INSIDE this property.\n let childMap: NullWideningMap | undefined\n\n // Recursively transform nested objects/arrays\n if (prop.type === 'object' && prop.properties) {\n const nested = makeStructuredOutputCompatible(prop, prop.required || [])\n properties[propName] = wasOptional\n ? { ...nested.schema, type: ['object', 'null'] }\n : nested.schema\n widenedHere = wasOptional\n childMap = nested.nullWidening\n } else if (prop.type === 'array' && prop.items) {\n const items = Array.isArray(prop.items) ? prop.items[0] : prop.items\n const nestedItems = items\n ? makeStructuredOutputCompatible(items, items.required || [])\n : undefined\n properties[propName] = {\n ...prop,\n items: nestedItems ? nestedItems.schema : prop.items,\n ...(wasOptional ? { type: ['array', 'null'] } : {}),\n }\n widenedHere = wasOptional\n childMap = nestedItems?.nullWidening\n ? { items: nestedItems.nullWidening }\n : undefined\n } else if (wasOptional) {\n // Make optional fields nullable by adding null to the type. Mark\n // `widenedHere` only where we actually add `null`; a field already\n // typed nullable (`.nullish()`) is left as-is and keeps its null.\n if (prop.type && !Array.isArray(prop.type)) {\n properties[propName] = { ...prop, type: [prop.type, 'null'] }\n widenedHere = true\n } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {\n properties[propName] = { ...prop, type: [...prop.type, 'null'] }\n widenedHere = true\n }\n }\n\n if (widenedHere || childMap) {\n propertyMaps[propName] = {\n ...(childMap ?? {}),\n ...(widenedHere ? { widened: true } : {}),\n }\n }\n }\n\n result.properties = properties\n // ALL properties must be required for OpenAI structured output\n result.required = allPropertyNames\n // additionalProperties must be false\n result.additionalProperties = false\n if (Object.keys(propertyMaps).length > 0) map.properties = propertyMaps\n }\n\n // Handle array types with object items\n if (result.type === 'array' && result.items) {\n const items = Array.isArray(result.items) ? result.items[0] : result.items\n if (items) {\n const nestedItems = makeStructuredOutputCompatible(\n items,\n items.required || [],\n )\n result.items = nestedItems.schema\n if (nestedItems.nullWidening) map.items = nestedItems.nullWidening\n }\n }\n\n return { schema: result, nullWidening: pruneMap(map) }\n}\n\n/**\n * Options for schema conversion\n */\nexport interface ConvertSchemaOptions {\n /**\n * When true, transforms the schema to be compatible with OpenAI's structured output requirements:\n * - All properties are added to the `required` array\n * - Optional fields get null added to their type union\n * - additionalProperties is set to false for all objects\n *\n * @default false\n */\n forStructuredOutput?: boolean\n}\n\n/**\n * Normalize any supported schema input to a typed, UN-widened `JSONSchema` —\n * the shared first half of conversion, before any structured-output widening.\n *\n * - Standard JSON Schemas are rebuilt structurally (dropping `$schema`, which\n * LLM providers ignore) and given the explicit `type`/`properties`/`required`\n * defaults object shapes need downstream.\n * - Plain `JSONSchema` inputs are rebuilt into the typed view; non-object inputs\n * are surfaced untouched (they can't be widened).\n * - Standard Schema validators lacking a `~standard.jsonSchema` converter throw\n * with actionable guidance, rather than shipping `{ '~standard': … }` to the\n * provider and producing an opaque downstream error.\n */\nfunction toTypedJsonSchema(schema: SchemaInput): JSONSchema | undefined {\n if (isStandardJSONSchema(schema)) {\n const jsonSchema = schema['~standard'].jsonSchema.input({\n target: 'draft-07',\n })\n const result: JSONSchema = toJsonSchema(jsonSchema)\n if ('properties' in result && !result.type) result.type = 'object'\n if (result.type === 'object' && !('properties' in result)) {\n result.properties = {}\n }\n if (result.type === 'object' && !('required' in result)) {\n result.required = []\n }\n return result\n }\n\n if (isStandardSchema(schema)) {\n throw new Error(\n 'Schema is a Standard Schema validator but does not expose a JSON Schema ' +\n 'converter on `~standard.jsonSchema`. Use Zod v4.2+, ArkType v2.1.28+, ' +\n 'or wrap a Valibot schema with `toStandardJsonSchema()` from ' +\n '`@valibot/to-json-schema` before passing it as `outputSchema`.',\n )\n }\n\n if (typeof schema !== 'object') return schema\n return toJsonSchema(schema)\n}\n\n/**\n * Converts a Standard JSON Schema compliant schema or plain JSONSchema to JSON Schema format\n * compatible with LLM providers.\n *\n * Supports any schema library that implements the Standard JSON Schema spec (v1):\n * - Zod v4+ (natively supports StandardJSONSchemaV1)\n * - ArkType (natively supports StandardJSONSchemaV1)\n * - Valibot (via `toStandardJsonSchema()` from `@valibot/to-json-schema`)\n *\n * If the input is already a plain JSONSchema object, it is returned as-is.\n *\n * @param schema - Standard JSON Schema compliant schema or plain JSONSchema object to convert\n * @param options - Conversion options\n * @returns JSON Schema object that can be sent to LLM providers\n *\n * @example\n * ```typescript\n * // Using Zod v4+ (natively supports Standard JSON Schema)\n * import * as z from 'zod';\n *\n * const zodSchema = z.object({\n * location: z.string().describe('City name'),\n * unit: z.enum(['celsius', 'fahrenheit']).optional()\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(zodSchema);\n *\n * @example\n * // Using ArkType (natively supports Standard JSON Schema)\n * import { type } from 'arktype';\n *\n * const arkSchema = type({\n * location: 'string',\n * unit: \"'celsius' | 'fahrenheit'\"\n * });\n *\n * const jsonSchema = convertSchemaToJsonSchema(arkSchema);\n *\n * @example\n * // Using Valibot (via toStandardJsonSchema)\n * import * as v from 'valibot';\n * import { toStandardJsonSchema } from '@valibot/to-json-schema';\n *\n * const valibotSchema = toStandardJsonSchema(v.object({\n * location: v.string(),\n * unit: v.optional(v.picklist(['celsius', 'fahrenheit']))\n * }));\n *\n * const jsonSchema = convertSchemaToJsonSchema(valibotSchema);\n *\n * @example\n * // Using JSONSchema directly (passes through unchanged)\n * const rawSchema = {\n * type: 'object',\n * properties: { location: { type: 'string' } },\n * required: ['location']\n * };\n * const result = convertSchemaToJsonSchema(rawSchema);\n * ```\n */\nexport function convertSchemaToJsonSchema(\n schema: SchemaInput | undefined,\n options: ConvertSchemaOptions = {},\n): JSONSchema | undefined {\n if (!schema) return undefined\n\n const { forStructuredOutput = false } = options\n\n // Plain-JSONSchema passthrough: with no widening requested, return the schema\n // by reference so callers comparing via `===` keep identity. Only the widening\n // path needs the rebuilt, normalized view from `toTypedJsonSchema`.\n if (\n !forStructuredOutput &&\n !isStandardJSONSchema(schema) &&\n !isStandardSchema(schema)\n ) {\n return schema\n }\n\n const base = toTypedJsonSchema(schema)\n // Non-object inputs can't be widened; surface them untouched.\n if (!base || typeof base !== 'object') return base\n if (!forStructuredOutput) return base\n return makeStructuredOutputCompatible(base, base.required || []).schema\n}\n\n/**\n * Convert a schema for structured output AND capture the {@link NullWideningMap}\n * recording every `null` the strict-mode widening synthesized. The map lets the\n * caller undo that widening on the provider's response (via `undoNullWidening`)\n * before validating against the original schema — optional fields read back as\n * absent while genuine `.nullable()` nulls survive. The map is `undefined` when\n * the schema isn't a widenable object or when no field needed widening.\n */\nexport function convertSchemaForStructuredOutput(\n schema: SchemaInput | undefined,\n): {\n jsonSchema: JSONSchema | undefined\n nullWideningMap: NullWideningMap | undefined\n} {\n if (!schema) return { jsonSchema: undefined, nullWideningMap: undefined }\n const base = toTypedJsonSchema(schema)\n if (!base || typeof base !== 'object') {\n return { jsonSchema: base, nullWideningMap: undefined }\n }\n const { schema: jsonSchema, nullWidening } = makeStructuredOutputCompatible(\n base,\n base.required || [],\n )\n return { jsonSchema, nullWideningMap: nullWidening }\n}\n\n/**\n * Validates data against a Standard Schema compliant schema.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Validation result with success status, data or issues\n */\nexport async function validateWithStandardSchema<T>(\n schema: unknown,\n data: unknown,\n): Promise<\n | { success: true; data: T }\n | {\n success: false\n issues: Array<{ message: string; path?: Array<string> | undefined }>\n }\n> {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return { success: true, data: data as T }\n }\n\n const result = await schema['~standard'].validate(data)\n\n if (!result.issues) {\n return { success: true, data: result.value as T }\n }\n\n return {\n success: false,\n issues: result.issues.map((issue) => ({\n message: issue.message || 'Validation failed',\n path: issue.path?.map(String),\n })),\n }\n}\n\n/**\n * Error thrown when Standard Schema validation fails. Carries the original\n * `issues` array so consumers (middleware `onError`, callers catching from\n * `chat({ outputSchema })`) can programmatically inspect each failure.\n */\nexport class StandardSchemaValidationError extends Error {\n override readonly name = 'StandardSchemaValidationError'\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Validation failed: ${issues\n .map((i) => i.message || 'Validation failed')\n .join(', ')}`,\n )\n this.issues = issues\n }\n}\n\n/**\n * Synchronously validates data against a Standard Schema compliant schema.\n * Note: Some Standard Schema implementations may only support async validation.\n * In those cases, this function will throw.\n *\n * @param schema - Standard Schema compliant schema\n * @param data - Data to validate\n * @returns Parsed/validated data\n * @throws StandardSchemaValidationError if validation fails; Error if the\n * schema only supports async validation.\n */\nexport function parseWithStandardSchema<T>(schema: unknown, data: unknown): T {\n if (!isStandardSchema(schema)) {\n // If it's not a Standard Schema, just return the data as-is\n return data as T\n }\n\n const result = schema['~standard'].validate(data)\n\n // Handle async result (Promise)\n if (result instanceof Promise) {\n throw new Error(\n 'Schema validation returned a Promise. Use validateWithStandardSchema for async validation.',\n )\n }\n // Standard Schema validation returns { value } for success or { issues } for failure\n if (!result.issues) {\n return result.value as T\n }\n\n throw new StandardSchemaValidationError(result.issues)\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { DebugOption } from '../../logger/types.js';
2
+ import { GenerationMiddleware } from '../middleware.js';
2
3
  import { AudioAdapter } from './adapter.js';
3
4
  import { AudioGenerationResult, StreamChunk } from '../../types.js';
4
5
  /** The adapter kind this activity handles */
@@ -43,6 +44,12 @@ export interface AudioActivityOptions<TAdapter extends AudioAdapter<string, Audi
43
44
  * control and/or a custom `Logger`.
44
45
  */
45
46
  debug?: DebugOption;
47
+ /**
48
+ * Observe-only middleware notified on start, usage, success, and error. Pass
49
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
50
+ * `GenerationMiddleware` contract for a custom backend.
51
+ */
52
+ middleware?: Array<GenerationMiddleware>;
46
53
  }
47
54
  /**
48
55
  * Result type for the audio generation activity.
@@ -1,6 +1,7 @@
1
1
  import { aiEventClient } from "@tanstack/ai-event-client";
2
2
  import { streamGenerationResult } from "../stream-generation-result.js";
3
3
  import { resolveDebugOption } from "../../logger/resolve.js";
4
+ import { createGenerationContext, runGenerationStart, runGenerationUsage, runGenerationFinish, runGenerationError } from "../middleware/run.js";
4
5
  const kind = "audio";
5
6
  function createId(prefix) {
6
7
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
@@ -14,12 +15,27 @@ function generateAudio(options) {
14
15
  return runGenerateAudio(options);
15
16
  }
16
17
  async function runGenerateAudio(options) {
17
- const { adapter, stream: _stream, debug: _debug, ...rest } = options;
18
+ const {
19
+ adapter,
20
+ stream: _stream,
21
+ debug: _debug,
22
+ middleware,
23
+ ...rest
24
+ } = options;
18
25
  const model = adapter.model;
19
26
  const requestId = createId("audio");
20
27
  const startTime = Date.now();
21
28
  const logger = resolveDebugOption(options.debug);
22
29
  const providerName = adapter.provider ?? adapter.name ?? "unknown";
30
+ const mwCtx = createGenerationContext({
31
+ requestId,
32
+ activity: "audio",
33
+ provider: adapter.name,
34
+ model,
35
+ modelOptions: rest.modelOptions,
36
+ createId
37
+ });
38
+ await runGenerationStart(middleware, mwCtx);
23
39
  aiEventClient.emit("audio:request:started", {
24
40
  requestId,
25
41
  provider: adapter.name,
@@ -58,6 +74,11 @@ async function runGenerateAudio(options) {
58
74
  contentType: result.audio.contentType,
59
75
  audioDuration: result.audio.duration
60
76
  });
77
+ if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage);
78
+ await runGenerationFinish(middleware, mwCtx, {
79
+ duration: elapsedMs,
80
+ usage: result.usage
81
+ });
61
82
  return result;
62
83
  } catch (error) {
63
84
  const elapsedMs = Date.now() - startTime;
@@ -71,6 +92,10 @@ async function runGenerateAudio(options) {
71
92
  modelOptions: rest.modelOptions,
72
93
  timestamp: Date.now()
73
94
  });
95
+ await runGenerationError(middleware, mwCtx, {
96
+ error,
97
+ duration: elapsedMs
98
+ });
74
99
  logger.errors("generateAudio activity failed", {
75
100
  error,
76
101
  source: "generateAudio"
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/activities/generateAudio/index.ts"],"sourcesContent":["/**\n * Audio Generation Activity\n *\n * Generates audio (music, sound effects, etc.) from text prompts.\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 { AudioAdapter } from './adapter'\nimport type { AudioGenerationResult, StreamChunk } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'audio' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from an AudioAdapter via ~types.\n */\nexport type AudioProviderOptions<TAdapter> = TAdapter extends {\n '~types': { providerOptions: infer P extends object }\n}\n ? P\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the audio generation activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The audio adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface AudioActivityOptions<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n> {\n /** The audio adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Provider-specific options for audio generation */\n modelOptions?: AudioProviderOptions<TAdapter>\n /**\n * Whether to stream the generation result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming transport.\n * When false or not provided, returns a Promise<AudioGenerationResult>.\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 audio generation activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<AudioGenerationResult>\n */\nexport type AudioActivityResult<TStream extends boolean = false> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<AudioGenerationResult>\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 * Audio generation activity - generates audio from text prompts.\n *\n * Uses AI models to create music, sound effects, and other audio content.\n *\n * @example Generate music from a prompt\n * ```ts\n * import { generateAudio } from '@tanstack/ai'\n * import { falAudio } from '@tanstack/ai-fal'\n *\n * const result = await generateAudio({\n * adapter: falAudio('fal-ai/diffrhythm'),\n * prompt: 'An upbeat electronic track with synths',\n * duration: 10\n * })\n *\n * console.log(result.audio.url) // URL to generated audio\n * ```\n */\nexport function generateAudio<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n>(\n options: AudioActivityOptions<TAdapter, TStream>,\n): AudioActivityResult<TStream> {\n if (options.stream) {\n return streamGenerationResult(() =>\n runGenerateAudio(options),\n ) as AudioActivityResult<TStream>\n }\n return runGenerateAudio(options) as AudioActivityResult<TStream>\n}\n\n/**\n * Run the core audio generation logic (non-streaming).\n */\nasync function runGenerateAudio<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n>(\n options: AudioActivityOptions<TAdapter, boolean>,\n): Promise<AudioGenerationResult> {\n const { adapter, stream: _stream, debug: _debug, ...rest } = options\n const model = adapter.model\n const requestId = createId('audio')\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n aiEventClient.emit('audio:request:started', {\n requestId,\n provider: adapter.name,\n model,\n prompt: rest.prompt,\n duration: rest.duration,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: startTime,\n })\n\n logger.request(`activity=generateAudio provider=${providerName}`, {\n provider: providerName,\n model,\n })\n\n try {\n const result = await adapter.generateAudio({ ...rest, model, logger })\n const elapsedMs = Date.now() - startTime\n\n aiEventClient.emit('audio:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n audio: result.audio,\n duration: elapsedMs,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n\n if (result.usage) {\n aiEventClient.emit('audio:usage', {\n requestId,\n model,\n usage: result.usage,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n }\n\n logger.output(`activity=generateAudio provider=${providerName}`, {\n contentType: result.audio.contentType,\n audioDuration: result.audio.duration,\n })\n\n return result\n } catch (error) {\n const elapsedMs = Date.now() - startTime\n const err = error as Error\n aiEventClient.emit('audio:request:error', {\n requestId,\n provider: adapter.name,\n model,\n error: { message: err.message, name: err.name },\n duration: elapsedMs,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n logger.errors('generateAudio activity failed', {\n error,\n source: 'generateAudio',\n })\n throw error\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateAudio() function without executing.\n */\nexport function createAudioOptions<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n>(\n options: AudioActivityOptions<TAdapter, TStream>,\n): AudioActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n AudioAdapter,\n AudioAdapterConfig,\n AnyAudioAdapter,\n} from './adapter'\nexport { BaseAudioAdapter } from './adapter'\n"],"names":[],"mappings":";;;AAoBO,MAAM,OAAO;AAoEpB,SAAS,SAAS,QAAwB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAyBO,SAAS,cAId,SAC8B;AAC9B,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MAAuB,MAC5B,iBAAiB,OAAO;AAAA,IAAA;AAAA,EAE5B;AACA,SAAO,iBAAiB,OAAO;AACjC;AAKA,eAAe,iBAGb,SACgC;AAChC,QAAM,EAAE,SAAS,QAAQ,SAAS,OAAO,QAAQ,GAAG,SAAS;AAC7D,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAY,SAAS,OAAO;AAClC,QAAM,YAAY,KAAK,IAAA;AACvB,QAAM,SAAyB,mBAAmB,QAAQ,KAAK;AAC/D,QAAM,eACH,QAAiD,YACjD,QAA8B,QAC/B;AAEF,gBAAc,KAAK,yBAAyB;AAAA,IAC1C;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,cAAc,KAAK;AAAA,IACnB,WAAW;AAAA,EAAA,CACZ;AAED,SAAO,QAAQ,mCAAmC,YAAY,IAAI;AAAA,IAChE,UAAU;AAAA,IACV;AAAA,EAAA,CACD;AAED,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,cAAc,EAAE,GAAG,MAAM,OAAO,QAAQ;AACrE,UAAM,YAAY,KAAK,IAAA,IAAQ;AAE/B,kBAAc,KAAK,2BAA2B;AAAA,MAC5C;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,OAAO,OAAO;AAAA,MACd,UAAU;AAAA,MACV,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AAED,QAAI,OAAO,OAAO;AAChB,oBAAc,KAAK,eAAe;AAAA,QAChC;AAAA,QACA;AAAA,QACA,OAAO,OAAO;AAAA,QACd,cAAc,KAAK;AAAA,QACnB,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB;AAAA,IACH;AAEA,WAAO,OAAO,mCAAmC,YAAY,IAAI;AAAA,MAC/D,aAAa,OAAO,MAAM;AAAA,MAC1B,eAAe,OAAO,MAAM;AAAA,IAAA,CAC7B;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,YAAY,KAAK,IAAA,IAAQ;AAC/B,UAAM,MAAM;AACZ,kBAAc,KAAK,uBAAuB;AAAA,MACxC;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB;AAAA,MACA,OAAO,EAAE,SAAS,IAAI,SAAS,MAAM,IAAI,KAAA;AAAA,MACzC,UAAU;AAAA,MACV,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,IAAA;AAAA,IAAI,CACrB;AACD,WAAO,OAAO,iCAAiC;AAAA,MAC7C;AAAA,MACA,QAAQ;AAAA,IAAA,CACT;AACD,UAAM;AAAA,EACR;AACF;AASO,SAAS,mBAId,SACyC;AACzC,SAAO;AACT;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/activities/generateAudio/index.ts"],"sourcesContent":["/**\n * Audio Generation Activity\n *\n * Generates audio (music, sound effects, etc.) from text prompts.\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 {\n createGenerationContext,\n runGenerationError,\n runGenerationFinish,\n runGenerationStart,\n runGenerationUsage,\n} from '../middleware'\nimport type { InternalLogger } from '../../logger/internal-logger'\nimport type { DebugOption } from '../../logger/types'\nimport type { GenerationMiddleware } from '../middleware'\nimport type { AudioAdapter } from './adapter'\nimport type { AudioGenerationResult, StreamChunk } from '../../types'\n\n// ===========================\n// Activity Kind\n// ===========================\n\n/** The adapter kind this activity handles */\nexport const kind = 'audio' as const\n\n// ===========================\n// Type Extraction Helpers\n// ===========================\n\n/**\n * Extract provider options from an AudioAdapter via ~types.\n */\nexport type AudioProviderOptions<TAdapter> = TAdapter extends {\n '~types': { providerOptions: infer P extends object }\n}\n ? P\n : object\n\n// ===========================\n// Activity Options Type\n// ===========================\n\n/**\n * Options for the audio generation activity.\n * The model is extracted from the adapter's model property.\n *\n * @template TAdapter - The audio adapter type\n * @template TStream - Whether to stream the output\n */\nexport interface AudioActivityOptions<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n> {\n /** The audio adapter to use (must be created with a model) */\n adapter: TAdapter & { kind: typeof kind }\n /** Text description of the desired audio */\n prompt: string\n /** Desired duration in seconds */\n duration?: number\n /** Provider-specific options for audio generation */\n modelOptions?: AudioProviderOptions<TAdapter>\n /**\n * Whether to stream the generation result.\n * When true, returns an AsyncIterable<StreamChunk> for streaming transport.\n * When false or not provided, returns a Promise<AudioGenerationResult>.\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 * Observe-only middleware notified on start, usage, success, and error. Pass\n * `otelMiddleware()` to emit OpenTelemetry spans, or implement the\n * `GenerationMiddleware` contract for a custom backend.\n */\n middleware?: Array<GenerationMiddleware>\n}\n\n// ===========================\n// Activity Result Type\n// ===========================\n\n/**\n * Result type for the audio generation activity.\n * - If stream is true: AsyncIterable<StreamChunk>\n * - Otherwise: Promise<AudioGenerationResult>\n */\nexport type AudioActivityResult<TStream extends boolean = false> =\n TStream extends true\n ? AsyncIterable<StreamChunk>\n : Promise<AudioGenerationResult>\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 * Audio generation activity - generates audio from text prompts.\n *\n * Uses AI models to create music, sound effects, and other audio content.\n *\n * @example Generate music from a prompt\n * ```ts\n * import { generateAudio } from '@tanstack/ai'\n * import { falAudio } from '@tanstack/ai-fal'\n *\n * const result = await generateAudio({\n * adapter: falAudio('fal-ai/diffrhythm'),\n * prompt: 'An upbeat electronic track with synths',\n * duration: 10\n * })\n *\n * console.log(result.audio.url) // URL to generated audio\n * ```\n */\nexport function generateAudio<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n>(\n options: AudioActivityOptions<TAdapter, TStream>,\n): AudioActivityResult<TStream> {\n if (options.stream) {\n return streamGenerationResult(() =>\n runGenerateAudio(options),\n ) as AudioActivityResult<TStream>\n }\n return runGenerateAudio(options) as AudioActivityResult<TStream>\n}\n\n/**\n * Run the core audio generation logic (non-streaming).\n */\nasync function runGenerateAudio<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n>(\n options: AudioActivityOptions<TAdapter, boolean>,\n): Promise<AudioGenerationResult> {\n const {\n adapter,\n stream: _stream,\n debug: _debug,\n middleware,\n ...rest\n } = options\n const model = adapter.model\n const requestId = createId('audio')\n const startTime = Date.now()\n const logger: InternalLogger = resolveDebugOption(options.debug)\n const providerName =\n (adapter as { name?: string; provider?: string }).provider ??\n (adapter as { name?: string }).name ??\n 'unknown'\n\n const mwCtx = createGenerationContext({\n requestId,\n activity: 'audio',\n provider: adapter.name,\n model,\n modelOptions: rest.modelOptions,\n createId,\n })\n\n await runGenerationStart(middleware, mwCtx)\n\n aiEventClient.emit('audio:request:started', {\n requestId,\n provider: adapter.name,\n model,\n prompt: rest.prompt,\n duration: rest.duration,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: startTime,\n })\n\n logger.request(`activity=generateAudio provider=${providerName}`, {\n provider: providerName,\n model,\n })\n\n try {\n const result = await adapter.generateAudio({ ...rest, model, logger })\n const elapsedMs = Date.now() - startTime\n\n aiEventClient.emit('audio:request:completed', {\n requestId,\n provider: adapter.name,\n model,\n audio: result.audio,\n duration: elapsedMs,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n\n if (result.usage) {\n aiEventClient.emit('audio:usage', {\n requestId,\n model,\n usage: result.usage,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n }\n\n logger.output(`activity=generateAudio provider=${providerName}`, {\n contentType: result.audio.contentType,\n audioDuration: result.audio.duration,\n })\n\n if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage)\n await runGenerationFinish(middleware, mwCtx, {\n duration: elapsedMs,\n usage: result.usage,\n })\n\n return result\n } catch (error) {\n const elapsedMs = Date.now() - startTime\n const err = error as Error\n aiEventClient.emit('audio:request:error', {\n requestId,\n provider: adapter.name,\n model,\n error: { message: err.message, name: err.name },\n duration: elapsedMs,\n modelOptions: rest.modelOptions as Record<string, unknown> | undefined,\n timestamp: Date.now(),\n })\n await runGenerationError(middleware, mwCtx, {\n error,\n duration: elapsedMs,\n })\n logger.errors('generateAudio activity failed', {\n error,\n source: 'generateAudio',\n })\n throw error\n }\n}\n\n// ===========================\n// Options Factory\n// ===========================\n\n/**\n * Create typed options for the generateAudio() function without executing.\n */\nexport function createAudioOptions<\n TAdapter extends AudioAdapter<string, AudioProviderOptions<TAdapter>>,\n TStream extends boolean = false,\n>(\n options: AudioActivityOptions<TAdapter, TStream>,\n): AudioActivityOptions<TAdapter, TStream> {\n return options\n}\n\n// Re-export adapter types\nexport type {\n AudioAdapter,\n AudioAdapterConfig,\n AnyAudioAdapter,\n} from './adapter'\nexport { BaseAudioAdapter } from './adapter'\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { DebugOption } from '../../logger/types.js';
2
+ import { GenerationMiddleware } from '../middleware.js';
2
3
  import { ImageAdapter } from './adapter.js';
3
4
  import { ImageGenerationResult, MediaPrompt, MediaPromptFor, StreamChunk } from '../../types.js';
4
5
  /** The adapter kind this activity handles */
@@ -62,6 +63,12 @@ export type ImageActivityOptions<TAdapter extends ImageAdapter<string, any, any,
62
63
  * control and/or a custom `Logger`.
63
64
  */
64
65
  debug?: DebugOption;
66
+ /**
67
+ * Observe-only middleware notified on start, usage, success, and error. Pass
68
+ * `otelMiddleware()` to emit OpenTelemetry spans, or implement the
69
+ * `GenerationMiddleware` contract for a custom backend.
70
+ */
71
+ middleware?: Array<GenerationMiddleware>;
65
72
  } & ({} extends ImageProviderOptionsForModel<TAdapter, TAdapter['model']> ? {
66
73
  /** Provider-specific options for image generation */ modelOptions?: ImageProviderOptionsForModel<TAdapter, TAdapter['model']>;
67
74
  } : {