@gajae-code/ai 0.12.11 → 0.12.13
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 +27 -0
- package/dist/types/providers/anthropic.d.ts +13 -0
- package/dist/types/providers/openai-responses-shared.d.ts +1 -0
- package/dist/types/types.d.ts +5 -3
- package/dist/types/utils/idle-iterator.d.ts +17 -0
- package/dist/types/utils/tool-choice-capability.d.ts +5 -0
- package/dist/types/utils.d.ts +28 -0
- package/package.json +2 -2
- package/src/model-thinking.ts +34 -11
- package/src/models.json +52 -118
- package/src/provider-models/descriptors.ts +3 -3
- package/src/providers/anthropic.ts +57 -9
- package/src/providers/azure-openai-responses.ts +20 -4
- package/src/providers/openai-codex-responses.ts +31 -6
- package/src/providers/openai-completions.ts +3 -21
- package/src/providers/openai-responses-shared.ts +26 -0
- package/src/providers/openai-responses.ts +8 -26
- package/src/providers/register-builtins.ts +52 -27
- package/src/types.ts +9 -3
- package/src/utils/idle-iterator.ts +31 -0
- package/src/utils/tool-choice-capability.ts +33 -1
- package/src/utils/validation.ts +5 -0
- package/src/utils.ts +56 -0
|
@@ -165,7 +165,10 @@ export function resolveToolChoice(
|
|
|
165
165
|
|
|
166
166
|
/** Detects provider errors indicating forced tool_choice is unsupported. */
|
|
167
167
|
export function isForcedToolChoiceUnsupportedError(error: unknown, sentForcedToolChoice: boolean): boolean {
|
|
168
|
-
|
|
168
|
+
const status = extractHttpStatusFromError(error);
|
|
169
|
+
if (!sentForcedToolChoice || status !== 400) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
169
172
|
const message = errorMessage(error);
|
|
170
173
|
return (
|
|
171
174
|
// `by <something>` continuations ("not supported by billing") describe a
|
|
@@ -179,6 +182,35 @@ export function isForcedToolChoiceUnsupportedError(error: unknown, sentForcedToo
|
|
|
179
182
|
/tool[_\s-]?choices?\s+['"`][^'"`\r\n]+['"`]\s+not\s+found\s+in\s+['"`]tools['"`]\s+parameter\b/is.test(message)
|
|
180
183
|
);
|
|
181
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Detects Codex's statusless SSE rejection for a named function tool choice.
|
|
187
|
+
* This is intentionally separate from the shared HTTP-400 classifier.
|
|
188
|
+
*/
|
|
189
|
+
export function isCodexStatuslessNamedToolChoiceNotFoundError(
|
|
190
|
+
error: unknown,
|
|
191
|
+
forcedToolName: string | undefined,
|
|
192
|
+
sentToolNames: readonly string[],
|
|
193
|
+
): boolean {
|
|
194
|
+
if (
|
|
195
|
+
extractHttpStatusFromError(error) !== undefined ||
|
|
196
|
+
extractProviderErrorCode(error) !== "invalid_request_error" ||
|
|
197
|
+
!forcedToolName
|
|
198
|
+
) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
const match =
|
|
202
|
+
/^Tool choice '([^']+)' not found in 'tools' parameter\.$/.exec(errorMessage(error)) ??
|
|
203
|
+
/^Codex error event: Tool choice '([^']+)' not found in 'tools' parameter\. \(code=invalid_request_error\)$/.exec(
|
|
204
|
+
errorMessage(error),
|
|
205
|
+
);
|
|
206
|
+
return match?.[1] === forcedToolName && sentToolNames.includes(forcedToolName);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function extractProviderErrorCode(error: unknown): string | undefined {
|
|
210
|
+
if (!error || typeof error !== "object") return undefined;
|
|
211
|
+
const code = (error as { code?: unknown }).code;
|
|
212
|
+
return typeof code === "string" ? code : undefined;
|
|
213
|
+
}
|
|
182
214
|
|
|
183
215
|
export type { ToolChoiceCompat, ToolChoiceSupport, ToolChoiceSupportSource } from "../types";
|
|
184
216
|
|
package/src/utils/validation.ts
CHANGED
|
@@ -965,6 +965,11 @@ const RAW_ARGUMENT_REJECTION_MESSAGES: Record<RawArgumentRejectionCode, string>
|
|
|
965
965
|
"deepInterview.intent_contract requires non-empty items and confirmation_options",
|
|
966
966
|
"ask-deep-interview-metadata-requires-deep-interview-gate":
|
|
967
967
|
"deepInterview metadata cannot be combined with a non-deep-interview workflowGate",
|
|
968
|
+
"todo-write-unknown-root-key": "todo_write root accepts only an ops array of operation entries",
|
|
969
|
+
"todo-write-unknown-op-entry-key":
|
|
970
|
+
"todo_write operation entries accept only op, list, task, phase, items, and text keys",
|
|
971
|
+
"todo-write-done-drop-requires-target": "todo_write done and drop entries require a task or phase target",
|
|
972
|
+
"todo-write-unknown-init-entry-key": "todo_write init list entries accept only phase and items keys",
|
|
968
973
|
};
|
|
969
974
|
|
|
970
975
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -199,6 +199,62 @@ function asLowerString(value: unknown): string | undefined {
|
|
|
199
199
|
return typeof value === "string" ? value.toLowerCase() : undefined;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Shape-tolerant classifier for the DeepSeek-family reasoning-content replay
|
|
204
|
+
* rejection: "The `reasoning_content` in the thinking mode must be passed back
|
|
205
|
+
* to the API." DeepSeek V4 (and reasoning-capable siblings reached through any
|
|
206
|
+
* OpenAI-compatible proxy) 400 every follow-up turn once a prior assistant turn
|
|
207
|
+
* carried reasoning the proxy stripped to an empty `encrypted_content` /
|
|
208
|
+
* `reasoning_content`. Resending the identical history re-triggers it, so naive
|
|
209
|
+
* session auto-retry just burns the budget — it needs the same bounded
|
|
210
|
+
* repair-and-resend contract as the `invalid_prompt` poisoned-history breaker.
|
|
211
|
+
*
|
|
212
|
+
* Accepts a raw provider error, an assistant message, or any object carrying an
|
|
213
|
+
* `errorMessage` field (the agent-loop circuit breaker keys on this).
|
|
214
|
+
*/
|
|
215
|
+
export function isReasoningContentReplayError(input: unknown): boolean {
|
|
216
|
+
if (!input) return false;
|
|
217
|
+
const message =
|
|
218
|
+
typeof input === "string"
|
|
219
|
+
? input
|
|
220
|
+
: input && typeof input === "object"
|
|
221
|
+
? ((input as { errorMessage?: unknown; message?: unknown }).errorMessage ??
|
|
222
|
+
(input as { message?: unknown }).message)
|
|
223
|
+
: undefined;
|
|
224
|
+
return typeof message === "string" && REASONING_CONTENT_REPLAY_MESSAGE_RE.test(message);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const REASONING_CONTENT_REPLAY_MESSAGE_RE =
|
|
228
|
+
/reasoning_content[\s\S]*must be passed back to the API|reasoning content[\s\S]*must be passed back to the API/i;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Remove Responses-API `reasoning` items whose `encrypted_content` is missing or
|
|
232
|
+
* empty from an outgoing history payload. DeepSeek rejects replay of reasoning
|
|
233
|
+
* whose encrypted blob a proxy stripped to `""`; dropping those items lets the
|
|
234
|
+
* model re-reason on the next turn instead of re-triggering a deterministic 400.
|
|
235
|
+
* Non-reasoning items (text, function_call, function_call_output, ...) are kept
|
|
236
|
+
* verbatim so tool-use pairing and message order are preserved. Returns whether
|
|
237
|
+
* any item was actually removed — the circuit breaker uses this to decide
|
|
238
|
+
* between a single repaired resend (removed) and immediate fail-fast (unchanged).
|
|
239
|
+
*/
|
|
240
|
+
export function stripUnusableReasoningItems(items: Array<Record<string, unknown>>): {
|
|
241
|
+
result: Array<Record<string, unknown>>;
|
|
242
|
+
removed: number;
|
|
243
|
+
} {
|
|
244
|
+
let removed = 0;
|
|
245
|
+
const result: Array<Record<string, unknown>> = [];
|
|
246
|
+
for (const item of items) {
|
|
247
|
+
if (item?.type === "reasoning") {
|
|
248
|
+
const encrypted = item.encrypted_content;
|
|
249
|
+
if (encrypted === undefined || encrypted === null || encrypted === "") {
|
|
250
|
+
removed++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
result.push(item);
|
|
255
|
+
}
|
|
256
|
+
return { result, removed };
|
|
257
|
+
}
|
|
202
258
|
/**
|
|
203
259
|
* Neutralize leaked reserved control tokens across every string in an outgoing
|
|
204
260
|
* Responses `input` array. This is the request-boundary complement to the
|