@tambo-ai/react 1.0.0 → 1.0.2

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 (56) hide show
  1. package/README.md +42 -20
  2. package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  3. package/dist/v1/hooks/use-tambo-v1-send-message.js +4 -31
  4. package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  5. package/dist/v1/hooks/use-tambo-v1.js +2 -2
  6. package/dist/v1/hooks/use-tambo-v1.js.map +1 -1
  7. package/dist/v1/hooks/use-tambo-v1.test.js +2 -0
  8. package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -1
  9. package/dist/v1/utils/event-accumulator.d.ts +3 -0
  10. package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
  11. package/dist/v1/utils/event-accumulator.js +26 -5
  12. package/dist/v1/utils/event-accumulator.js.map +1 -1
  13. package/dist/v1/utils/event-accumulator.test.js +113 -0
  14. package/dist/v1/utils/event-accumulator.test.js.map +1 -1
  15. package/dist/v1/utils/tool-call-tracker.d.ts +26 -4
  16. package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -1
  17. package/dist/v1/utils/tool-call-tracker.js +82 -5
  18. package/dist/v1/utils/tool-call-tracker.js.map +1 -1
  19. package/dist/v1/utils/tool-call-tracker.test.js +178 -0
  20. package/dist/v1/utils/tool-call-tracker.test.js.map +1 -1
  21. package/dist/v1/utils/unstrictify.d.ts +32 -0
  22. package/dist/v1/utils/unstrictify.d.ts.map +1 -0
  23. package/dist/v1/utils/unstrictify.js +159 -0
  24. package/dist/v1/utils/unstrictify.js.map +1 -0
  25. package/dist/v1/utils/unstrictify.test.d.ts +2 -0
  26. package/dist/v1/utils/unstrictify.test.d.ts.map +1 -0
  27. package/dist/v1/utils/unstrictify.test.js +187 -0
  28. package/dist/v1/utils/unstrictify.test.js.map +1 -0
  29. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  30. package/esm/v1/hooks/use-tambo-v1-send-message.js +4 -31
  31. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  32. package/esm/v1/hooks/use-tambo-v1.js +2 -2
  33. package/esm/v1/hooks/use-tambo-v1.js.map +1 -1
  34. package/esm/v1/hooks/use-tambo-v1.test.js +2 -0
  35. package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -1
  36. package/esm/v1/utils/event-accumulator.d.ts +3 -0
  37. package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
  38. package/esm/v1/utils/event-accumulator.js +26 -5
  39. package/esm/v1/utils/event-accumulator.js.map +1 -1
  40. package/esm/v1/utils/event-accumulator.test.js +113 -0
  41. package/esm/v1/utils/event-accumulator.test.js.map +1 -1
  42. package/esm/v1/utils/tool-call-tracker.d.ts +26 -4
  43. package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -1
  44. package/esm/v1/utils/tool-call-tracker.js +82 -5
  45. package/esm/v1/utils/tool-call-tracker.js.map +1 -1
  46. package/esm/v1/utils/tool-call-tracker.test.js +178 -0
  47. package/esm/v1/utils/tool-call-tracker.test.js.map +1 -1
  48. package/esm/v1/utils/unstrictify.d.ts +32 -0
  49. package/esm/v1/utils/unstrictify.d.ts.map +1 -0
  50. package/esm/v1/utils/unstrictify.js +155 -0
  51. package/esm/v1/utils/unstrictify.js.map +1 -0
  52. package/esm/v1/utils/unstrictify.test.d.ts +2 -0
  53. package/esm/v1/utils/unstrictify.test.d.ts.map +1 -0
  54. package/esm/v1/utils/unstrictify.test.js +185 -0
  55. package/esm/v1/utils/unstrictify.test.js.map +1 -0
  56. package/package.json +3 -3
@@ -2,9 +2,12 @@
2
2
  * Tool Call Tracker
3
3
  *
4
4
  * Tracks tool calls during streaming, accumulating arguments until complete.
5
- * Used by the send message hook to collect tool call state for execution.
5
+ * Owns the tool name JSON Schema mapping and handles unstrictification
6
+ * so callers don't need to know about schema conversion.
6
7
  */
7
8
  import { type AGUIEvent } from "@ag-ui/core";
9
+ import type { JSONSchema7 } from "json-schema";
10
+ import type { TamboTool } from "../../model/component-metadata";
8
11
  import type { PendingToolCall } from "./tool-executor";
9
12
  /**
10
13
  * Tracks tool calls during streaming, accumulating arguments until complete.
@@ -14,21 +17,35 @@ import type { PendingToolCall } from "./tool-executor";
14
17
  * 2. TOOL_CALL_ARGS (multiple) - streams JSON argument fragments
15
18
  * 3. TOOL_CALL_END - marks the tool call as complete, triggers JSON parsing
16
19
  *
17
- * This class accumulates these events and provides the complete tool call
18
- * data when requested for execution.
20
+ * When constructed with a tool registry, the tracker unstrictifies parsed
21
+ * args (both partial and final) using the original JSON Schemas.
19
22
  */
20
23
  export declare class ToolCallTracker {
21
24
  private pendingToolCalls;
22
25
  private accumulatingArgs;
26
+ private _toolSchemas;
27
+ constructor(toolRegistry?: Record<string, TamboTool>);
28
+ /**
29
+ * The tool-name → JSONSchema7 map, for passing to the reducer.
30
+ * @returns The tool schemas map
31
+ */
32
+ get toolSchemas(): Map<string, JSONSchema7>;
23
33
  /**
24
34
  * Handles a streaming event, tracking tool call state as needed.
25
35
  * @param event - The streaming event to process
26
36
  * @throws {Error} If JSON parsing fails on TOOL_CALL_END (fail-fast, no silent fallback)
27
37
  */
28
38
  handleEvent(event: AGUIEvent): void;
39
+ /**
40
+ * Parses partial JSON from the accumulated args for a tool call and
41
+ * unstrictifies the result. Used during streaming to get the current
42
+ * best-effort parsed args.
43
+ * @param toolCallId - ID of the tool call to parse
44
+ * @returns Parsed and unstrictified args, or undefined if not parseable yet
45
+ */
46
+ parsePartialArgs(toolCallId: string): Record<string, unknown> | undefined;
29
47
  /**
30
48
  * Gets the name and accumulated args for a tool call that is still accumulating.
31
- * Used by the event loop to get tool state for partial JSON parsing.
32
49
  * @param toolCallId - ID of the tool call to look up
33
50
  * @returns The tool name and raw accumulated args string, or undefined if not found
34
51
  */
@@ -47,5 +64,10 @@ export declare class ToolCallTracker {
47
64
  * @param toolCallIds - IDs of tool calls to clear
48
65
  */
49
66
  clearToolCalls(toolCallIds: string[]): void;
67
+ /**
68
+ * Unstrictify params using the schema for the given tool name.
69
+ * Returns params unchanged if no schema is available.
70
+ */
71
+ private unstrictify;
50
72
  }
51
73
  //# sourceMappingURL=tool-call-tracker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-tracker.d.ts","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,gBAAgB,CAA6B;IAErD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAyCnC;;;;;OAKG;IACH,uBAAuB,CACrB,UAAU,EAAE,MAAM,GACjB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAOxD;;;;OAIG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;IAWrE;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI;CAM5C"}
1
+ {"version":3,"file":"tool-call-tracker.d.ts","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AA4BvD;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,YAAY,CAA2B;gBAEnC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IAMpD;;;OAGG;IACH,IAAI,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAE1C;IAED;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA6CnC;;;;;;OAMG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAsBzE;;;;OAIG;IACH,uBAAuB,CACrB,UAAU,EAAE,MAAM,GACjB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAOxD;;;;OAIG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;IAWrE;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI;IAO3C;;;OAGG;IACH,OAAO,CAAC,WAAW;CAQpB"}
@@ -3,11 +3,39 @@
3
3
  * Tool Call Tracker
4
4
  *
5
5
  * Tracks tool calls during streaming, accumulating arguments until complete.
6
- * Used by the send message hook to collect tool call state for execution.
6
+ * Owns the tool name JSON Schema mapping and handles unstrictification
7
+ * so callers don't need to know about schema conversion.
7
8
  */
8
9
  Object.defineProperty(exports, "__esModule", { value: true });
9
10
  exports.ToolCallTracker = void 0;
10
11
  const core_1 = require("@ag-ui/core");
12
+ const partial_json_1 = require("partial-json");
13
+ const schema_1 = require("../../schema/schema");
14
+ const unstrictify_1 = require("./unstrictify");
15
+ /**
16
+ * Build a tool-name → JSONSchema7 map from the tool registry.
17
+ * Handles both modern `inputSchema` and deprecated `toolSchema` formats.
18
+ * Tools whose schema can't be converted are silently skipped.
19
+ * @param toolRegistry - Record of tool name → tool definition
20
+ * @returns Map of tool name → JSON Schema
21
+ */
22
+ function buildToolSchemas(toolRegistry) {
23
+ const schemas = new Map();
24
+ for (const tool of Object.values(toolRegistry)) {
25
+ try {
26
+ if ("inputSchema" in tool && tool.inputSchema) {
27
+ schemas.set(tool.name, (0, schema_1.schemaToJsonSchema)(tool.inputSchema));
28
+ }
29
+ else if ("toolSchema" in tool && tool.toolSchema) {
30
+ schemas.set(tool.name, (0, schema_1.schemaToJsonSchema)(tool.toolSchema));
31
+ }
32
+ }
33
+ catch {
34
+ // Schema conversion failed — tool still works, just without unstrictification
35
+ }
36
+ }
37
+ return schemas;
38
+ }
11
39
  /**
12
40
  * Tracks tool calls during streaming, accumulating arguments until complete.
13
41
  *
@@ -16,12 +44,25 @@ const core_1 = require("@ag-ui/core");
16
44
  * 2. TOOL_CALL_ARGS (multiple) - streams JSON argument fragments
17
45
  * 3. TOOL_CALL_END - marks the tool call as complete, triggers JSON parsing
18
46
  *
19
- * This class accumulates these events and provides the complete tool call
20
- * data when requested for execution.
47
+ * When constructed with a tool registry, the tracker unstrictifies parsed
48
+ * args (both partial and final) using the original JSON Schemas.
21
49
  */
22
50
  class ToolCallTracker {
23
51
  pendingToolCalls = new Map();
24
52
  accumulatingArgs = new Map();
53
+ _toolSchemas;
54
+ constructor(toolRegistry) {
55
+ this._toolSchemas = toolRegistry
56
+ ? buildToolSchemas(toolRegistry)
57
+ : new Map();
58
+ }
59
+ /**
60
+ * The tool-name → JSONSchema7 map, for passing to the reducer.
61
+ * @returns The tool schemas map
62
+ */
63
+ get toolSchemas() {
64
+ return this._toolSchemas;
65
+ }
25
66
  /**
26
67
  * Handles a streaming event, tracking tool call state as needed.
27
68
  * @param event - The streaming event to process
@@ -45,13 +86,16 @@ class ToolCallTracker {
45
86
  const jsonStr = this.accumulatingArgs.get(event.toolCallId);
46
87
  const toolCall = this.pendingToolCalls.get(event.toolCallId);
47
88
  if (toolCall && jsonStr) {
89
+ let parsedInput;
48
90
  try {
49
- toolCall.input = JSON.parse(jsonStr);
91
+ parsedInput = JSON.parse(jsonStr);
50
92
  }
51
93
  catch (error) {
52
94
  // Fail-fast: don't silently continue with empty input
53
95
  throw new Error(`Failed to parse tool call arguments for ${event.toolCallId}: ${error instanceof Error ? error.message : "Unknown error"}. JSON: ${jsonStr.slice(0, 100)}${jsonStr.length > 100 ? "..." : ""}`);
54
96
  }
97
+ parsedInput = this.unstrictify(toolCall.name, parsedInput);
98
+ toolCall.input = parsedInput;
55
99
  }
56
100
  break;
57
101
  }
@@ -60,9 +104,32 @@ class ToolCallTracker {
60
104
  break;
61
105
  }
62
106
  }
107
+ /**
108
+ * Parses partial JSON from the accumulated args for a tool call and
109
+ * unstrictifies the result. Used during streaming to get the current
110
+ * best-effort parsed args.
111
+ * @param toolCallId - ID of the tool call to parse
112
+ * @returns Parsed and unstrictified args, or undefined if not parseable yet
113
+ */
114
+ parsePartialArgs(toolCallId) {
115
+ const accToolCall = this.getAccumulatingToolCall(toolCallId);
116
+ if (!accToolCall)
117
+ return undefined;
118
+ try {
119
+ const parsed = (0, partial_json_1.parse)(accToolCall.accumulatedArgs);
120
+ if (typeof parsed === "object" &&
121
+ parsed !== null &&
122
+ !Array.isArray(parsed)) {
123
+ return this.unstrictify(accToolCall.name, parsed);
124
+ }
125
+ }
126
+ catch {
127
+ /* not parseable yet */
128
+ }
129
+ return undefined;
130
+ }
63
131
  /**
64
132
  * Gets the name and accumulated args for a tool call that is still accumulating.
65
- * Used by the event loop to get tool state for partial JSON parsing.
66
133
  * @param toolCallId - ID of the tool call to look up
67
134
  * @returns The tool name and raw accumulated args string, or undefined if not found
68
135
  */
@@ -98,6 +165,16 @@ class ToolCallTracker {
98
165
  this.accumulatingArgs.delete(id);
99
166
  }
100
167
  }
168
+ /**
169
+ * Unstrictify params using the schema for the given tool name.
170
+ * Returns params unchanged if no schema is available.
171
+ */
172
+ unstrictify(toolName, params) {
173
+ const schema = this._toolSchemas.get(toolName);
174
+ if (!schema)
175
+ return params;
176
+ return (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, params);
177
+ }
101
178
  }
102
179
  exports.ToolCallTracker = ToolCallTracker;
103
180
  //# sourceMappingURL=tool-call-tracker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-tracker.js","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Call Tracker\n *\n * Tracks tool calls during streaming, accumulating arguments until complete.\n * Used by the send message hook to collect tool call state for execution.\n */\n\nimport { EventType, type AGUIEvent } from \"@ag-ui/core\";\nimport type { PendingToolCall } from \"./tool-executor\";\n\n/**\n * Tracks tool calls during streaming, accumulating arguments until complete.\n *\n * Tool calls arrive as a sequence of events:\n * 1. TOOL_CALL_START - initializes the tool call with name\n * 2. TOOL_CALL_ARGS (multiple) - streams JSON argument fragments\n * 3. TOOL_CALL_END - marks the tool call as complete, triggers JSON parsing\n *\n * This class accumulates these events and provides the complete tool call\n * data when requested for execution.\n */\nexport class ToolCallTracker {\n private pendingToolCalls = new Map<string, PendingToolCall>();\n private accumulatingArgs = new Map<string, string>();\n\n /**\n * Handles a streaming event, tracking tool call state as needed.\n * @param event - The streaming event to process\n * @throws {Error} If JSON parsing fails on TOOL_CALL_END (fail-fast, no silent fallback)\n */\n handleEvent(event: AGUIEvent): void {\n switch (event.type) {\n case EventType.TOOL_CALL_START:\n this.pendingToolCalls.set(event.toolCallId, {\n name: event.toolCallName,\n input: {},\n });\n this.accumulatingArgs.set(event.toolCallId, \"\");\n break;\n\n case EventType.TOOL_CALL_ARGS: {\n const current = this.accumulatingArgs.get(event.toolCallId);\n this.accumulatingArgs.set(\n event.toolCallId,\n (current ?? \"\") + event.delta,\n );\n break;\n }\n\n case EventType.TOOL_CALL_END: {\n const jsonStr = this.accumulatingArgs.get(event.toolCallId);\n const toolCall = this.pendingToolCalls.get(event.toolCallId);\n if (toolCall && jsonStr) {\n try {\n toolCall.input = JSON.parse(jsonStr) as Record<string, unknown>;\n } catch (error) {\n // Fail-fast: don't silently continue with empty input\n throw new Error(\n `Failed to parse tool call arguments for ${event.toolCallId}: ${error instanceof Error ? error.message : \"Unknown error\"}. JSON: ${jsonStr.slice(0, 100)}${jsonStr.length > 100 ? \"...\" : \"\"}`,\n );\n }\n }\n break;\n }\n\n default:\n // Other event types are ignored - only tool call events are tracked\n break;\n }\n }\n\n /**\n * Gets the name and accumulated args for a tool call that is still accumulating.\n * Used by the event loop to get tool state for partial JSON parsing.\n * @param toolCallId - ID of the tool call to look up\n * @returns The tool name and raw accumulated args string, or undefined if not found\n */\n getAccumulatingToolCall(\n toolCallId: string,\n ): { name: string; accumulatedArgs: string } | undefined {\n const toolCall = this.pendingToolCalls.get(toolCallId);\n const args = this.accumulatingArgs.get(toolCallId);\n if (!toolCall || args === undefined) return undefined;\n return { name: toolCall.name, accumulatedArgs: args };\n }\n\n /**\n * Gets tool calls for the given IDs, filtered to only those that exist.\n * @param toolCallIds - IDs of tool calls to retrieve\n * @returns Map of tool call ID to pending tool call\n */\n getToolCallsById(toolCallIds: string[]): Map<string, PendingToolCall> {\n const result = new Map<string, PendingToolCall>();\n for (const id of toolCallIds) {\n const toolCall = this.pendingToolCalls.get(id);\n if (toolCall) {\n result.set(id, toolCall);\n }\n }\n return result;\n }\n\n /**\n * Clears tracked tool calls for the given IDs.\n * @param toolCallIds - IDs of tool calls to clear\n */\n clearToolCalls(toolCallIds: string[]): void {\n for (const id of toolCallIds) {\n this.pendingToolCalls.delete(id);\n this.accumulatingArgs.delete(id);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"tool-call-tracker.js","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Call Tracker\n *\n * Tracks tool calls during streaming, accumulating arguments until complete.\n * Owns the tool name → JSON Schema mapping and handles unstrictification\n * so callers don't need to know about schema conversion.\n */\n\nimport { EventType, type AGUIEvent } from \"@ag-ui/core\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { parse as parsePartialJson } from \"partial-json\";\nimport type { TamboTool } from \"../../model/component-metadata\";\nimport { schemaToJsonSchema } from \"../../schema/schema\";\nimport type { PendingToolCall } from \"./tool-executor\";\nimport { unstrictifyToolCallParamsFromSchema } from \"./unstrictify\";\n\n/**\n * Build a tool-name → JSONSchema7 map from the tool registry.\n * Handles both modern `inputSchema` and deprecated `toolSchema` formats.\n * Tools whose schema can't be converted are silently skipped.\n * @param toolRegistry - Record of tool name → tool definition\n * @returns Map of tool name → JSON Schema\n */\nfunction buildToolSchemas(\n toolRegistry: Record<string, TamboTool>,\n): Map<string, JSONSchema7> {\n const schemas = new Map<string, JSONSchema7>();\n for (const tool of Object.values(toolRegistry)) {\n try {\n if (\"inputSchema\" in tool && tool.inputSchema) {\n schemas.set(tool.name, schemaToJsonSchema(tool.inputSchema));\n } else if (\"toolSchema\" in tool && tool.toolSchema) {\n schemas.set(tool.name, schemaToJsonSchema(tool.toolSchema));\n }\n } catch {\n // Schema conversion failed — tool still works, just without unstrictification\n }\n }\n return schemas;\n}\n\n/**\n * Tracks tool calls during streaming, accumulating arguments until complete.\n *\n * Tool calls arrive as a sequence of events:\n * 1. TOOL_CALL_START - initializes the tool call with name\n * 2. TOOL_CALL_ARGS (multiple) - streams JSON argument fragments\n * 3. TOOL_CALL_END - marks the tool call as complete, triggers JSON parsing\n *\n * When constructed with a tool registry, the tracker unstrictifies parsed\n * args (both partial and final) using the original JSON Schemas.\n */\nexport class ToolCallTracker {\n private pendingToolCalls = new Map<string, PendingToolCall>();\n private accumulatingArgs = new Map<string, string>();\n private _toolSchemas: Map<string, JSONSchema7>;\n\n constructor(toolRegistry?: Record<string, TamboTool>) {\n this._toolSchemas = toolRegistry\n ? buildToolSchemas(toolRegistry)\n : new Map();\n }\n\n /**\n * The tool-name → JSONSchema7 map, for passing to the reducer.\n * @returns The tool schemas map\n */\n get toolSchemas(): Map<string, JSONSchema7> {\n return this._toolSchemas;\n }\n\n /**\n * Handles a streaming event, tracking tool call state as needed.\n * @param event - The streaming event to process\n * @throws {Error} If JSON parsing fails on TOOL_CALL_END (fail-fast, no silent fallback)\n */\n handleEvent(event: AGUIEvent): void {\n switch (event.type) {\n case EventType.TOOL_CALL_START:\n this.pendingToolCalls.set(event.toolCallId, {\n name: event.toolCallName,\n input: {},\n });\n this.accumulatingArgs.set(event.toolCallId, \"\");\n break;\n\n case EventType.TOOL_CALL_ARGS: {\n const current = this.accumulatingArgs.get(event.toolCallId);\n this.accumulatingArgs.set(\n event.toolCallId,\n (current ?? \"\") + event.delta,\n );\n break;\n }\n\n case EventType.TOOL_CALL_END: {\n const jsonStr = this.accumulatingArgs.get(event.toolCallId);\n const toolCall = this.pendingToolCalls.get(event.toolCallId);\n if (toolCall && jsonStr) {\n let parsedInput: Record<string, unknown>;\n try {\n parsedInput = JSON.parse(jsonStr) as Record<string, unknown>;\n } catch (error) {\n // Fail-fast: don't silently continue with empty input\n throw new Error(\n `Failed to parse tool call arguments for ${event.toolCallId}: ${error instanceof Error ? error.message : \"Unknown error\"}. JSON: ${jsonStr.slice(0, 100)}${jsonStr.length > 100 ? \"...\" : \"\"}`,\n );\n }\n\n parsedInput = this.unstrictify(toolCall.name, parsedInput);\n toolCall.input = parsedInput;\n }\n break;\n }\n\n default:\n // Other event types are ignored - only tool call events are tracked\n break;\n }\n }\n\n /**\n * Parses partial JSON from the accumulated args for a tool call and\n * unstrictifies the result. Used during streaming to get the current\n * best-effort parsed args.\n * @param toolCallId - ID of the tool call to parse\n * @returns Parsed and unstrictified args, or undefined if not parseable yet\n */\n parsePartialArgs(toolCallId: string): Record<string, unknown> | undefined {\n const accToolCall = this.getAccumulatingToolCall(toolCallId);\n if (!accToolCall) return undefined;\n\n try {\n const parsed: unknown = parsePartialJson(accToolCall.accumulatedArgs);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n !Array.isArray(parsed)\n ) {\n return this.unstrictify(\n accToolCall.name,\n parsed as Record<string, unknown>,\n );\n }\n } catch {\n /* not parseable yet */\n }\n return undefined;\n }\n\n /**\n * Gets the name and accumulated args for a tool call that is still accumulating.\n * @param toolCallId - ID of the tool call to look up\n * @returns The tool name and raw accumulated args string, or undefined if not found\n */\n getAccumulatingToolCall(\n toolCallId: string,\n ): { name: string; accumulatedArgs: string } | undefined {\n const toolCall = this.pendingToolCalls.get(toolCallId);\n const args = this.accumulatingArgs.get(toolCallId);\n if (!toolCall || args === undefined) return undefined;\n return { name: toolCall.name, accumulatedArgs: args };\n }\n\n /**\n * Gets tool calls for the given IDs, filtered to only those that exist.\n * @param toolCallIds - IDs of tool calls to retrieve\n * @returns Map of tool call ID to pending tool call\n */\n getToolCallsById(toolCallIds: string[]): Map<string, PendingToolCall> {\n const result = new Map<string, PendingToolCall>();\n for (const id of toolCallIds) {\n const toolCall = this.pendingToolCalls.get(id);\n if (toolCall) {\n result.set(id, toolCall);\n }\n }\n return result;\n }\n\n /**\n * Clears tracked tool calls for the given IDs.\n * @param toolCallIds - IDs of tool calls to clear\n */\n clearToolCalls(toolCallIds: string[]): void {\n for (const id of toolCallIds) {\n this.pendingToolCalls.delete(id);\n this.accumulatingArgs.delete(id);\n }\n }\n\n /**\n * Unstrictify params using the schema for the given tool name.\n * Returns params unchanged if no schema is available.\n */\n private unstrictify(\n toolName: string,\n params: Record<string, unknown>,\n ): Record<string, unknown> {\n const schema = this._toolSchemas.get(toolName);\n if (!schema) return params;\n return unstrictifyToolCallParamsFromSchema(schema, params);\n }\n}\n"]}
@@ -2,6 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@ag-ui/core");
4
4
  const tool_call_tracker_1 = require("./tool-call-tracker");
5
+ /** Minimal tool definition for tests — only name + inputSchema are needed. */
6
+ function fakeTool(name, inputSchema) {
7
+ return {
8
+ name,
9
+ description: "",
10
+ tool: () => null,
11
+ inputSchema,
12
+ outputSchema: {},
13
+ };
14
+ }
15
+ /** Helper to create a tracker with a started tool call. */
16
+ function createTrackerWithToolCall(toolCallId = "call_1", toolCallName = "get_weather", toolRegistry) {
17
+ const tracker = new tool_call_tracker_1.ToolCallTracker(toolRegistry);
18
+ tracker.handleEvent({
19
+ type: core_1.EventType.TOOL_CALL_START,
20
+ toolCallId,
21
+ toolCallName,
22
+ parentMessageId: "msg_1",
23
+ });
24
+ return tracker;
25
+ }
5
26
  describe("ToolCallTracker", () => {
6
27
  describe("getAccumulatingToolCall", () => {
7
28
  it("returns undefined for unknown tool call ID", () => {
@@ -63,5 +84,162 @@ describe("ToolCallTracker", () => {
63
84
  });
64
85
  });
65
86
  });
87
+ describe("handleEvent - TOOL_CALL_END", () => {
88
+ it("parses accumulated JSON on TOOL_CALL_END", () => {
89
+ const tracker = createTrackerWithToolCall();
90
+ tracker.handleEvent({
91
+ type: core_1.EventType.TOOL_CALL_ARGS,
92
+ toolCallId: "call_1",
93
+ delta: '{"city":"NYC"}',
94
+ });
95
+ tracker.handleEvent({
96
+ type: core_1.EventType.TOOL_CALL_END,
97
+ toolCallId: "call_1",
98
+ });
99
+ const result = tracker.getToolCallsById(["call_1"]);
100
+ expect(result.get("call_1")?.input).toEqual({ city: "NYC" });
101
+ });
102
+ it("throws on invalid JSON at TOOL_CALL_END", () => {
103
+ const tracker = createTrackerWithToolCall();
104
+ tracker.handleEvent({
105
+ type: core_1.EventType.TOOL_CALL_ARGS,
106
+ toolCallId: "call_1",
107
+ delta: "{not valid json",
108
+ });
109
+ expect(() => tracker.handleEvent({
110
+ type: core_1.EventType.TOOL_CALL_END,
111
+ toolCallId: "call_1",
112
+ })).toThrow("Failed to parse tool call arguments for call_1");
113
+ });
114
+ });
115
+ describe("TOOL_CALL_END with unstrictification", () => {
116
+ const schema = {
117
+ type: "object",
118
+ properties: {
119
+ city: { type: "string" },
120
+ units: { type: "string" },
121
+ },
122
+ required: ["city"],
123
+ };
124
+ const registry = {
125
+ get_weather: fakeTool("get_weather", schema),
126
+ };
127
+ it("strips null optional params when registry is provided", () => {
128
+ const tracker = createTrackerWithToolCall("call_1", "get_weather", registry);
129
+ tracker.handleEvent({
130
+ type: core_1.EventType.TOOL_CALL_ARGS,
131
+ toolCallId: "call_1",
132
+ delta: '{"city":"Seattle","units":null}',
133
+ });
134
+ tracker.handleEvent({
135
+ type: core_1.EventType.TOOL_CALL_END,
136
+ toolCallId: "call_1",
137
+ });
138
+ const result = tracker.getToolCallsById(["call_1"]);
139
+ expect(result.get("call_1")?.input).toEqual({ city: "Seattle" });
140
+ });
141
+ it("preserves required null params", () => {
142
+ const tracker = createTrackerWithToolCall("call_1", "get_weather", registry);
143
+ tracker.handleEvent({
144
+ type: core_1.EventType.TOOL_CALL_ARGS,
145
+ toolCallId: "call_1",
146
+ delta: '{"city":null,"units":null}',
147
+ });
148
+ tracker.handleEvent({
149
+ type: core_1.EventType.TOOL_CALL_END,
150
+ toolCallId: "call_1",
151
+ });
152
+ const result = tracker.getToolCallsById(["call_1"]);
153
+ // city is required, so null is preserved; units is optional, so null is stripped
154
+ expect(result.get("call_1")?.input).toEqual({ city: null });
155
+ });
156
+ it("does not unstrictify when no registry is provided", () => {
157
+ const tracker = createTrackerWithToolCall();
158
+ tracker.handleEvent({
159
+ type: core_1.EventType.TOOL_CALL_ARGS,
160
+ toolCallId: "call_1",
161
+ delta: '{"city":"Seattle","units":null}',
162
+ });
163
+ tracker.handleEvent({
164
+ type: core_1.EventType.TOOL_CALL_END,
165
+ toolCallId: "call_1",
166
+ });
167
+ const result = tracker.getToolCallsById(["call_1"]);
168
+ // Without registry, nulls are preserved
169
+ expect(result.get("call_1")?.input).toEqual({
170
+ city: "Seattle",
171
+ units: null,
172
+ });
173
+ });
174
+ it("preserves _tambo_* pass-through params", () => {
175
+ const tracker = createTrackerWithToolCall("call_1", "get_weather", registry);
176
+ tracker.handleEvent({
177
+ type: core_1.EventType.TOOL_CALL_ARGS,
178
+ toolCallId: "call_1",
179
+ delta: '{"city":"Seattle","units":null,"_tambo_statusMessage":"Loading"}',
180
+ });
181
+ tracker.handleEvent({
182
+ type: core_1.EventType.TOOL_CALL_END,
183
+ toolCallId: "call_1",
184
+ });
185
+ const result = tracker.getToolCallsById(["call_1"]);
186
+ expect(result.get("call_1")?.input).toEqual({
187
+ city: "Seattle",
188
+ _tambo_statusMessage: "Loading",
189
+ });
190
+ });
191
+ });
192
+ describe("parsePartialArgs", () => {
193
+ const schema = {
194
+ type: "object",
195
+ properties: {
196
+ required_string: { type: "string" },
197
+ optional_string: { type: "string" },
198
+ },
199
+ required: ["required_string"],
200
+ };
201
+ const registry = {
202
+ my_tool: fakeTool("my_tool", schema),
203
+ };
204
+ it("returns undefined for unknown tool call ID", () => {
205
+ const tracker = new tool_call_tracker_1.ToolCallTracker(registry);
206
+ expect(tracker.parsePartialArgs("nonexistent")).toBeUndefined();
207
+ });
208
+ it("returns undefined when partial JSON is not parseable", () => {
209
+ const tracker = createTrackerWithToolCall("call_1", "my_tool", registry);
210
+ tracker.handleEvent({
211
+ type: core_1.EventType.TOOL_CALL_ARGS,
212
+ toolCallId: "call_1",
213
+ delta: '{"req',
214
+ });
215
+ // partial-json should handle this, but if it can't parse to an object
216
+ // the method returns undefined
217
+ const result = tracker.parsePartialArgs("call_1");
218
+ // partial-json can parse this to { req: undefined } or similar — either
219
+ // way it should not throw
220
+ expect(result === undefined || typeof result === "object").toBe(true);
221
+ });
222
+ it("unstrictifies partial args during streaming", () => {
223
+ const tracker = createTrackerWithToolCall("call_1", "my_tool", registry);
224
+ // Stream the full strict JSON in 3-char chunks
225
+ const fullJson = '{"required_string":"required","optional_string":null}';
226
+ const chunkSize = 3;
227
+ for (let i = 0; i < fullJson.length; i += chunkSize) {
228
+ tracker.handleEvent({
229
+ type: core_1.EventType.TOOL_CALL_ARGS,
230
+ toolCallId: "call_1",
231
+ delta: fullJson.slice(i, i + chunkSize),
232
+ });
233
+ const partial = tracker.parsePartialArgs("call_1");
234
+ if (partial) {
235
+ // Must never contain optional_string
236
+ expect(partial).not.toHaveProperty("optional_string");
237
+ }
238
+ }
239
+ // After all chunks, should have the required param
240
+ const final = tracker.parsePartialArgs("call_1");
241
+ expect(final).toEqual({ required_string: "required" });
242
+ });
243
+ });
66
244
  });
67
245
  //# sourceMappingURL=tool-call-tracker.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-call-tracker.test.js","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.test.ts"],"names":[],"mappings":";;AAAA,sCAAwC;AACxC,2DAAsD;AAEtD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,OAAO,GAAG,IAAI,mCAAe,EAAE,CAAC;YACtC,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,MAAM,OAAO,GAAG,IAAI,mCAAe,EAAE,CAAC;YACtC,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,eAAe;gBAC/B,UAAU,EAAE,QAAQ;gBACpB,YAAY,EAAE,aAAa;gBAC3B,eAAe,EAAE,OAAO;aACzB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;YACrE,MAAM,OAAO,GAAG,IAAI,mCAAe,EAAE,CAAC;YACtC,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,eAAe;gBAC/B,UAAU,EAAE,QAAQ;gBACpB,YAAY,EAAE,aAAa;gBAC3B,eAAe,EAAE,OAAO;aACzB,CAAC,CAAC;YACH,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,cAAc;gBAC9B,UAAU,EAAE,QAAQ;gBACpB,KAAK,EAAE,gBAAgB;aACxB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,IAAI,EAAE,aAAa;gBACnB,eAAe,EAAE,gBAAgB;aAClC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,OAAO,GAAG,IAAI,mCAAe,EAAE,CAAC;YACtC,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,eAAe;gBAC/B,UAAU,EAAE,QAAQ;gBACpB,YAAY,EAAE,aAAa;gBAC3B,eAAe,EAAE,OAAO;aACzB,CAAC,CAAC;YACH,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,cAAc;gBAC9B,UAAU,EAAE,QAAQ;gBACpB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;YACH,OAAO,CAAC,WAAW,CAAC;gBAClB,IAAI,EAAE,gBAAS,CAAC,cAAc;gBAC9B,UAAU,EAAE,QAAQ;gBACpB,KAAK,EAAE,UAAU;aAClB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,IAAI,EAAE,aAAa;gBACnB,eAAe,EAAE,mBAAmB;aACrC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { EventType } from \"@ag-ui/core\";\nimport { ToolCallTracker } from \"./tool-call-tracker\";\n\ndescribe(\"ToolCallTracker\", () => {\n describe(\"getAccumulatingToolCall\", () => {\n it(\"returns undefined for unknown tool call ID\", () => {\n const tracker = new ToolCallTracker();\n expect(tracker.getAccumulatingToolCall(\"nonexistent\")).toBeUndefined();\n });\n\n it(\"returns name and empty accumulated args after TOOL_CALL_START\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({ name: \"write_story\", accumulatedArgs: \"\" });\n });\n\n it(\"returns name and accumulated args after START + ARGS events\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"title\":\"Once',\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({\n name: \"write_story\",\n accumulatedArgs: '{\"title\":\"Once',\n });\n });\n\n it(\"accumulates multiple ARGS deltas\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"title\":',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '\"Hello\"}',\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({\n name: \"write_story\",\n accumulatedArgs: '{\"title\":\"Hello\"}',\n });\n });\n });\n});\n"]}
1
+ {"version":3,"file":"tool-call-tracker.test.js","sourceRoot":"","sources":["../../../src/v1/utils/tool-call-tracker.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventType } from \"@ag-ui/core\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { TamboTool } from \"../../model/component-metadata\";\nimport { ToolCallTracker } from \"./tool-call-tracker\";\n\n/** Minimal tool definition for tests — only name + inputSchema are needed. */\nfunction fakeTool(name: string, inputSchema: JSONSchema7): TamboTool {\n return {\n name,\n description: \"\",\n tool: () => null,\n inputSchema,\n outputSchema: {},\n } as TamboTool;\n}\n\n/** Helper to create a tracker with a started tool call. */\nfunction createTrackerWithToolCall(\n toolCallId = \"call_1\",\n toolCallName = \"get_weather\",\n toolRegistry?: Record<string, TamboTool>,\n): ToolCallTracker {\n const tracker = new ToolCallTracker(toolRegistry);\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName,\n parentMessageId: \"msg_1\",\n });\n return tracker;\n}\n\ndescribe(\"ToolCallTracker\", () => {\n describe(\"getAccumulatingToolCall\", () => {\n it(\"returns undefined for unknown tool call ID\", () => {\n const tracker = new ToolCallTracker();\n expect(tracker.getAccumulatingToolCall(\"nonexistent\")).toBeUndefined();\n });\n\n it(\"returns name and empty accumulated args after TOOL_CALL_START\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({ name: \"write_story\", accumulatedArgs: \"\" });\n });\n\n it(\"returns name and accumulated args after START + ARGS events\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"title\":\"Once',\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({\n name: \"write_story\",\n accumulatedArgs: '{\"title\":\"Once',\n });\n });\n\n it(\"accumulates multiple ARGS deltas\", () => {\n const tracker = new ToolCallTracker();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_START,\n toolCallId: \"call_1\",\n toolCallName: \"write_story\",\n parentMessageId: \"msg_1\",\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"title\":',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '\"Hello\"}',\n });\n\n const result = tracker.getAccumulatingToolCall(\"call_1\");\n expect(result).toEqual({\n name: \"write_story\",\n accumulatedArgs: '{\"title\":\"Hello\"}',\n });\n });\n });\n\n describe(\"handleEvent - TOOL_CALL_END\", () => {\n it(\"parses accumulated JSON on TOOL_CALL_END\", () => {\n const tracker = createTrackerWithToolCall();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"city\":\"NYC\"}',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n });\n\n const result = tracker.getToolCallsById([\"call_1\"]);\n expect(result.get(\"call_1\")?.input).toEqual({ city: \"NYC\" });\n });\n\n it(\"throws on invalid JSON at TOOL_CALL_END\", () => {\n const tracker = createTrackerWithToolCall();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: \"{not valid json\",\n });\n\n expect(() =>\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n }),\n ).toThrow(\"Failed to parse tool call arguments for call_1\");\n });\n });\n\n describe(\"TOOL_CALL_END with unstrictification\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n city: { type: \"string\" },\n units: { type: \"string\" },\n },\n required: [\"city\"],\n };\n\n const registry: Record<string, TamboTool> = {\n get_weather: fakeTool(\"get_weather\", schema),\n };\n\n it(\"strips null optional params when registry is provided\", () => {\n const tracker = createTrackerWithToolCall(\n \"call_1\",\n \"get_weather\",\n registry,\n );\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"city\":\"Seattle\",\"units\":null}',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n });\n\n const result = tracker.getToolCallsById([\"call_1\"]);\n expect(result.get(\"call_1\")?.input).toEqual({ city: \"Seattle\" });\n });\n\n it(\"preserves required null params\", () => {\n const tracker = createTrackerWithToolCall(\n \"call_1\",\n \"get_weather\",\n registry,\n );\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"city\":null,\"units\":null}',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n });\n\n const result = tracker.getToolCallsById([\"call_1\"]);\n // city is required, so null is preserved; units is optional, so null is stripped\n expect(result.get(\"call_1\")?.input).toEqual({ city: null });\n });\n\n it(\"does not unstrictify when no registry is provided\", () => {\n const tracker = createTrackerWithToolCall();\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"city\":\"Seattle\",\"units\":null}',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n });\n\n const result = tracker.getToolCallsById([\"call_1\"]);\n // Without registry, nulls are preserved\n expect(result.get(\"call_1\")?.input).toEqual({\n city: \"Seattle\",\n units: null,\n });\n });\n\n it(\"preserves _tambo_* pass-through params\", () => {\n const tracker = createTrackerWithToolCall(\n \"call_1\",\n \"get_weather\",\n registry,\n );\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta:\n '{\"city\":\"Seattle\",\"units\":null,\"_tambo_statusMessage\":\"Loading\"}',\n });\n tracker.handleEvent({\n type: EventType.TOOL_CALL_END,\n toolCallId: \"call_1\",\n });\n\n const result = tracker.getToolCallsById([\"call_1\"]);\n expect(result.get(\"call_1\")?.input).toEqual({\n city: \"Seattle\",\n _tambo_statusMessage: \"Loading\",\n });\n });\n });\n\n describe(\"parsePartialArgs\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n required_string: { type: \"string\" },\n optional_string: { type: \"string\" },\n },\n required: [\"required_string\"],\n };\n\n const registry: Record<string, TamboTool> = {\n my_tool: fakeTool(\"my_tool\", schema),\n };\n\n it(\"returns undefined for unknown tool call ID\", () => {\n const tracker = new ToolCallTracker(registry);\n expect(tracker.parsePartialArgs(\"nonexistent\")).toBeUndefined();\n });\n\n it(\"returns undefined when partial JSON is not parseable\", () => {\n const tracker = createTrackerWithToolCall(\"call_1\", \"my_tool\", registry);\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: '{\"req',\n });\n\n // partial-json should handle this, but if it can't parse to an object\n // the method returns undefined\n const result = tracker.parsePartialArgs(\"call_1\");\n // partial-json can parse this to { req: undefined } or similar — either\n // way it should not throw\n expect(result === undefined || typeof result === \"object\").toBe(true);\n });\n\n it(\"unstrictifies partial args during streaming\", () => {\n const tracker = createTrackerWithToolCall(\"call_1\", \"my_tool\", registry);\n\n // Stream the full strict JSON in 3-char chunks\n const fullJson = '{\"required_string\":\"required\",\"optional_string\":null}';\n const chunkSize = 3;\n for (let i = 0; i < fullJson.length; i += chunkSize) {\n tracker.handleEvent({\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: \"call_1\",\n delta: fullJson.slice(i, i + chunkSize),\n });\n\n const partial = tracker.parsePartialArgs(\"call_1\");\n if (partial) {\n // Must never contain optional_string\n expect(partial).not.toHaveProperty(\"optional_string\");\n }\n }\n\n // After all chunks, should have the required param\n const final = tracker.parsePartialArgs(\"call_1\");\n expect(final).toEqual({ required_string: \"required\" });\n });\n });\n});\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Unstrictify tool call parameters using the original JSON Schema.
3
+ *
4
+ * When OpenAI's structured outputs mode is enabled, all optional parameters
5
+ * become required-and-nullable. The LLM then sends `null` for parameters the
6
+ * user didn't specify. This module reverses that transformation by comparing
7
+ * the LLM's output against the original schema and stripping nulls for
8
+ * parameters that were originally optional and non-nullable.
9
+ *
10
+ * Copied from packages/core/src/strictness/tool-call-strict.ts (minus the
11
+ * OpenAI-specific `unstrictifyToolCallRequest` wrapper).
12
+ */
13
+ import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
14
+ /**
15
+ * Unstrictify tool call params using the original JSON Schema.
16
+ *
17
+ * Unlike the private `unstrictifyToolCallParams` which throws on unknown params,
18
+ * this function separates params into schema-defined vs `_tambo_*` pass-through
19
+ * (server-injected params not in the original schema), unstrictifies only the
20
+ * schema-defined ones, and merges pass-through params back. Unknown keys that
21
+ * aren't in the schema and don't have the `_tambo_` prefix are dropped.
22
+ * @returns The params with strictification-induced nulls stripped for optional
23
+ * non-nullable properties, and pass-through params preserved as-is.
24
+ */
25
+ export declare function unstrictifyToolCallParamsFromSchema(originalSchema: JSONSchema7, params: Record<string, unknown>): Record<string, unknown>;
26
+ /**
27
+ * Check if a JSON Schema definition allows null values.
28
+ * @param originalSchema - The schema definition to check
29
+ * @returns True if the schema allows null values
30
+ */
31
+ export declare function canBeNull(originalSchema: JSONSchema7Definition): boolean;
32
+ //# sourceMappingURL=unstrictify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unstrictify.d.ts","sourceRoot":"","sources":["../../../src/v1/utils/unstrictify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAsItE;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,WAAW,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyBzB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,cAAc,EAAE,qBAAqB,GAAG,OAAO,CAaxE"}