@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.
- package/README.md +42 -20
- package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-send-message.js +4 -31
- package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
- package/dist/v1/utils/event-accumulator.d.ts +3 -0
- package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
- package/dist/v1/utils/event-accumulator.js +26 -5
- package/dist/v1/utils/event-accumulator.js.map +1 -1
- package/dist/v1/utils/event-accumulator.test.js +113 -0
- package/dist/v1/utils/event-accumulator.test.js.map +1 -1
- package/dist/v1/utils/tool-call-tracker.d.ts +26 -4
- package/dist/v1/utils/tool-call-tracker.d.ts.map +1 -1
- package/dist/v1/utils/tool-call-tracker.js +82 -5
- package/dist/v1/utils/tool-call-tracker.js.map +1 -1
- package/dist/v1/utils/tool-call-tracker.test.js +178 -0
- package/dist/v1/utils/tool-call-tracker.test.js.map +1 -1
- package/dist/v1/utils/unstrictify.d.ts +32 -0
- package/dist/v1/utils/unstrictify.d.ts.map +1 -0
- package/dist/v1/utils/unstrictify.js +159 -0
- package/dist/v1/utils/unstrictify.js.map +1 -0
- package/dist/v1/utils/unstrictify.test.d.ts +2 -0
- package/dist/v1/utils/unstrictify.test.d.ts.map +1 -0
- package/dist/v1/utils/unstrictify.test.js +187 -0
- package/dist/v1/utils/unstrictify.test.js.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-send-message.js +4 -31
- package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
- package/esm/v1/utils/event-accumulator.d.ts +3 -0
- package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
- package/esm/v1/utils/event-accumulator.js +26 -5
- package/esm/v1/utils/event-accumulator.js.map +1 -1
- package/esm/v1/utils/event-accumulator.test.js +113 -0
- package/esm/v1/utils/event-accumulator.test.js.map +1 -1
- package/esm/v1/utils/tool-call-tracker.d.ts +26 -4
- package/esm/v1/utils/tool-call-tracker.d.ts.map +1 -1
- package/esm/v1/utils/tool-call-tracker.js +82 -5
- package/esm/v1/utils/tool-call-tracker.js.map +1 -1
- package/esm/v1/utils/tool-call-tracker.test.js +178 -0
- package/esm/v1/utils/tool-call-tracker.test.js.map +1 -1
- package/esm/v1/utils/unstrictify.d.ts +32 -0
- package/esm/v1/utils/unstrictify.d.ts.map +1 -0
- package/esm/v1/utils/unstrictify.js +155 -0
- package/esm/v1/utils/unstrictify.js.map +1 -0
- package/esm/v1/utils/unstrictify.test.d.ts +2 -0
- package/esm/v1/utils/unstrictify.test.d.ts.map +1 -0
- package/esm/v1/utils/unstrictify.test.js +185 -0
- package/esm/v1/utils/unstrictify.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
/**
|
|
14
|
+
* Unstrictify the parameters of a tool call request.
|
|
15
|
+
*
|
|
16
|
+
* This effectively reverses the process of strictifyToolCallParams, for a
|
|
17
|
+
* tool call request that was built from a strict JSON Schema, by returning a
|
|
18
|
+
* updated tool call request with the parameter values unstrictified.
|
|
19
|
+
*/
|
|
20
|
+
function unstrictifyToolCallParams(originalToolParamSchema, toolCallRequestParams) {
|
|
21
|
+
if (originalToolParamSchema.type !== "object") {
|
|
22
|
+
throw new Error(`tool call parameter schema must be an object, instead got ${originalToolParamSchema.type} / ${typeof originalToolParamSchema}`);
|
|
23
|
+
}
|
|
24
|
+
const newParams = Object.entries(toolCallRequestParams)
|
|
25
|
+
.map(([parameterName, parameterValue]) => {
|
|
26
|
+
const isRequired = originalToolParamSchema.required?.includes(parameterName);
|
|
27
|
+
// find the param in the original tool schema
|
|
28
|
+
const originalParamSchema = parameterName in (originalToolParamSchema.properties ?? {})
|
|
29
|
+
? originalToolParamSchema.properties?.[parameterName]
|
|
30
|
+
: undefined;
|
|
31
|
+
// This should never happen, because the strict schema was derived from
|
|
32
|
+
// the original schema, so the parameter should always be present.
|
|
33
|
+
if (!originalParamSchema) {
|
|
34
|
+
throw new Error(`Tool call request parameter ${parameterName} not found in original tool`);
|
|
35
|
+
}
|
|
36
|
+
if (parameterValue === null &&
|
|
37
|
+
!canBeNull(originalParamSchema) &&
|
|
38
|
+
!isRequired) {
|
|
39
|
+
// This is the meat of this function. In the strict schema, this is
|
|
40
|
+
// "required and can be null", but in the original schema, the param was
|
|
41
|
+
// not required.
|
|
42
|
+
if (typeof originalParamSchema === "object" &&
|
|
43
|
+
"default" in originalParamSchema) {
|
|
44
|
+
return [parameterName, originalParamSchema.default];
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
// recurse into arrays
|
|
49
|
+
if (typeof originalParamSchema === "object" &&
|
|
50
|
+
originalParamSchema.type === "array") {
|
|
51
|
+
const arrayValue = parameterValue;
|
|
52
|
+
const itemSchema = originalParamSchema.items;
|
|
53
|
+
if (Array.isArray(arrayValue) &&
|
|
54
|
+
itemSchema &&
|
|
55
|
+
typeof itemSchema === "object" &&
|
|
56
|
+
!Array.isArray(itemSchema)) {
|
|
57
|
+
const newArrayValue = arrayValue.map((item) => {
|
|
58
|
+
if (itemSchema.type === "object" &&
|
|
59
|
+
typeof item === "object" &&
|
|
60
|
+
item !== null) {
|
|
61
|
+
// recurse into each object in the array
|
|
62
|
+
return unstrictifyToolCallParams(itemSchema, item);
|
|
63
|
+
}
|
|
64
|
+
return item;
|
|
65
|
+
});
|
|
66
|
+
return [parameterName, newArrayValue];
|
|
67
|
+
}
|
|
68
|
+
return [parameterName, parameterValue];
|
|
69
|
+
}
|
|
70
|
+
// recurse into the parameter value, passing along the matching original schema
|
|
71
|
+
if (typeof originalParamSchema === "object" &&
|
|
72
|
+
originalParamSchema.type === "object") {
|
|
73
|
+
// If the LLM sent a JSON string instead of an object (common with z.any() schemas),
|
|
74
|
+
// try to parse it
|
|
75
|
+
let objectValue = parameterValue;
|
|
76
|
+
if (typeof parameterValue === "string") {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(parameterValue);
|
|
79
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
80
|
+
objectValue = parsed;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Not valid JSON, keep original value
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Only recurse if we have an actual object AND the schema has properties defined.
|
|
88
|
+
// If the schema has no properties (e.g., z.any() which produces {type: 'object', anyOf: [...]}),
|
|
89
|
+
// just return the value as-is without recursing.
|
|
90
|
+
const hasProperties = originalParamSchema.properties &&
|
|
91
|
+
Object.keys(originalParamSchema.properties).length > 0;
|
|
92
|
+
if (hasProperties &&
|
|
93
|
+
typeof objectValue === "object" &&
|
|
94
|
+
objectValue !== null &&
|
|
95
|
+
!Array.isArray(objectValue)) {
|
|
96
|
+
const newParamValue = unstrictifyToolCallParams(originalParamSchema, objectValue);
|
|
97
|
+
return [parameterName, newParamValue];
|
|
98
|
+
}
|
|
99
|
+
// Return the (possibly parsed) object value without recursing
|
|
100
|
+
return [parameterName, objectValue];
|
|
101
|
+
}
|
|
102
|
+
return [parameterName, parameterValue];
|
|
103
|
+
})
|
|
104
|
+
.filter((param) => param !== undefined);
|
|
105
|
+
return Object.fromEntries(newParams);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Unstrictify tool call params using the original JSON Schema.
|
|
109
|
+
*
|
|
110
|
+
* Unlike the private `unstrictifyToolCallParams` which throws on unknown params,
|
|
111
|
+
* this function separates params into schema-defined vs `_tambo_*` pass-through
|
|
112
|
+
* (server-injected params not in the original schema), unstrictifies only the
|
|
113
|
+
* schema-defined ones, and merges pass-through params back. Unknown keys that
|
|
114
|
+
* aren't in the schema and don't have the `_tambo_` prefix are dropped.
|
|
115
|
+
* @returns The params with strictification-induced nulls stripped for optional
|
|
116
|
+
* non-nullable properties, and pass-through params preserved as-is.
|
|
117
|
+
*/
|
|
118
|
+
export function unstrictifyToolCallParamsFromSchema(originalSchema, params) {
|
|
119
|
+
if (originalSchema.type !== "object") {
|
|
120
|
+
return params;
|
|
121
|
+
}
|
|
122
|
+
const schemaProperties = originalSchema.properties ?? {};
|
|
123
|
+
const schemaDefinedParams = {};
|
|
124
|
+
const passThroughParams = {};
|
|
125
|
+
for (const [key, value] of Object.entries(params)) {
|
|
126
|
+
if (key in schemaProperties) {
|
|
127
|
+
schemaDefinedParams[key] = value;
|
|
128
|
+
}
|
|
129
|
+
else if (key.startsWith("_tambo_")) {
|
|
130
|
+
passThroughParams[key] = value;
|
|
131
|
+
}
|
|
132
|
+
// Unknown keys not in schema and not _tambo_* are dropped —
|
|
133
|
+
// they're likely hallucinated by the model.
|
|
134
|
+
}
|
|
135
|
+
const unstrictified = unstrictifyToolCallParams(originalSchema, schemaDefinedParams);
|
|
136
|
+
return { ...unstrictified, ...passThroughParams };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a JSON Schema definition allows null values.
|
|
140
|
+
* @param originalSchema - The schema definition to check
|
|
141
|
+
* @returns True if the schema allows null values
|
|
142
|
+
*/
|
|
143
|
+
export function canBeNull(originalSchema) {
|
|
144
|
+
if (typeof originalSchema !== "object") {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (originalSchema.type === "null") {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (originalSchema.anyOf?.some((anyOf) => canBeNull(anyOf))) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
//# 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 @@
|
|
|
1
|
+
{"version":3,"file":"unstrictify.test.d.ts","sourceRoot":"","sources":["../../../src/v1/utils/unstrictify.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { canBeNull, unstrictifyToolCallParamsFromSchema } from "./unstrictify.js";
|
|
2
|
+
describe("canBeNull", () => {
|
|
3
|
+
it("should return true for schema with type null", () => {
|
|
4
|
+
const schema = { type: "null" };
|
|
5
|
+
expect(canBeNull(schema)).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it("should return true for schema with anyOf containing type null", () => {
|
|
8
|
+
const schema = {
|
|
9
|
+
anyOf: [{ type: "string" }, { type: "null" }],
|
|
10
|
+
};
|
|
11
|
+
expect(canBeNull(schema)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it("should return true for deeply nested anyOf containing type null", () => {
|
|
14
|
+
const schema = {
|
|
15
|
+
anyOf: [
|
|
16
|
+
{ type: "string" },
|
|
17
|
+
{
|
|
18
|
+
anyOf: [{ type: "number" }, { type: "null" }],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
expect(canBeNull(schema)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it("should return false for non-object schema", () => {
|
|
25
|
+
expect(canBeNull(true)).toBe(false);
|
|
26
|
+
expect(canBeNull("string")).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it("should return false for schema without null type", () => {
|
|
29
|
+
const schema = { type: "string" };
|
|
30
|
+
expect(canBeNull(schema)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
it("should return false for schema with anyOf not containing null type", () => {
|
|
33
|
+
const schema = {
|
|
34
|
+
anyOf: [{ type: "string" }, { type: "number" }],
|
|
35
|
+
};
|
|
36
|
+
expect(canBeNull(schema)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("unstrictifyToolCallParamsFromSchema", () => {
|
|
40
|
+
it("should strip null values for optional non-nullable params", () => {
|
|
41
|
+
const schema = {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
name: { type: "string" },
|
|
45
|
+
age: { type: "number" },
|
|
46
|
+
},
|
|
47
|
+
required: ["name"],
|
|
48
|
+
};
|
|
49
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
50
|
+
name: "John",
|
|
51
|
+
age: null,
|
|
52
|
+
});
|
|
53
|
+
expect(result).toEqual({ name: "John" });
|
|
54
|
+
});
|
|
55
|
+
it("should preserve pass-through params not in original schema", () => {
|
|
56
|
+
const schema = {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
name: { type: "string" },
|
|
60
|
+
},
|
|
61
|
+
required: ["name"],
|
|
62
|
+
};
|
|
63
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
64
|
+
name: "John",
|
|
65
|
+
_tambo_statusMessage: "Processing...",
|
|
66
|
+
_tambo_displayMessage: "Hello",
|
|
67
|
+
});
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
name: "John",
|
|
70
|
+
_tambo_statusMessage: "Processing...",
|
|
71
|
+
_tambo_displayMessage: "Hello",
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it("should handle nested object unstrictification with pass-through params", () => {
|
|
75
|
+
const schema = {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
user: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
name: { type: "string" },
|
|
82
|
+
email: { type: "string" },
|
|
83
|
+
},
|
|
84
|
+
required: ["name"],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["user"],
|
|
88
|
+
};
|
|
89
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
90
|
+
user: { name: "John", email: null },
|
|
91
|
+
_tambo_statusMessage: "Updating user",
|
|
92
|
+
});
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
user: { name: "John" },
|
|
95
|
+
_tambo_statusMessage: "Updating user",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it("should return params as-is for non-object schemas", () => {
|
|
99
|
+
const schema = { type: "string" };
|
|
100
|
+
const params = { name: "John", _tambo_foo: "bar" };
|
|
101
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, params);
|
|
102
|
+
expect(result).toEqual(params);
|
|
103
|
+
});
|
|
104
|
+
it("should handle empty params", () => {
|
|
105
|
+
const schema = {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
name: { type: "string" },
|
|
109
|
+
},
|
|
110
|
+
required: [],
|
|
111
|
+
};
|
|
112
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {});
|
|
113
|
+
expect(result).toEqual({});
|
|
114
|
+
});
|
|
115
|
+
it("should handle schema with no properties defined", () => {
|
|
116
|
+
const schema = {
|
|
117
|
+
type: "object",
|
|
118
|
+
};
|
|
119
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
120
|
+
anything: "value",
|
|
121
|
+
_tambo_status: "ok",
|
|
122
|
+
});
|
|
123
|
+
// Only _tambo_* keys pass through; unknown keys are dropped
|
|
124
|
+
expect(result).toEqual({
|
|
125
|
+
_tambo_status: "ok",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it("should drop hallucinated keys not in schema or _tambo_* prefix", () => {
|
|
129
|
+
const schema = {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
city: { type: "string" },
|
|
133
|
+
},
|
|
134
|
+
required: ["city"],
|
|
135
|
+
};
|
|
136
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
137
|
+
city: "NYC",
|
|
138
|
+
temperatur: "celsius",
|
|
139
|
+
_tambo_statusMessage: "Fetching",
|
|
140
|
+
});
|
|
141
|
+
expect(result).toEqual({
|
|
142
|
+
city: "NYC",
|
|
143
|
+
_tambo_statusMessage: "Fetching",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
it("should handle arrays with nested objects", () => {
|
|
147
|
+
const schema = {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
targets: {
|
|
151
|
+
type: "array",
|
|
152
|
+
items: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
range: { type: "string" },
|
|
156
|
+
style: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
bold: { type: "boolean" },
|
|
160
|
+
fontFamily: { type: "string" },
|
|
161
|
+
},
|
|
162
|
+
required: [],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ["style"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
required: ["targets"],
|
|
170
|
+
};
|
|
171
|
+
const result = unstrictifyToolCallParamsFromSchema(schema, {
|
|
172
|
+
targets: [
|
|
173
|
+
{ range: "A1:B1", style: { bold: true, fontFamily: null } },
|
|
174
|
+
{ range: "A2:A6", style: { bold: false, fontFamily: null } },
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
expect(result).toEqual({
|
|
178
|
+
targets: [
|
|
179
|
+
{ range: "A1:B1", style: { bold: true } },
|
|
180
|
+
{ range: "A2:A6", style: { bold: false } },
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
//# 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"]}
|