@tambo-ai/react 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) 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/utils/event-accumulator.d.ts +3 -0
  6. package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
  7. package/dist/v1/utils/event-accumulator.js +26 -5
  8. package/dist/v1/utils/event-accumulator.js.map +1 -1
  9. package/dist/v1/utils/event-accumulator.test.js +113 -0
  10. package/dist/v1/utils/event-accumulator.test.js.map +1 -1
  11. package/dist/v1/utils/tool-call-tracker.d.ts +26 -4
  12. package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -1
  13. package/dist/v1/utils/tool-call-tracker.js +82 -5
  14. package/dist/v1/utils/tool-call-tracker.js.map +1 -1
  15. package/dist/v1/utils/tool-call-tracker.test.js +178 -0
  16. package/dist/v1/utils/tool-call-tracker.test.js.map +1 -1
  17. package/dist/v1/utils/unstrictify.d.ts +32 -0
  18. package/dist/v1/utils/unstrictify.d.ts.map +1 -0
  19. package/dist/v1/utils/unstrictify.js +159 -0
  20. package/dist/v1/utils/unstrictify.js.map +1 -0
  21. package/dist/v1/utils/unstrictify.test.d.ts +2 -0
  22. package/dist/v1/utils/unstrictify.test.d.ts.map +1 -0
  23. package/dist/v1/utils/unstrictify.test.js +187 -0
  24. package/dist/v1/utils/unstrictify.test.js.map +1 -0
  25. package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
  26. package/esm/v1/hooks/use-tambo-v1-send-message.js +4 -31
  27. package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
  28. package/esm/v1/utils/event-accumulator.d.ts +3 -0
  29. package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
  30. package/esm/v1/utils/event-accumulator.js +26 -5
  31. package/esm/v1/utils/event-accumulator.js.map +1 -1
  32. package/esm/v1/utils/event-accumulator.test.js +113 -0
  33. package/esm/v1/utils/event-accumulator.test.js.map +1 -1
  34. package/esm/v1/utils/tool-call-tracker.d.ts +26 -4
  35. package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -1
  36. package/esm/v1/utils/tool-call-tracker.js +82 -5
  37. package/esm/v1/utils/tool-call-tracker.js.map +1 -1
  38. package/esm/v1/utils/tool-call-tracker.test.js +178 -0
  39. package/esm/v1/utils/tool-call-tracker.test.js.map +1 -1
  40. package/esm/v1/utils/unstrictify.d.ts +32 -0
  41. package/esm/v1/utils/unstrictify.d.ts.map +1 -0
  42. package/esm/v1/utils/unstrictify.js +155 -0
  43. package/esm/v1/utils/unstrictify.js.map +1 -0
  44. package/esm/v1/utils/unstrictify.test.d.ts +2 -0
  45. package/esm/v1/utils/unstrictify.test.d.ts.map +1 -0
  46. package/esm/v1/utils/unstrictify.test.js +185 -0
  47. package/esm/v1/utils/unstrictify.test.js.map +1 -0
  48. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ /**
3
+ * Unstrictify tool call parameters using the original JSON Schema.
4
+ *
5
+ * When OpenAI's structured outputs mode is enabled, all optional parameters
6
+ * become required-and-nullable. The LLM then sends `null` for parameters the
7
+ * user didn't specify. This module reverses that transformation by comparing
8
+ * the LLM's output against the original schema and stripping nulls for
9
+ * parameters that were originally optional and non-nullable.
10
+ *
11
+ * Copied from packages/core/src/strictness/tool-call-strict.ts (minus the
12
+ * OpenAI-specific `unstrictifyToolCallRequest` wrapper).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.unstrictifyToolCallParamsFromSchema = unstrictifyToolCallParamsFromSchema;
16
+ exports.canBeNull = canBeNull;
17
+ /**
18
+ * Unstrictify the parameters of a tool call request.
19
+ *
20
+ * This effectively reverses the process of strictifyToolCallParams, for a
21
+ * tool call request that was built from a strict JSON Schema, by returning a
22
+ * updated tool call request with the parameter values unstrictified.
23
+ */
24
+ function unstrictifyToolCallParams(originalToolParamSchema, toolCallRequestParams) {
25
+ if (originalToolParamSchema.type !== "object") {
26
+ throw new Error(`tool call parameter schema must be an object, instead got ${originalToolParamSchema.type} / ${typeof originalToolParamSchema}`);
27
+ }
28
+ const newParams = Object.entries(toolCallRequestParams)
29
+ .map(([parameterName, parameterValue]) => {
30
+ const isRequired = originalToolParamSchema.required?.includes(parameterName);
31
+ // find the param in the original tool schema
32
+ const originalParamSchema = parameterName in (originalToolParamSchema.properties ?? {})
33
+ ? originalToolParamSchema.properties?.[parameterName]
34
+ : undefined;
35
+ // This should never happen, because the strict schema was derived from
36
+ // the original schema, so the parameter should always be present.
37
+ if (!originalParamSchema) {
38
+ throw new Error(`Tool call request parameter ${parameterName} not found in original tool`);
39
+ }
40
+ if (parameterValue === null &&
41
+ !canBeNull(originalParamSchema) &&
42
+ !isRequired) {
43
+ // This is the meat of this function. In the strict schema, this is
44
+ // "required and can be null", but in the original schema, the param was
45
+ // not required.
46
+ if (typeof originalParamSchema === "object" &&
47
+ "default" in originalParamSchema) {
48
+ return [parameterName, originalParamSchema.default];
49
+ }
50
+ return undefined;
51
+ }
52
+ // recurse into arrays
53
+ if (typeof originalParamSchema === "object" &&
54
+ originalParamSchema.type === "array") {
55
+ const arrayValue = parameterValue;
56
+ const itemSchema = originalParamSchema.items;
57
+ if (Array.isArray(arrayValue) &&
58
+ itemSchema &&
59
+ typeof itemSchema === "object" &&
60
+ !Array.isArray(itemSchema)) {
61
+ const newArrayValue = arrayValue.map((item) => {
62
+ if (itemSchema.type === "object" &&
63
+ typeof item === "object" &&
64
+ item !== null) {
65
+ // recurse into each object in the array
66
+ return unstrictifyToolCallParams(itemSchema, item);
67
+ }
68
+ return item;
69
+ });
70
+ return [parameterName, newArrayValue];
71
+ }
72
+ return [parameterName, parameterValue];
73
+ }
74
+ // recurse into the parameter value, passing along the matching original schema
75
+ if (typeof originalParamSchema === "object" &&
76
+ originalParamSchema.type === "object") {
77
+ // If the LLM sent a JSON string instead of an object (common with z.any() schemas),
78
+ // try to parse it
79
+ let objectValue = parameterValue;
80
+ if (typeof parameterValue === "string") {
81
+ try {
82
+ const parsed = JSON.parse(parameterValue);
83
+ if (typeof parsed === "object" && parsed !== null) {
84
+ objectValue = parsed;
85
+ }
86
+ }
87
+ catch {
88
+ // Not valid JSON, keep original value
89
+ }
90
+ }
91
+ // Only recurse if we have an actual object AND the schema has properties defined.
92
+ // If the schema has no properties (e.g., z.any() which produces {type: 'object', anyOf: [...]}),
93
+ // just return the value as-is without recursing.
94
+ const hasProperties = originalParamSchema.properties &&
95
+ Object.keys(originalParamSchema.properties).length > 0;
96
+ if (hasProperties &&
97
+ typeof objectValue === "object" &&
98
+ objectValue !== null &&
99
+ !Array.isArray(objectValue)) {
100
+ const newParamValue = unstrictifyToolCallParams(originalParamSchema, objectValue);
101
+ return [parameterName, newParamValue];
102
+ }
103
+ // Return the (possibly parsed) object value without recursing
104
+ return [parameterName, objectValue];
105
+ }
106
+ return [parameterName, parameterValue];
107
+ })
108
+ .filter((param) => param !== undefined);
109
+ return Object.fromEntries(newParams);
110
+ }
111
+ /**
112
+ * Unstrictify tool call params using the original JSON Schema.
113
+ *
114
+ * Unlike the private `unstrictifyToolCallParams` which throws on unknown params,
115
+ * this function separates params into schema-defined vs `_tambo_*` pass-through
116
+ * (server-injected params not in the original schema), unstrictifies only the
117
+ * schema-defined ones, and merges pass-through params back. Unknown keys that
118
+ * aren't in the schema and don't have the `_tambo_` prefix are dropped.
119
+ * @returns The params with strictification-induced nulls stripped for optional
120
+ * non-nullable properties, and pass-through params preserved as-is.
121
+ */
122
+ function unstrictifyToolCallParamsFromSchema(originalSchema, params) {
123
+ if (originalSchema.type !== "object") {
124
+ return params;
125
+ }
126
+ const schemaProperties = originalSchema.properties ?? {};
127
+ const schemaDefinedParams = {};
128
+ const passThroughParams = {};
129
+ for (const [key, value] of Object.entries(params)) {
130
+ if (key in schemaProperties) {
131
+ schemaDefinedParams[key] = value;
132
+ }
133
+ else if (key.startsWith("_tambo_")) {
134
+ passThroughParams[key] = value;
135
+ }
136
+ // Unknown keys not in schema and not _tambo_* are dropped —
137
+ // they're likely hallucinated by the model.
138
+ }
139
+ const unstrictified = unstrictifyToolCallParams(originalSchema, schemaDefinedParams);
140
+ return { ...unstrictified, ...passThroughParams };
141
+ }
142
+ /**
143
+ * Check if a JSON Schema definition allows null values.
144
+ * @param originalSchema - The schema definition to check
145
+ * @returns True if the schema allows null values
146
+ */
147
+ function canBeNull(originalSchema) {
148
+ if (typeof originalSchema !== "object") {
149
+ return false;
150
+ }
151
+ if (originalSchema.type === "null") {
152
+ return true;
153
+ }
154
+ if (originalSchema.anyOf?.some((anyOf) => canBeNull(anyOf))) {
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+ //# sourceMappingURL=unstrictify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unstrictify.js","sourceRoot":"","sources":["../../../src/v1/utils/unstrictify.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unstrictify tool call parameters using the original JSON Schema.\n *\n * When OpenAI's structured outputs mode is enabled, all optional parameters\n * become required-and-nullable. The LLM then sends `null` for parameters the\n * user didn't specify. This module reverses that transformation by comparing\n * the LLM's output against the original schema and stripping nulls for\n * parameters that were originally optional and non-nullable.\n *\n * Copied from packages/core/src/strictness/tool-call-strict.ts (minus the\n * OpenAI-specific `unstrictifyToolCallRequest` wrapper).\n */\n\nimport type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\n\n/**\n * Unstrictify the parameters of a tool call request.\n *\n * This effectively reverses the process of strictifyToolCallParams, for a\n * tool call request that was built from a strict JSON Schema, by returning a\n * updated tool call request with the parameter values unstrictified.\n */\nfunction unstrictifyToolCallParams(\n originalToolParamSchema: JSONSchema7,\n toolCallRequestParams: Record<string, unknown>,\n): Record<string, unknown> {\n if (originalToolParamSchema.type !== \"object\") {\n throw new Error(\n `tool call parameter schema must be an object, instead got ${originalToolParamSchema.type} / ${typeof originalToolParamSchema}`,\n );\n }\n const newParams = Object.entries(toolCallRequestParams)\n .map(([parameterName, parameterValue]) => {\n const isRequired =\n originalToolParamSchema.required?.includes(parameterName);\n // find the param in the original tool schema\n const originalParamSchema =\n parameterName in (originalToolParamSchema.properties ?? {})\n ? originalToolParamSchema.properties?.[parameterName]\n : undefined;\n\n // This should never happen, because the strict schema was derived from\n // the original schema, so the parameter should always be present.\n if (!originalParamSchema) {\n throw new Error(\n `Tool call request parameter ${parameterName} not found in original tool`,\n );\n }\n\n if (\n parameterValue === null &&\n !canBeNull(originalParamSchema) &&\n !isRequired\n ) {\n // This is the meat of this function. In the strict schema, this is\n // \"required and can be null\", but in the original schema, the param was\n // not required.\n if (\n typeof originalParamSchema === \"object\" &&\n \"default\" in originalParamSchema\n ) {\n return [parameterName, originalParamSchema.default] as const;\n }\n return undefined;\n }\n\n // recurse into arrays\n if (\n typeof originalParamSchema === \"object\" &&\n originalParamSchema.type === \"array\"\n ) {\n const arrayValue = parameterValue as unknown[];\n const itemSchema = originalParamSchema.items;\n if (\n Array.isArray(arrayValue) &&\n itemSchema &&\n typeof itemSchema === \"object\" &&\n !Array.isArray(itemSchema)\n ) {\n const newArrayValue = arrayValue.map((item) => {\n if (\n itemSchema.type === \"object\" &&\n typeof item === \"object\" &&\n item !== null\n ) {\n // recurse into each object in the array\n return unstrictifyToolCallParams(\n itemSchema,\n item as Record<string, unknown>,\n );\n }\n return item;\n });\n return [parameterName, newArrayValue] as const;\n }\n return [parameterName, parameterValue] as const;\n }\n\n // recurse into the parameter value, passing along the matching original schema\n if (\n typeof originalParamSchema === \"object\" &&\n originalParamSchema.type === \"object\"\n ) {\n // If the LLM sent a JSON string instead of an object (common with z.any() schemas),\n // try to parse it\n let objectValue = parameterValue;\n if (typeof parameterValue === \"string\") {\n try {\n const parsed = JSON.parse(parameterValue);\n if (typeof parsed === \"object\" && parsed !== null) {\n objectValue = parsed;\n }\n } catch {\n // Not valid JSON, keep original value\n }\n }\n\n // Only recurse if we have an actual object AND the schema has properties defined.\n // If the schema has no properties (e.g., z.any() which produces {type: 'object', anyOf: [...]}),\n // just return the value as-is without recursing.\n const hasProperties =\n originalParamSchema.properties &&\n Object.keys(originalParamSchema.properties).length > 0;\n\n if (\n hasProperties &&\n typeof objectValue === \"object\" &&\n objectValue !== null &&\n !Array.isArray(objectValue)\n ) {\n const newParamValue = unstrictifyToolCallParams(\n originalParamSchema,\n objectValue as Record<string, unknown>,\n );\n return [parameterName, newParamValue] as const;\n }\n\n // Return the (possibly parsed) object value without recursing\n return [parameterName, objectValue] as const;\n }\n\n return [parameterName, parameterValue] as const;\n })\n .filter((param) => param !== undefined);\n return Object.fromEntries(newParams);\n}\n\n/**\n * Unstrictify tool call params using the original JSON Schema.\n *\n * Unlike the private `unstrictifyToolCallParams` which throws on unknown params,\n * this function separates params into schema-defined vs `_tambo_*` pass-through\n * (server-injected params not in the original schema), unstrictifies only the\n * schema-defined ones, and merges pass-through params back. Unknown keys that\n * aren't in the schema and don't have the `_tambo_` prefix are dropped.\n * @returns The params with strictification-induced nulls stripped for optional\n * non-nullable properties, and pass-through params preserved as-is.\n */\nexport function unstrictifyToolCallParamsFromSchema(\n originalSchema: JSONSchema7,\n params: Record<string, unknown>,\n): Record<string, unknown> {\n if (originalSchema.type !== \"object\") {\n return params;\n }\n\n const schemaProperties = originalSchema.properties ?? {};\n const schemaDefinedParams: Record<string, unknown> = {};\n const passThroughParams: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(params)) {\n if (key in schemaProperties) {\n schemaDefinedParams[key] = value;\n } else if (key.startsWith(\"_tambo_\")) {\n passThroughParams[key] = value;\n }\n // Unknown keys not in schema and not _tambo_* are dropped —\n // they're likely hallucinated by the model.\n }\n\n const unstrictified = unstrictifyToolCallParams(\n originalSchema,\n schemaDefinedParams,\n );\n\n return { ...unstrictified, ...passThroughParams };\n}\n\n/**\n * Check if a JSON Schema definition allows null values.\n * @param originalSchema - The schema definition to check\n * @returns True if the schema allows null values\n */\nexport function canBeNull(originalSchema: JSONSchema7Definition): boolean {\n if (typeof originalSchema !== \"object\") {\n return false;\n }\n\n if (originalSchema.type === \"null\") {\n return true;\n }\n\n if (originalSchema.anyOf?.some((anyOf) => canBeNull(anyOf))) {\n return true;\n }\n return false;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=unstrictify.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unstrictify.test.d.ts","sourceRoot":"","sources":["../../../src/v1/utils/unstrictify.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const unstrictify_1 = require("./unstrictify");
4
+ describe("canBeNull", () => {
5
+ it("should return true for schema with type null", () => {
6
+ const schema = { type: "null" };
7
+ expect((0, unstrictify_1.canBeNull)(schema)).toBe(true);
8
+ });
9
+ it("should return true for schema with anyOf containing type null", () => {
10
+ const schema = {
11
+ anyOf: [{ type: "string" }, { type: "null" }],
12
+ };
13
+ expect((0, unstrictify_1.canBeNull)(schema)).toBe(true);
14
+ });
15
+ it("should return true for deeply nested anyOf containing type null", () => {
16
+ const schema = {
17
+ anyOf: [
18
+ { type: "string" },
19
+ {
20
+ anyOf: [{ type: "number" }, { type: "null" }],
21
+ },
22
+ ],
23
+ };
24
+ expect((0, unstrictify_1.canBeNull)(schema)).toBe(true);
25
+ });
26
+ it("should return false for non-object schema", () => {
27
+ expect((0, unstrictify_1.canBeNull)(true)).toBe(false);
28
+ expect((0, unstrictify_1.canBeNull)("string")).toBe(false);
29
+ });
30
+ it("should return false for schema without null type", () => {
31
+ const schema = { type: "string" };
32
+ expect((0, unstrictify_1.canBeNull)(schema)).toBe(false);
33
+ });
34
+ it("should return false for schema with anyOf not containing null type", () => {
35
+ const schema = {
36
+ anyOf: [{ type: "string" }, { type: "number" }],
37
+ };
38
+ expect((0, unstrictify_1.canBeNull)(schema)).toBe(false);
39
+ });
40
+ });
41
+ describe("unstrictifyToolCallParamsFromSchema", () => {
42
+ it("should strip null values for optional non-nullable params", () => {
43
+ const schema = {
44
+ type: "object",
45
+ properties: {
46
+ name: { type: "string" },
47
+ age: { type: "number" },
48
+ },
49
+ required: ["name"],
50
+ };
51
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
52
+ name: "John",
53
+ age: null,
54
+ });
55
+ expect(result).toEqual({ name: "John" });
56
+ });
57
+ it("should preserve pass-through params not in original schema", () => {
58
+ const schema = {
59
+ type: "object",
60
+ properties: {
61
+ name: { type: "string" },
62
+ },
63
+ required: ["name"],
64
+ };
65
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
66
+ name: "John",
67
+ _tambo_statusMessage: "Processing...",
68
+ _tambo_displayMessage: "Hello",
69
+ });
70
+ expect(result).toEqual({
71
+ name: "John",
72
+ _tambo_statusMessage: "Processing...",
73
+ _tambo_displayMessage: "Hello",
74
+ });
75
+ });
76
+ it("should handle nested object unstrictification with pass-through params", () => {
77
+ const schema = {
78
+ type: "object",
79
+ properties: {
80
+ user: {
81
+ type: "object",
82
+ properties: {
83
+ name: { type: "string" },
84
+ email: { type: "string" },
85
+ },
86
+ required: ["name"],
87
+ },
88
+ },
89
+ required: ["user"],
90
+ };
91
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
92
+ user: { name: "John", email: null },
93
+ _tambo_statusMessage: "Updating user",
94
+ });
95
+ expect(result).toEqual({
96
+ user: { name: "John" },
97
+ _tambo_statusMessage: "Updating user",
98
+ });
99
+ });
100
+ it("should return params as-is for non-object schemas", () => {
101
+ const schema = { type: "string" };
102
+ const params = { name: "John", _tambo_foo: "bar" };
103
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, params);
104
+ expect(result).toEqual(params);
105
+ });
106
+ it("should handle empty params", () => {
107
+ const schema = {
108
+ type: "object",
109
+ properties: {
110
+ name: { type: "string" },
111
+ },
112
+ required: [],
113
+ };
114
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {});
115
+ expect(result).toEqual({});
116
+ });
117
+ it("should handle schema with no properties defined", () => {
118
+ const schema = {
119
+ type: "object",
120
+ };
121
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
122
+ anything: "value",
123
+ _tambo_status: "ok",
124
+ });
125
+ // Only _tambo_* keys pass through; unknown keys are dropped
126
+ expect(result).toEqual({
127
+ _tambo_status: "ok",
128
+ });
129
+ });
130
+ it("should drop hallucinated keys not in schema or _tambo_* prefix", () => {
131
+ const schema = {
132
+ type: "object",
133
+ properties: {
134
+ city: { type: "string" },
135
+ },
136
+ required: ["city"],
137
+ };
138
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
139
+ city: "NYC",
140
+ temperatur: "celsius",
141
+ _tambo_statusMessage: "Fetching",
142
+ });
143
+ expect(result).toEqual({
144
+ city: "NYC",
145
+ _tambo_statusMessage: "Fetching",
146
+ });
147
+ });
148
+ it("should handle arrays with nested objects", () => {
149
+ const schema = {
150
+ type: "object",
151
+ properties: {
152
+ targets: {
153
+ type: "array",
154
+ items: {
155
+ type: "object",
156
+ properties: {
157
+ range: { type: "string" },
158
+ style: {
159
+ type: "object",
160
+ properties: {
161
+ bold: { type: "boolean" },
162
+ fontFamily: { type: "string" },
163
+ },
164
+ required: [],
165
+ },
166
+ },
167
+ required: ["style"],
168
+ },
169
+ },
170
+ },
171
+ required: ["targets"],
172
+ };
173
+ const result = (0, unstrictify_1.unstrictifyToolCallParamsFromSchema)(schema, {
174
+ targets: [
175
+ { range: "A1:B1", style: { bold: true, fontFamily: null } },
176
+ { range: "A2:A6", style: { bold: false, fontFamily: null } },
177
+ ],
178
+ });
179
+ expect(result).toEqual({
180
+ targets: [
181
+ { range: "A1:B1", style: { bold: true } },
182
+ { range: "A2:A6", style: { bold: false } },
183
+ ],
184
+ });
185
+ });
186
+ });
187
+ //# sourceMappingURL=unstrictify.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unstrictify.test.js","sourceRoot":"","sources":["../../../src/v1/utils/unstrictify.test.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { canBeNull, unstrictifyToolCallParamsFromSchema } from \"./unstrictify\";\n\ndescribe(\"canBeNull\", () => {\n it(\"should return true for schema with type null\", () => {\n const schema: JSONSchema7 = { type: \"null\" };\n expect(canBeNull(schema)).toBe(true);\n });\n\n it(\"should return true for schema with anyOf containing type null\", () => {\n const schema: JSONSchema7 = {\n anyOf: [{ type: \"string\" }, { type: \"null\" }],\n };\n expect(canBeNull(schema)).toBe(true);\n });\n\n it(\"should return true for deeply nested anyOf containing type null\", () => {\n const schema: JSONSchema7 = {\n anyOf: [\n { type: \"string\" },\n {\n anyOf: [{ type: \"number\" }, { type: \"null\" }],\n },\n ],\n };\n expect(canBeNull(schema)).toBe(true);\n });\n\n it(\"should return false for non-object schema\", () => {\n expect(canBeNull(true)).toBe(false);\n expect(canBeNull(\"string\" as JSONSchema7Definition)).toBe(false);\n });\n\n it(\"should return false for schema without null type\", () => {\n const schema: JSONSchema7 = { type: \"string\" };\n expect(canBeNull(schema)).toBe(false);\n });\n\n it(\"should return false for schema with anyOf not containing null type\", () => {\n const schema: JSONSchema7 = {\n anyOf: [{ type: \"string\" }, { type: \"number\" }],\n };\n expect(canBeNull(schema)).toBe(false);\n });\n});\n\ndescribe(\"unstrictifyToolCallParamsFromSchema\", () => {\n it(\"should strip null values for optional non-nullable params\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n age: { type: \"number\" },\n },\n required: [\"name\"],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n name: \"John\",\n age: null,\n });\n\n expect(result).toEqual({ name: \"John\" });\n });\n\n it(\"should preserve pass-through params not in original schema\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n },\n required: [\"name\"],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n name: \"John\",\n _tambo_statusMessage: \"Processing...\",\n _tambo_displayMessage: \"Hello\",\n });\n\n expect(result).toEqual({\n name: \"John\",\n _tambo_statusMessage: \"Processing...\",\n _tambo_displayMessage: \"Hello\",\n });\n });\n\n it(\"should handle nested object unstrictification with pass-through params\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n user: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n email: { type: \"string\" },\n },\n required: [\"name\"],\n },\n },\n required: [\"user\"],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n user: { name: \"John\", email: null },\n _tambo_statusMessage: \"Updating user\",\n });\n\n expect(result).toEqual({\n user: { name: \"John\" },\n _tambo_statusMessage: \"Updating user\",\n });\n });\n\n it(\"should return params as-is for non-object schemas\", () => {\n const schema: JSONSchema7 = { type: \"string\" };\n const params = { name: \"John\", _tambo_foo: \"bar\" };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, params);\n\n expect(result).toEqual(params);\n });\n\n it(\"should handle empty params\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n },\n required: [],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {});\n\n expect(result).toEqual({});\n });\n\n it(\"should handle schema with no properties defined\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n anything: \"value\",\n _tambo_status: \"ok\",\n });\n\n // Only _tambo_* keys pass through; unknown keys are dropped\n expect(result).toEqual({\n _tambo_status: \"ok\",\n });\n });\n\n it(\"should drop hallucinated keys not in schema or _tambo_* prefix\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n city: { type: \"string\" },\n },\n required: [\"city\"],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n city: \"NYC\",\n temperatur: \"celsius\",\n _tambo_statusMessage: \"Fetching\",\n });\n\n expect(result).toEqual({\n city: \"NYC\",\n _tambo_statusMessage: \"Fetching\",\n });\n });\n\n it(\"should handle arrays with nested objects\", () => {\n const schema: JSONSchema7 = {\n type: \"object\",\n properties: {\n targets: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n range: { type: \"string\" },\n style: {\n type: \"object\",\n properties: {\n bold: { type: \"boolean\" },\n fontFamily: { type: \"string\" },\n },\n required: [],\n },\n },\n required: [\"style\"],\n },\n },\n },\n required: [\"targets\"],\n };\n\n const result = unstrictifyToolCallParamsFromSchema(schema, {\n targets: [\n { range: \"A1:B1\", style: { bold: true, fontFamily: null } },\n { range: \"A2:A6\", style: { bold: false, fontFamily: null } },\n ],\n });\n\n expect(result).toEqual({\n targets: [\n { range: \"A1:B1\", style: { bold: true } },\n { range: \"A2:A6\", style: { bold: false } },\n ],\n });\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use-tambo-v1-send-message.d.ts","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-send-message.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AAMtE,OAAO,EAEL,KAAK,oBAAoB,IAAI,aAAa,EAC3C,MAAM,yCAAyC,CAAC;AAQjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA6MvD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,KAAK,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC7D,KAAK,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAgFD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CA4DhC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM;;;;uCA+PpD"}
1
+ {"version":3,"file":"use-tambo-v1-send-message.d.ts","sourceRoot":"","sources":["../../../src/v1/hooks/use-tambo-v1-send-message.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yCAAyC,CAAC;AAMtE,OAAO,EAEL,KAAK,oBAAoB,IAAI,aAAa,EAC3C,MAAM,yCAAyC,CAAC;AAQjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA4KvD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,KAAK,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAC7D,KAAK,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAgFD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CA4DhC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM;;;;uCAiQpD"}
@@ -19,7 +19,6 @@ import { toAvailableComponents, toAvailableTools, } from "../utils/registry-conv
19
19
  import { handleEventStream } from "../utils/stream-handler.js";
20
20
  import { executeAllPendingTools, createThrottledStreamableExecutor, } from "../utils/tool-executor.js";
21
21
  import { ToolCallTracker } from "../utils/tool-call-tracker.js";
22
- import { parse as parsePartialJson } from "partial-json";
23
22
  /**
24
23
  * Dispatches synthetic AG-UI events to show a user message in the thread.
25
24
  * @param dispatch - Stream state dispatcher
@@ -115,33 +114,6 @@ function shouldGenerateThreadName(threadId, threadAlreadyHasName, preMutationMes
115
114
  // +2 accounts for the user message and assistant response just added
116
115
  preMutationMessageCount + 2 >= autoGenerateNameThreshold);
117
116
  }
118
- /**
119
- * Attempts to parse partial JSON from accumulated tool call args.
120
- *
121
- * Returns a parsed object if the accumulated args are parseable as
122
- * a JSON object, or undefined if parsing fails or the result is not
123
- * a plain object (e.g. array or primitive).
124
- * @param toolTracker - Tracker holding pending tool call state
125
- * @param toolCallId - The tool call ID to parse args for
126
- * @returns Parsed args object, or undefined if not parseable yet
127
- */
128
- function parseToolCallArgs(toolTracker, toolCallId) {
129
- const accToolCall = toolTracker.getAccumulatingToolCall(toolCallId);
130
- if (!accToolCall)
131
- return undefined;
132
- try {
133
- const parsed = parsePartialJson(accToolCall.accumulatedArgs);
134
- if (typeof parsed === "object" &&
135
- parsed !== null &&
136
- !Array.isArray(parsed)) {
137
- return parsed;
138
- }
139
- }
140
- catch {
141
- /* not parseable yet */
142
- }
143
- return undefined;
144
- }
145
117
  /**
146
118
  * Generates a thread name via the beta API, dispatches the name update,
147
119
  * and invalidates the thread list cache. Errors are logged, never thrown.
@@ -341,7 +313,7 @@ export function useTamboSendMessage(threadId) {
341
313
  const existingThread = streamState.threadMap[apiThreadId ?? ""];
342
314
  const preMutationMessageCount = existingThread?.thread.messages.length ?? 0;
343
315
  const threadAlreadyHasName = !!existingThread?.thread.name;
344
- const toolTracker = new ToolCallTracker();
316
+ const toolTracker = new ToolCallTracker(registry.toolRegistry);
345
317
  const throttledStreamable = createThrottledStreamableExecutor(toolTracker, registry.toolRegistry);
346
318
  // Generate a stable message ID for the user message
347
319
  const userMessageId = userMessageText
@@ -405,19 +377,20 @@ export function useTamboSendMessage(threadId) {
405
377
  toolTracker.handleEvent(event);
406
378
  // Parse partial JSON once for TOOL_CALL_ARGS — reused by both dispatch and streamable execution
407
379
  const parsedToolArgs = event.type === EventType.TOOL_CALL_ARGS
408
- ? parseToolCallArgs(toolTracker, event.toolCallId)
380
+ ? toolTracker.parsePartialArgs(event.toolCallId)
409
381
  : undefined;
410
382
  dispatch({
411
383
  type: "EVENT",
412
384
  event,
413
385
  threadId: actualThreadId,
414
386
  parsedToolArgs,
387
+ toolSchemas: toolTracker.toolSchemas,
415
388
  });
416
389
  // Schedule debounced streamable tool execution with the same pre-parsed args
417
390
  if (parsedToolArgs && event.type === EventType.TOOL_CALL_ARGS) {
418
391
  throttledStreamable.schedule(event.toolCallId, parsedToolArgs);
419
392
  }
420
- // Check for awaiting_input - if found, break to execute tools
393
+ // Handle custom events
421
394
  if (event.type === EventType.CUSTOM) {
422
395
  const customEvent = asTamboCustomEvent(event);
423
396
  if (customEvent?.name === "tambo.run.awaiting_input") {