@sayknow-cli/ai 0.2.4 → 0.2.5
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/CHANGELOG.md +23 -0
- package/README.md +1 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -0
- package/dist/types/providers/anthropic.d.ts +0 -6
- package/dist/types/types.d.ts +11 -0
- package/dist/types/utils/schema/index.d.ts +1 -0
- package/dist/types/utils/schema/root-combinator.d.ts +12 -0
- package/package.json +2 -2
- package/src/model-thinking.ts +1 -1
- package/src/models.json +4974 -701
- package/src/models.ts +12 -5
- package/src/provider-models/openai-compat.ts +349 -60
- package/src/providers/amazon-bedrock.ts +2 -2
- package/src/providers/anthropic.ts +8 -123
- package/src/providers/azure-openai-responses.ts +2 -2
- package/src/providers/cursor.ts +2 -2
- package/src/providers/ollama.ts +2 -2
- package/src/providers/openai-codex-responses.ts +25 -5
- package/src/providers/openai-completions-compat.ts +2 -0
- package/src/providers/openai-completions.ts +12 -2
- package/src/providers/openai-responses-shared.ts +165 -85
- package/src/providers/openai-responses.ts +8 -2
- package/src/types.ts +11 -0
- package/src/utils/schema/index.ts +1 -0
- package/src/utils/schema/root-combinator.ts +143 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool `input_schema` root flattening.
|
|
3
|
+
*
|
|
4
|
+
* Tool/function-calling `input_schema` roots MUST be a single JSON Schema object.
|
|
5
|
+
* Bedrock Converse (`toolConfig.tools[*].toolSpec.inputSchema.json.type must be
|
|
6
|
+
* one of the following: object`), OpenAI strict mode, Gemini, and Anthropic all
|
|
7
|
+
* reject roots whose top level is a bare `oneOf`/`anyOf`/`allOf` combinator (the
|
|
8
|
+
* validators require `type: "object"`).
|
|
9
|
+
*
|
|
10
|
+
* Zod `z.union(...)` / `z.discriminatedUnion(...)` tool parameters serialize to
|
|
11
|
+
* exactly such a combinator root, so this normalization is provider-agnostic and
|
|
12
|
+
* runs inside the shared wire pipeline (`toolWireSchema`).
|
|
13
|
+
*/
|
|
14
|
+
import { isRecord } from "@sayknow-cli/utils";
|
|
15
|
+
import { COMBINATOR_KEYS } from "./fields";
|
|
16
|
+
import { spillToDescription } from "./spill";
|
|
17
|
+
|
|
18
|
+
/** `minItems` / `maxItems` apply to arrays; some validators reject them on `type: "object"`. */
|
|
19
|
+
function isJsonSchemaArrayNode(schema: Record<string, unknown>): boolean {
|
|
20
|
+
const t = schema.type;
|
|
21
|
+
if (t === "array") return true;
|
|
22
|
+
if (Array.isArray(t) && t.includes("array") && !t.includes("object")) return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** True when a JSON Schema node describes an object (explicit `type` or `properties`). */
|
|
27
|
+
export function isJsonSchemaObjectNode(schema: Record<string, unknown>): boolean {
|
|
28
|
+
if (isJsonSchemaArrayNode(schema)) return false;
|
|
29
|
+
if (schema.type === "object") return true;
|
|
30
|
+
if (Array.isArray(schema.type) && schema.type.includes("object")) return true;
|
|
31
|
+
if (isRecord(schema.properties)) return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getRequiredNames(schema: Record<string, unknown>): Set<string> {
|
|
36
|
+
return new Set(
|
|
37
|
+
Array.isArray(schema.required)
|
|
38
|
+
? schema.required.filter((entry): entry is string => typeof entry === "string")
|
|
39
|
+
: [],
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSingleLiteralValue(schema: unknown): unknown | undefined {
|
|
44
|
+
if (!isRecord(schema)) return undefined;
|
|
45
|
+
if (Object.hasOwn(schema, "const")) return schema.const;
|
|
46
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 1) return schema.enum[0];
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function describeRootBranch(index: number, branch: Record<string, unknown>, action: unknown): string {
|
|
51
|
+
const required = [...getRequiredNames(branch)];
|
|
52
|
+
const parts = [`Branch ${index + 1}`];
|
|
53
|
+
if (typeof action === "string" || typeof action === "number" || typeof action === "boolean") {
|
|
54
|
+
parts.push(`action ${JSON.stringify(action)}`);
|
|
55
|
+
}
|
|
56
|
+
if (required.length > 0) parts.push(`branch-required fields: ${required.join(", ")}`);
|
|
57
|
+
if (typeof branch.description === "string" && branch.description.length > 0) parts.push(branch.description);
|
|
58
|
+
return parts.join("; ");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function collectRootObjectBranches(schema: unknown): Record<string, unknown>[] | undefined {
|
|
62
|
+
if (!isRecord(schema)) return undefined;
|
|
63
|
+
if (isJsonSchemaObjectNode(schema)) return [schema];
|
|
64
|
+
|
|
65
|
+
const combinatorKeys = COMBINATOR_KEYS.filter(key => Array.isArray(schema[key]));
|
|
66
|
+
if (combinatorKeys.length === 0) return undefined;
|
|
67
|
+
|
|
68
|
+
const branches: Record<string, unknown>[] = [];
|
|
69
|
+
for (const key of combinatorKeys) {
|
|
70
|
+
const variants = schema[key];
|
|
71
|
+
if (!Array.isArray(variants) || variants.length === 0) return undefined;
|
|
72
|
+
for (const variant of variants) {
|
|
73
|
+
const nestedBranches = collectRootObjectBranches(variant);
|
|
74
|
+
if (nestedBranches === undefined) return undefined;
|
|
75
|
+
branches.push(...nestedBranches);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return branches;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Flatten a provider-emitted tool ROOT whose top level is a `oneOf`/`anyOf`/`allOf`
|
|
83
|
+
* combinator into one `type: "object"` schema: merge object-branch properties,
|
|
84
|
+
* derive the discriminant (`action`) enum, keep the common required set, and demote
|
|
85
|
+
* leftover combinators plus per-branch guidance into the description. Nested
|
|
86
|
+
* combinators (inside individual properties) are left untouched.
|
|
87
|
+
*
|
|
88
|
+
* Idempotent: a root that already lacks top-level combinators is returned unchanged.
|
|
89
|
+
*/
|
|
90
|
+
export function flattenToolRootCombinators(schema: Record<string, unknown>): Record<string, unknown> {
|
|
91
|
+
const rootCombinators = COMBINATOR_KEYS.filter(key => Array.isArray(schema[key]));
|
|
92
|
+
if (rootCombinators.length === 0) return schema;
|
|
93
|
+
const result: Record<string, unknown> = { ...schema };
|
|
94
|
+
|
|
95
|
+
const baseProperties = isRecord(result.properties) ? { ...result.properties } : {};
|
|
96
|
+
const flattenedBranches = rootCombinators
|
|
97
|
+
.map(key => ({ key, branches: collectRootObjectBranches({ [key]: result[key] }) }))
|
|
98
|
+
.find(entry => entry.branches !== undefined && entry.branches.length > 0);
|
|
99
|
+
|
|
100
|
+
result.type = "object";
|
|
101
|
+
result.properties = baseProperties;
|
|
102
|
+
result.additionalProperties = result.additionalProperties === undefined ? false : result.additionalProperties;
|
|
103
|
+
|
|
104
|
+
if (flattenedBranches?.branches !== undefined) {
|
|
105
|
+
const variants = flattenedBranches.branches;
|
|
106
|
+
const commonRequired = variants.map(variant => getRequiredNames(variant));
|
|
107
|
+
const required = [...commonRequired[0]].filter(name => commonRequired.every(set => set.has(name)));
|
|
108
|
+
const actionValues: unknown[] = [];
|
|
109
|
+
const guidance: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (const [index, branch] of variants.entries()) {
|
|
112
|
+
if (isRecord(branch.properties)) {
|
|
113
|
+
Object.assign(baseProperties, branch.properties);
|
|
114
|
+
const actionValue = getSingleLiteralValue(branch.properties.action);
|
|
115
|
+
if (actionValue !== undefined && !actionValues.includes(actionValue)) actionValues.push(actionValue);
|
|
116
|
+
guidance.push(describeRootBranch(index, branch, actionValue));
|
|
117
|
+
} else {
|
|
118
|
+
guidance.push(describeRootBranch(index, branch, undefined));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (actionValues.length > 0) {
|
|
123
|
+
const existingAction = isRecord(baseProperties.action) ? { ...baseProperties.action } : {};
|
|
124
|
+
delete existingAction.const;
|
|
125
|
+
baseProperties.action = { ...existingAction, enum: actionValues };
|
|
126
|
+
}
|
|
127
|
+
result.required = required;
|
|
128
|
+
spillToDescription(result, [
|
|
129
|
+
["rootCombinatorGuidance", guidance],
|
|
130
|
+
...rootCombinators
|
|
131
|
+
.filter(key => key !== flattenedBranches.key)
|
|
132
|
+
.map(key => [key, result[key]] as [string, unknown]),
|
|
133
|
+
]);
|
|
134
|
+
} else {
|
|
135
|
+
spillToDescription(
|
|
136
|
+
result,
|
|
137
|
+
rootCombinators.map(key => [key, result[key]]),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const key of COMBINATOR_KEYS) delete result[key];
|
|
142
|
+
return result;
|
|
143
|
+
}
|