@code-yeongyu/senpi-ai 2026.10.31-1 → 2026.11.2-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/dist/api/anthropic-messages.d.ts +13 -3
- package/dist/api/anthropic-messages.js +60 -174
- package/dist/api/openai-completions.js +19 -2
- package/dist/api/openai-responses-shared.d.ts +2 -0
- package/dist/api/openai-responses-shared.js +19 -3
- package/dist/api/openai-responses.js +30 -9
- package/dist/auth/credential-store.d.ts +3 -1
- package/dist/auth/credential-store.js +78 -0
- package/dist/auth/helpers.d.ts +1 -0
- package/dist/auth/helpers.js +1 -0
- package/dist/auth/oauth/kimi-coding.js +6 -4
- package/dist/auth/oauth/openai-chatgpt-model-catalog.d.ts +36 -0
- package/dist/auth/oauth/openai-chatgpt-model-catalog.js +104 -0
- package/dist/auth/oauth/openai-chatgpt.js +76 -0
- package/dist/auth/oauth/radius.js +1 -2
- package/dist/auth/oauth-refresh.d.ts +2 -0
- package/dist/auth/oauth-refresh.js +53 -7
- package/dist/auth/pool/failover.d.ts +0 -40
- package/dist/auth/pool/failover.js +0 -76
- package/dist/auth/pool/slots.d.ts +11 -1
- package/dist/auth/pool/slots.js +73 -1
- package/dist/auth/resolve.js +3 -0
- package/dist/auth/types.d.ts +18 -0
- package/dist/compat/extension-oauth-types.d.ts +1 -0
- package/dist/models.d.ts +4 -5
- package/dist/models.js +37 -2
- package/dist/provider-diagnostic.d.ts +2 -0
- package/dist/provider-diagnostic.js +6 -1
- package/dist/providers/chatgpt-subscription.js +2 -0
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/utils/chatgpt-subscription-auth.d.ts +7 -0
- package/dist/utils/chatgpt-subscription-auth.js +21 -4
- package/dist/utils/empty-response-errors.d.ts +1 -2
- package/dist/utils/empty-response-errors.js +1 -2
- package/dist/utils/oauth-refresh-error.d.ts +8 -1
- package/dist/utils/oauth-refresh-error.js +3 -1
- package/dist/utils/prompt-cache-ttl.d.ts +2 -1
- package/dist/utils/prompt-cache-ttl.js +2 -0
- package/dist/utils/provider-diagnostic-carrier.d.ts +10 -1
- package/dist/utils/provider-diagnostic-carrier.js +35 -0
- package/dist/utils/provider-diagnostic-sources.d.ts +2 -0
- package/dist/utils/provider-diagnostic-sources.js +5 -0
- package/dist/utils/provider-diagnostic-vocabulary.d.ts +1 -0
- package/dist/utils/provider-diagnostic-vocabulary.js +9 -0
- package/dist/utils/provider-retry.js +4 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +14 -3
- package/dist/utils/siwc-errors.d.ts +2 -0
- package/dist/utils/siwc-errors.js +2 -0
- package/dist/utils/tool-name-match.js +46 -1
- package/dist/utils/tool-schema-compat.js +35 -0
- package/dist/utils/validation.js +111 -4
- package/package.json +2 -2
- package/dist/auth/pool/classify.d.ts +0 -16
- package/dist/auth/pool/classify.js +0 -75
- package/dist/utils/claude-code-version-cache.d.ts +0 -12
- package/dist/utils/claude-code-version-cache.js +0 -60
- package/dist/utils/claude-code-version.d.ts +0 -78
- package/dist/utils/claude-code-version.js +0 -203
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import type { MessageCreateParamsNonStreaming } from "@anthropic-ai/sdk/resources/beta/messages/messages.js";
|
|
3
|
-
import type { AnthropicRefusalFallback, Context, Model, SimpleStreamOptions, StreamFunction, StreamOptions
|
|
3
|
+
import type { AnthropicRefusalFallback, Context, Model, SimpleStreamOptions, StreamFunction, StreamOptions } from "../types.ts";
|
|
4
4
|
export { getAnthropicCompat } from "../utils/prompt-cache-ttl.ts";
|
|
5
|
-
export declare const toClaudeCodeName: (name: string) => string;
|
|
6
|
-
export declare const fromClaudeCodeName: (name: string, tools?: Tool[]) => string;
|
|
7
5
|
export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
|
8
6
|
export type AnthropicThinkingDisplay = "summarized" | "omitted";
|
|
9
7
|
export interface AnthropicOptions extends StreamOptions {
|
|
@@ -72,6 +70,18 @@ export interface AnthropicOptions extends StreamOptions {
|
|
|
72
70
|
*/
|
|
73
71
|
client?: Anthropic;
|
|
74
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* A Claude OAuth token (`sk-ant-oat...`) offered as an API key. senpi no longer sends such a token
|
|
75
|
+
* to the Messages API while presenting itself as Claude Code (senpi#5131); the subscription path is
|
|
76
|
+
* the `anthropic-subscription` provider, which runs the official Claude Code CLI.
|
|
77
|
+
*/
|
|
78
|
+
export declare class ClaudeOAuthTokenRefusedError extends Error {
|
|
79
|
+
readonly code = "claude_oauth_token_refused";
|
|
80
|
+
readonly provider: string;
|
|
81
|
+
readonly modelId: string;
|
|
82
|
+
constructor(provider: string, modelId: string);
|
|
83
|
+
}
|
|
84
|
+
export declare function isClaudeOAuthTokenRefusedError(error: unknown): error is ClaudeOAuthTokenRefusedError;
|
|
75
85
|
export declare const stream: StreamFunction<"anthropic-messages", AnthropicOptions>;
|
|
76
86
|
export declare const streamSimple: StreamFunction<"anthropic-messages", SimpleStreamOptions>;
|
|
77
87
|
export declare function buildAnthropicWarmPromptCacheParams(model: Model<"anthropic-messages">, context: Context, options?: AnthropicOptions): MessageCreateParamsNonStreaming;
|
|
@@ -6,7 +6,6 @@ import { registerSessionResourceCleanup } from "../session-resources.js";
|
|
|
6
6
|
import { isVideoMimeType } from "../types.js";
|
|
7
7
|
import { combineAbortSignals } from "../utils/abort-signals.js";
|
|
8
8
|
import { pushAssistantProgress } from "../utils/assistant-capture.js";
|
|
9
|
-
import { claudeCodeVersionTooOldHint, getClaudeCodeVersion, isClaudeCodeVersionTooOldError, recoverClaudeCodeVersion, } from "../utils/claude-code-version.js";
|
|
10
9
|
import { splitDeferredTools } from "../utils/deferred-tools.js";
|
|
11
10
|
import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
|
|
12
11
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
@@ -73,49 +72,6 @@ function getCacheControl(model, cacheRetention, env) {
|
|
|
73
72
|
cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
|
|
74
73
|
};
|
|
75
74
|
}
|
|
76
|
-
// Stealth mode: Mimic Claude Code's identity and tool naming exactly.
|
|
77
|
-
// The bundled Claude Code version and the floor of the advertised `claude-cli/<version>`
|
|
78
|
-
// (see utils/claude-code-version.ts). Keep this exact declaration: a downstream installer
|
|
79
|
-
// (oh-my-openagent) rewrites it byte-for-byte in the installed dist and bundle.
|
|
80
|
-
const claudeCodeVersion = "2.1.306";
|
|
81
|
-
// Claude Code 2.x tool names (canonical casing)
|
|
82
|
-
// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
|
|
83
|
-
// To update: https://github.com/badlogic/cchistory
|
|
84
|
-
const claudeCodeTools = [
|
|
85
|
-
"Read",
|
|
86
|
-
"Write",
|
|
87
|
-
"Edit",
|
|
88
|
-
"Bash",
|
|
89
|
-
"Grep",
|
|
90
|
-
"Glob",
|
|
91
|
-
"AskUserQuestion",
|
|
92
|
-
"EnterPlanMode",
|
|
93
|
-
"ExitPlanMode",
|
|
94
|
-
"KillShell",
|
|
95
|
-
"NotebookEdit",
|
|
96
|
-
"Skill",
|
|
97
|
-
"Task",
|
|
98
|
-
"TaskOutput",
|
|
99
|
-
"TodoWrite",
|
|
100
|
-
"WebFetch",
|
|
101
|
-
"WebSearch",
|
|
102
|
-
];
|
|
103
|
-
const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
|
|
104
|
-
const ccToolAliases = new Map([["ask_user_question", "AskUserQuestion"]]);
|
|
105
|
-
// Convert tool name to CC canonical casing if it matches (case-insensitive)
|
|
106
|
-
export const toClaudeCodeName = (name) => ccToolAliases.get(name) ?? ccToolLookup.get(name.toLowerCase()) ?? name;
|
|
107
|
-
export const fromClaudeCodeName = (name, tools) => {
|
|
108
|
-
if (tools && tools.length > 0) {
|
|
109
|
-
const alias = [...ccToolAliases].find(([, wireName]) => wireName === name);
|
|
110
|
-
if (alias && tools.some((tool) => tool.name === alias[0]))
|
|
111
|
-
return alias[0];
|
|
112
|
-
const lowerName = name.toLowerCase();
|
|
113
|
-
const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);
|
|
114
|
-
if (matchedTool)
|
|
115
|
-
return matchedTool.name;
|
|
116
|
-
}
|
|
117
|
-
return name;
|
|
118
|
-
};
|
|
119
75
|
function convertContentBlocks(content) {
|
|
120
76
|
// If only text blocks, return as concatenated string for simplicity
|
|
121
77
|
const hasImages = content.some((c) => c.type === "image");
|
|
@@ -323,6 +279,28 @@ function assertRequestAuth(provider, apiKey, headers) {
|
|
|
323
279
|
if (!hasRequestAuth(apiKey, headers))
|
|
324
280
|
throw new Error(`No API key for provider: ${provider}`);
|
|
325
281
|
}
|
|
282
|
+
const CLAUDE_OAUTH_TOKEN_MARKER = "sk-ant-oat";
|
|
283
|
+
/**
|
|
284
|
+
* A Claude OAuth token (`sk-ant-oat...`) offered as an API key. senpi no longer sends such a token
|
|
285
|
+
* to the Messages API while presenting itself as Claude Code (senpi#5131); the subscription path is
|
|
286
|
+
* the `anthropic-subscription` provider, which runs the official Claude Code CLI.
|
|
287
|
+
*/
|
|
288
|
+
export class ClaudeOAuthTokenRefusedError extends Error {
|
|
289
|
+
constructor(provider, modelId) {
|
|
290
|
+
super(`${provider}/${modelId}: a Claude OAuth token (${CLAUDE_OAUTH_TOKEN_MARKER}...) is not accepted as an API key; senpi does not send Claude subscription tokens to the Anthropic API directly. For a Claude Pro/Max subscription sign in with /login anthropic-subscription (or set CLAUDE_CODE_OAUTH_TOKEN) and use anthropic-subscription/${modelId}; otherwise use an Anthropic API key.`);
|
|
291
|
+
this.code = "claude_oauth_token_refused";
|
|
292
|
+
this.name = "ClaudeOAuthTokenRefusedError";
|
|
293
|
+
this.provider = provider;
|
|
294
|
+
this.modelId = modelId;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export function isClaudeOAuthTokenRefusedError(error) {
|
|
298
|
+
return error instanceof ClaudeOAuthTokenRefusedError;
|
|
299
|
+
}
|
|
300
|
+
function assertNotClaudeOAuthToken(model, apiKey) {
|
|
301
|
+
if (apiKey?.includes(CLAUDE_OAUTH_TOKEN_MARKER))
|
|
302
|
+
throw new ClaudeOAuthTokenRefusedError(model.provider, model.id);
|
|
303
|
+
}
|
|
326
304
|
/**
|
|
327
305
|
* Anthropic SDK client that never runs the SDK's own credential chain
|
|
328
306
|
* (ANTHROPIC_PROFILE config files, federation env vars). Without this, every
|
|
@@ -410,7 +388,7 @@ function userMessageClosesServerTurn(message) {
|
|
|
410
388
|
return message.content.trim().length > 0;
|
|
411
389
|
return message.content.some((block) => (block.type === "text" && block.text.trim().length > 0) || block.type === "image");
|
|
412
390
|
}
|
|
413
|
-
function collectProviderNativeToolPairing(messages, model, deferredToolNames,
|
|
391
|
+
function collectProviderNativeToolPairing(messages, model, deferredToolNames, discardedFallbackToolCallIds) {
|
|
414
392
|
const resolvedUseIds = new Set();
|
|
415
393
|
const validResultIds = new Set();
|
|
416
394
|
// Uses whose turn can still be resumed. A same-model assistant resets the
|
|
@@ -462,10 +440,9 @@ function collectProviderNativeToolPairing(messages, model, deferredToolNames, no
|
|
|
462
440
|
// and the pending use resumable.
|
|
463
441
|
let emitsReferences = false;
|
|
464
442
|
for (const name of message.addedToolNames ?? []) {
|
|
465
|
-
|
|
466
|
-
if (!deferredToolNames.has(normalizedName) || loadedReferenceNames.has(normalizedName))
|
|
443
|
+
if (!deferredToolNames.has(name) || loadedReferenceNames.has(name))
|
|
467
444
|
continue;
|
|
468
|
-
loadedReferenceNames.add(
|
|
445
|
+
loadedReferenceNames.add(name);
|
|
469
446
|
emitsReferences = true;
|
|
470
447
|
}
|
|
471
448
|
if (emitsReferences)
|
|
@@ -926,7 +903,6 @@ async function* iterateAnthropicEvents(response, signal, onTransportActivity) {
|
|
|
926
903
|
export const stream = (model, context, options) => {
|
|
927
904
|
const stream = new AssistantMessageEventStream({ captureBlocks: true });
|
|
928
905
|
const normalizedContext = resolveTranscript(context, supportsMidConvoSystemMessages(model));
|
|
929
|
-
const currentTools = getCurrentTools(normalizedContext.messages);
|
|
930
906
|
(async () => {
|
|
931
907
|
const providerThinkingLevel = managedEffortForRequest(model, options);
|
|
932
908
|
const output = {
|
|
@@ -953,8 +929,6 @@ export const stream = (model, context, options) => {
|
|
|
953
929
|
const requestSignal = combinedAbort.signal;
|
|
954
930
|
try {
|
|
955
931
|
let client;
|
|
956
|
-
let isOAuth = false;
|
|
957
|
-
let advertisedClaudeCodeVersion;
|
|
958
932
|
let usageModel = model;
|
|
959
933
|
let inputTransformations;
|
|
960
934
|
let openClient;
|
|
@@ -967,6 +941,7 @@ export const stream = (model, context, options) => {
|
|
|
967
941
|
const federation = getAnthropicFederation(model, apiKey, optionsHeaders, options?.env);
|
|
968
942
|
if (!federation)
|
|
969
943
|
assertRequestAuth(model.provider, apiKey, optionsHeaders);
|
|
944
|
+
assertNotClaudeOAuthToken(model, apiKey);
|
|
970
945
|
let copilotDynamicHeaders;
|
|
971
946
|
if (model.provider === "github-copilot") {
|
|
972
947
|
const hasImages = hasCopilotVisionInput(normalizedContext.messages);
|
|
@@ -978,37 +953,12 @@ export const stream = (model, context, options) => {
|
|
|
978
953
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env, "short");
|
|
979
954
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
980
955
|
openClient = () => {
|
|
981
|
-
|
|
956
|
+
client = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, normalizedContext), options?.refusalFallbacks !== undefined && configuredBetaHeader(model, options) !== null, optionsHeaders, getAnthropicCompat(model).supportsGzipRequestBody
|
|
982
957
|
? withGzipRequestBodies(options?.fetch)
|
|
983
958
|
: options?.fetch, copilotDynamicHeaders, cacheSessionId, options?.env, federation);
|
|
984
|
-
client = created.client;
|
|
985
|
-
isOAuth = created.isOAuthToken;
|
|
986
|
-
advertisedClaudeCodeVersion = created.claudeCodeVersion;
|
|
987
959
|
};
|
|
988
960
|
openClient();
|
|
989
961
|
}
|
|
990
|
-
// One retry per request: a `claude_code_version_too_old` 400 names the version Anthropic
|
|
991
|
-
// wants, so the fingerprint is raised and the same request goes out again with it.
|
|
992
|
-
let claudeCodeVersionRetried = false;
|
|
993
|
-
const retryWithNewerClaudeCode = async (error) => {
|
|
994
|
-
if (claudeCodeVersionRetried ||
|
|
995
|
-
openClient === undefined ||
|
|
996
|
-
advertisedClaudeCodeVersion === undefined ||
|
|
997
|
-
!isClaudeCodeVersionTooOldError(error)) {
|
|
998
|
-
return false;
|
|
999
|
-
}
|
|
1000
|
-
const next = await recoverClaudeCodeVersion(error, claudeCodeVersion, advertisedClaudeCodeVersion, options?.env);
|
|
1001
|
-
if (next === undefined)
|
|
1002
|
-
return false;
|
|
1003
|
-
claudeCodeVersionRetried = true;
|
|
1004
|
-
openClient();
|
|
1005
|
-
return true;
|
|
1006
|
-
};
|
|
1007
|
-
const noteTooOldClaudeCode = (error) => {
|
|
1008
|
-
if (advertisedClaudeCodeVersion !== undefined && isClaudeCodeVersionTooOldError(error)) {
|
|
1009
|
-
error.message = `${error.message}\n${claudeCodeVersionTooOldHint(advertisedClaudeCodeVersion)}`;
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
962
|
const fallbackKey = unsignedThinkingFallbackKey(model, options?.sessionId);
|
|
1013
963
|
let unsignedThinkingReplay = fallbackKey && unsignedThinkingTextReplayFallbacks.has(fallbackKey)
|
|
1014
964
|
? "text"
|
|
@@ -1025,7 +975,7 @@ export const stream = (model, context, options) => {
|
|
|
1025
975
|
}
|
|
1026
976
|
let lastSentParams;
|
|
1027
977
|
const createRequest = async () => {
|
|
1028
|
-
let params = buildParams(model, normalizedContext,
|
|
978
|
+
let params = buildParams(model, normalizedContext, options, unsignedThinkingReplay);
|
|
1029
979
|
const nextParams = await options?.onPayload?.(params, model);
|
|
1030
980
|
if (nextParams !== undefined) {
|
|
1031
981
|
params = nextParams;
|
|
@@ -1110,16 +1060,6 @@ export const stream = (model, context, options) => {
|
|
|
1110
1060
|
}
|
|
1111
1061
|
if (markNewlyExpiredSearches(error))
|
|
1112
1062
|
return createRequestDemotingExpiredSearches();
|
|
1113
|
-
if (await retryWithNewerClaudeCode(error)) {
|
|
1114
|
-
try {
|
|
1115
|
-
return await createRequest();
|
|
1116
|
-
}
|
|
1117
|
-
catch (retryError) {
|
|
1118
|
-
noteTooOldClaudeCode(retryError);
|
|
1119
|
-
throw retryError;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
noteTooOldClaudeCode(error);
|
|
1123
1063
|
throw error;
|
|
1124
1064
|
}
|
|
1125
1065
|
}, {
|
|
@@ -1232,9 +1172,7 @@ export const stream = (model, context, options) => {
|
|
|
1232
1172
|
const block = {
|
|
1233
1173
|
type: "toolCall",
|
|
1234
1174
|
id: event.content_block.id,
|
|
1235
|
-
name:
|
|
1236
|
-
? fromClaudeCodeName(event.content_block.name, currentTools)
|
|
1237
|
-
: event.content_block.name,
|
|
1175
|
+
name: event.content_block.name,
|
|
1238
1176
|
arguments: isRecord(event.content_block.input)
|
|
1239
1177
|
? event.content_block.input
|
|
1240
1178
|
: {},
|
|
@@ -1594,6 +1532,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
1594
1532
|
if (!getAnthropicFederation(model, apiKey, optionsHeaders, options?.env)) {
|
|
1595
1533
|
assertRequestAuth(model.provider, apiKey, optionsHeaders);
|
|
1596
1534
|
}
|
|
1535
|
+
assertNotClaudeOAuthToken(model, apiKey);
|
|
1597
1536
|
const base = {
|
|
1598
1537
|
...buildBaseOptions(model, context, options, options?.apiKey),
|
|
1599
1538
|
toolChoice: options?.toolChoice,
|
|
@@ -1625,9 +1564,6 @@ export const streamSimple = (model, context, options) => {
|
|
|
1625
1564
|
thinkingBudgetTokens: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),
|
|
1626
1565
|
});
|
|
1627
1566
|
};
|
|
1628
|
-
function isOAuthToken(apiKey) {
|
|
1629
|
-
return apiKey.includes("sk-ant-oat");
|
|
1630
|
-
}
|
|
1631
1567
|
function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId, env, federation) {
|
|
1632
1568
|
// Adaptive thinking models have interleaved thinking built in, so skip the beta header.
|
|
1633
1569
|
const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model);
|
|
@@ -1657,7 +1593,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
1657
1593
|
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
1658
1594
|
}, model.headers, optionsHeaders)),
|
|
1659
1595
|
});
|
|
1660
|
-
return
|
|
1596
|
+
return client;
|
|
1661
1597
|
}
|
|
1662
1598
|
// Copilot: Bearer auth, selective betas.
|
|
1663
1599
|
if (model.provider === "github-copilot") {
|
|
@@ -1673,26 +1609,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
1673
1609
|
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
1674
1610
|
}, model.headers, dynamicHeaders, optionsHeaders)),
|
|
1675
1611
|
});
|
|
1676
|
-
return
|
|
1677
|
-
}
|
|
1678
|
-
// OAuth: Bearer auth, Claude Code identity headers
|
|
1679
|
-
if (apiKey && isOAuthToken(apiKey)) {
|
|
1680
|
-
const advertisedClaudeCodeVersion = getClaudeCodeVersion(claudeCodeVersion, env);
|
|
1681
|
-
const client = new PiAnthropic({
|
|
1682
|
-
apiKey: null,
|
|
1683
|
-
authToken: apiKey,
|
|
1684
|
-
baseURL: model.baseUrl,
|
|
1685
|
-
dangerouslyAllowBrowser: true,
|
|
1686
|
-
fetch,
|
|
1687
|
-
defaultHeaders: sanitizeAdaptiveThinkingHeaders(model, mergeClientHeaders(model, {
|
|
1688
|
-
accept: "application/json",
|
|
1689
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
1690
|
-
"anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
|
|
1691
|
-
"user-agent": `claude-cli/${advertisedClaudeCodeVersion}`,
|
|
1692
|
-
"x-app": "cli",
|
|
1693
|
-
}, model.headers, optionsHeaders)),
|
|
1694
|
-
});
|
|
1695
|
-
return { client, isOAuthToken: true, claudeCodeVersion: advertisedClaudeCodeVersion };
|
|
1612
|
+
return client;
|
|
1696
1613
|
}
|
|
1697
1614
|
// API key, header-owned auth, or workload identity federation.
|
|
1698
1615
|
const affinityCompat = getAnthropicCompat(model);
|
|
@@ -1721,9 +1638,9 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
1721
1638
|
});
|
|
1722
1639
|
federationClient = { key, fetch, client };
|
|
1723
1640
|
}
|
|
1724
|
-
return
|
|
1641
|
+
return federationClient.client.withOptions({ defaultHeaders });
|
|
1725
1642
|
}
|
|
1726
|
-
|
|
1643
|
+
return new PiAnthropic({
|
|
1727
1644
|
apiKey: apiKey ?? null,
|
|
1728
1645
|
authToken: null,
|
|
1729
1646
|
baseURL: model.baseUrl,
|
|
@@ -1731,13 +1648,12 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
1731
1648
|
fetch,
|
|
1732
1649
|
defaultHeaders,
|
|
1733
1650
|
});
|
|
1734
|
-
return { client, isOAuthToken: false };
|
|
1735
1651
|
}
|
|
1736
1652
|
export function buildAnthropicWarmPromptCacheParams(model, context, options) {
|
|
1737
1653
|
// Public fork entry (warmPromptCache, cache-keepalive): accepts a raw Context and builds
|
|
1738
1654
|
// the same transcript the turn's stream() resolves, so the warmed prefix matches.
|
|
1739
1655
|
const transcript = resolveTranscript(normalizeContext(context), supportsMidConvoSystemMessages(model));
|
|
1740
|
-
const params = buildParams(model, transcript,
|
|
1656
|
+
const params = buildParams(model, transcript, {
|
|
1741
1657
|
...options,
|
|
1742
1658
|
maxTokens: 0,
|
|
1743
1659
|
thinkingEnabled: undefined,
|
|
@@ -1759,14 +1675,12 @@ function configuredBetaHeader(model, options) {
|
|
|
1759
1675
|
}
|
|
1760
1676
|
return configured;
|
|
1761
1677
|
}
|
|
1762
|
-
function getBetaFeatures(model, context,
|
|
1678
|
+
function getBetaFeatures(model, context, shape, options) {
|
|
1763
1679
|
const configured = configuredBetaHeader(model, options);
|
|
1764
1680
|
if (configured === null)
|
|
1765
1681
|
return [];
|
|
1766
1682
|
// Betas the request body depends on: the API rejects the request without them.
|
|
1767
1683
|
const required = [];
|
|
1768
|
-
if (isOAuthToken)
|
|
1769
|
-
required.push("claude-code-20250219", "oauth-2025-04-20");
|
|
1770
1684
|
if (shape.midConvoEffort)
|
|
1771
1685
|
required.push(MID_CONVERSATION_OUTPUT_CONFIG_BETA);
|
|
1772
1686
|
if (shape.thinkingBinding)
|
|
@@ -1801,7 +1715,7 @@ function defaultOptionalBetas(model, context, options) {
|
|
|
1801
1715
|
features.push(SERVER_SIDE_FALLBACK_BETA);
|
|
1802
1716
|
return features;
|
|
1803
1717
|
}
|
|
1804
|
-
function buildParams(model, context,
|
|
1718
|
+
function buildParams(model, context, options, unsignedThinkingReplay = getAnthropicCompat(model).unsignedThinkingReplay) {
|
|
1805
1719
|
const compat = getAnthropicCompat(model);
|
|
1806
1720
|
const { cacheControl } = getCacheControl(model, options?.cacheRetention ?? model.cacheRetention, options?.env);
|
|
1807
1721
|
const initialSystemMessage = getInitialSystemMessage(context.messages);
|
|
@@ -1816,7 +1730,7 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
|
|
|
1816
1730
|
// old boundary (read) and the new one (written for the next step) are marked. Two checkpoints fit
|
|
1817
1731
|
// only when the system block holds one, which then also covers the tools before it (senpi#3242).
|
|
1818
1732
|
const stablePrefixMarkers = conversationMessages.filter((message) => message.role === "assistant" && message.promptCacheBreakpoint === true).length;
|
|
1819
|
-
const systemHoldsCheckpoint =
|
|
1733
|
+
const systemHoldsCheckpoint = initialSystemText.length > 0;
|
|
1820
1734
|
const stablePrefixCheckpointLimit = stablePrefixMarkers >= 2 && systemHoldsCheckpoint ? 2 : Math.min(1, stablePrefixMarkers);
|
|
1821
1735
|
// Native tool changes reference tools by name, so a redefined name cannot be expressed,
|
|
1822
1736
|
// and Anthropic rejects a tool list where every tool is deferred, so there must be an
|
|
@@ -1824,18 +1738,14 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
|
|
|
1824
1738
|
const initialTools = initialSystemMessage?.toolsAdded ?? [];
|
|
1825
1739
|
// A configured `anthropic-beta: null` asks for no betas at all, so the request takes a shape that needs none:
|
|
1826
1740
|
// the current tool list instead of native tool changes, top-level effort instead of per-message markers, and
|
|
1827
|
-
// no server-side fallbacks (senpi#2957).
|
|
1741
|
+
// no server-side fallbacks (senpi#2957).
|
|
1828
1742
|
const betasSuppressed = configuredBetaHeader(model, options) === null;
|
|
1829
|
-
if (betasSuppressed && isOAuthToken) {
|
|
1830
|
-
throw new Error(`The anthropic-beta header is set to null, but ${model.provider}/${model.id} is called with an OAuth token, which needs the claude-code-20250219 and oauth-2025-04-20 betas. Remove the null, or list the betas you want instead; senpi adds the ones a request needs.`);
|
|
1831
|
-
}
|
|
1832
1743
|
const midConvoEffort = model.compat?.supportsMidConvoEffort === true && !betasSuppressed;
|
|
1833
1744
|
const nativeToolChanges = !betasSuppressed &&
|
|
1834
1745
|
supportsMidConvoSystemMessages(model) &&
|
|
1835
1746
|
supportsMidConvoToolChanges(model) &&
|
|
1836
1747
|
initialTools.length > 0 &&
|
|
1837
1748
|
!hasToolRedefinitions(context.messages);
|
|
1838
|
-
const normalizeToolName = isOAuthToken ? toClaudeCodeName : (name) => name;
|
|
1839
1749
|
// A marker on a discarded pre-fallback result must not defer its tool:
|
|
1840
1750
|
// convertMessages drops that result, so no tool_reference would replay to
|
|
1841
1751
|
// load the deferred definition.
|
|
@@ -1846,23 +1756,23 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
|
|
|
1846
1756
|
// `defer_loading` and loaded by a `tool_reference`. Native tool changes own deferral otherwise.
|
|
1847
1757
|
const toolPlacement = nativeToolChanges
|
|
1848
1758
|
? { immediate: [], deferred: new Map() }
|
|
1849
|
-
: splitDeferredTools({ tools: getCurrentTools(context.messages), messages: partitionMessages }, compat.supportsToolReferences
|
|
1759
|
+
: splitDeferredTools({ tools: getCurrentTools(context.messages), messages: partitionMessages }, compat.supportsToolReferences);
|
|
1850
1760
|
let immediateTools = toolPlacement.immediate;
|
|
1851
1761
|
let deferredTools = [...toolPlacement.deferred.values()];
|
|
1852
1762
|
if (immediateTools.length === 0 && deferredTools.length > 0) {
|
|
1853
1763
|
immediateTools = deferredTools;
|
|
1854
1764
|
deferredTools = [];
|
|
1855
1765
|
}
|
|
1856
|
-
const deferredToolNames = new Set(deferredTools.map((tool) =>
|
|
1766
|
+
const deferredToolNames = new Set(deferredTools.map((tool) => tool.name));
|
|
1857
1767
|
const activeEffort = managedEffortForRequest(model, options) ?? "high";
|
|
1858
1768
|
const sendFallbacks = options?.refusalFallbacks !== undefined && !betasSuppressed;
|
|
1859
|
-
const betaFeatures = getBetaFeatures(model, context,
|
|
1769
|
+
const betaFeatures = getBetaFeatures(model, context, {
|
|
1860
1770
|
midConvoEffort,
|
|
1861
1771
|
thinkingBinding: midConvoEffort && options?.thinkingEnabled !== false,
|
|
1862
1772
|
nativeToolChanges,
|
|
1863
1773
|
refusalFallbacks: sendFallbacks,
|
|
1864
1774
|
}, options);
|
|
1865
|
-
const convertedMessages = convertMessages(conversationMessages, model,
|
|
1775
|
+
const convertedMessages = convertMessages(conversationMessages, model, cacheControl, unsignedThinkingReplay, deferredToolNames, midConvoEffort ? model.provider : undefined, nativeToolChanges, stablePrefixCheckpointLimit);
|
|
1866
1776
|
const messages = midConvoEffort
|
|
1867
1777
|
? insertThinkingLevelMessages(convertedMessages, activeEffort)
|
|
1868
1778
|
: convertedMessages.messages;
|
|
@@ -1888,21 +1798,7 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
|
|
|
1888
1798
|
stream: true,
|
|
1889
1799
|
...(betaFeatures.length > 0 ? { betas: betaFeatures } : {}),
|
|
1890
1800
|
};
|
|
1891
|
-
|
|
1892
|
-
if (isOAuthToken) {
|
|
1893
|
-
params.system = [
|
|
1894
|
-
{
|
|
1895
|
-
type: "text",
|
|
1896
|
-
text: "You are Claude Code, Anthropic's official CLI for Claude.",
|
|
1897
|
-
...(!initialSystemText && cacheControl ? { cache_control: cacheControl } : {}),
|
|
1898
|
-
},
|
|
1899
|
-
];
|
|
1900
|
-
if (initialSystemText) {
|
|
1901
|
-
params.system.push(...anthropicSystemTextBlocks(initialSystemText, initialSystemMessage?.stablePrefix, cacheControl));
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
else if (initialSystemText) {
|
|
1905
|
-
// Add cache control to system prompt for non-OAuth tokens
|
|
1801
|
+
if (initialSystemText) {
|
|
1906
1802
|
params.system = anthropicSystemTextBlocks(initialSystemText, initialSystemMessage?.stablePrefix, cacheControl);
|
|
1907
1803
|
}
|
|
1908
1804
|
// Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
|
|
@@ -1923,15 +1819,15 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
|
|
|
1923
1819
|
const initialNames = new Set(initialTools.map((tool) => tool.name));
|
|
1924
1820
|
const laterTools = getDeclaredTools(context.messages).filter((tool) => !initialNames.has(tool.name));
|
|
1925
1821
|
params.tools = [
|
|
1926
|
-
...convertTools(initialTools,
|
|
1822
|
+
...convertTools(initialTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
|
|
1927
1823
|
DEFERRED_TOOL_PLACEHOLDER,
|
|
1928
|
-
...convertTools(laterTools,
|
|
1824
|
+
...convertTools(laterTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
|
|
1929
1825
|
];
|
|
1930
1826
|
}
|
|
1931
1827
|
else if (immediateTools.length > 0 || deferredTools.length > 0) {
|
|
1932
1828
|
params.tools = [
|
|
1933
|
-
...convertTools(immediateTools,
|
|
1934
|
-
...convertTools(deferredTools,
|
|
1829
|
+
...convertTools(immediateTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
|
|
1830
|
+
...convertTools(deferredTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
|
|
1935
1831
|
];
|
|
1936
1832
|
}
|
|
1937
1833
|
// Managed effort models always use adaptive thinking so prefix mismatches can
|
|
@@ -2052,17 +1948,13 @@ function finalAssistantTurnStartsWithToolUse(messages) {
|
|
|
2052
1948
|
}
|
|
2053
1949
|
return false;
|
|
2054
1950
|
}
|
|
2055
|
-
function convertToolResult(msg,
|
|
1951
|
+
function convertToolResult(msg, deferredToolNames, loadedToolNames) {
|
|
2056
1952
|
const references = [];
|
|
2057
1953
|
for (const name of msg.addedToolNames ?? []) {
|
|
2058
|
-
|
|
2059
|
-
if (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName))
|
|
1954
|
+
if (!deferredToolNames.has(name) || loadedToolNames.has(name))
|
|
2060
1955
|
continue;
|
|
2061
|
-
loadedToolNames.add(
|
|
2062
|
-
references.push({
|
|
2063
|
-
type: "tool_reference",
|
|
2064
|
-
tool_name: isOAuthToken ? toClaudeCodeName(name) : name,
|
|
2065
|
-
});
|
|
1956
|
+
loadedToolNames.add(name);
|
|
1957
|
+
references.push({ type: "tool_reference", tool_name: name });
|
|
2066
1958
|
}
|
|
2067
1959
|
// The video block variant is not in the SDK's ContentBlockParam union, so the
|
|
2068
1960
|
// converted array is cast through the same escape hatch as tool_reference blocks.
|
|
@@ -2082,7 +1974,7 @@ function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames
|
|
|
2082
1974
|
: convertedContent,
|
|
2083
1975
|
};
|
|
2084
1976
|
}
|
|
2085
|
-
function convertMessages(transformedMessages, model,
|
|
1977
|
+
function convertMessages(transformedMessages, model, cacheControl, unsignedThinkingReplay = "text", deferredToolNames = new Set(), managedProvider, nativeToolChanges = false, stablePrefixCheckpointLimit = 0) {
|
|
2086
1978
|
const params = [];
|
|
2087
1979
|
const assistantLevels = new Map();
|
|
2088
1980
|
const loadedToolNames = new Set();
|
|
@@ -2101,7 +1993,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
|
|
|
2101
1993
|
// assistant turn below; drop their tool_results in lockstep so none dangle.
|
|
2102
1994
|
const discardedFallbackToolCallIds = collectDiscardedFallbackToolCallIds(transformedMessages, model);
|
|
2103
1995
|
const rejectsNativeWebSearchReplay = !getAnthropicCompat(model).supportsWebSearch;
|
|
2104
|
-
const providerNativeToolPairing = collectProviderNativeToolPairing(transformedMessages, model, deferredToolNames,
|
|
1996
|
+
const providerNativeToolPairing = collectProviderNativeToolPairing(transformedMessages, model, deferredToolNames, discardedFallbackToolCallIds);
|
|
2105
1997
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
2106
1998
|
const msg = transformedMessages[i];
|
|
2107
1999
|
if (msg.role === "system") {
|
|
@@ -2113,16 +2005,10 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
|
|
|
2113
2005
|
blocks.push({ type: "text", text: sanitizeSurrogates(text) });
|
|
2114
2006
|
if (nativeToolChanges) {
|
|
2115
2007
|
for (const tool of msg.toolsRemoved ?? []) {
|
|
2116
|
-
blocks.push({
|
|
2117
|
-
type: "tool_removal",
|
|
2118
|
-
tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
|
|
2119
|
-
});
|
|
2008
|
+
blocks.push({ type: "tool_removal", tool: { type: "tool_reference", name: tool.name } });
|
|
2120
2009
|
}
|
|
2121
2010
|
for (const tool of msg.toolsAdded ?? []) {
|
|
2122
|
-
blocks.push({
|
|
2123
|
-
type: "tool_addition",
|
|
2124
|
-
tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
|
|
2125
|
-
});
|
|
2011
|
+
blocks.push({ type: "tool_addition", tool: { type: "tool_reference", name: tool.name } });
|
|
2126
2012
|
}
|
|
2127
2013
|
}
|
|
2128
2014
|
if (blocks.length > 0)
|
|
@@ -2253,7 +2139,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
|
|
|
2253
2139
|
blocks.push({
|
|
2254
2140
|
type: "tool_use",
|
|
2255
2141
|
id: block.id,
|
|
2256
|
-
name:
|
|
2142
|
+
name: block.name,
|
|
2257
2143
|
input: block.arguments ?? {},
|
|
2258
2144
|
});
|
|
2259
2145
|
}
|
|
@@ -2298,7 +2184,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
|
|
|
2298
2184
|
while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
|
|
2299
2185
|
const nextMsg = transformedMessages[j]; // We know it's a toolResult
|
|
2300
2186
|
if (!discardedFallbackToolCallIds.has(nextMsg.toolCallId)) {
|
|
2301
|
-
const converted = convertToolResult(nextMsg,
|
|
2187
|
+
const converted = convertToolResult(nextMsg, deferredToolNames, loadedToolNames);
|
|
2302
2188
|
toolResults.push(converted.toolResult);
|
|
2303
2189
|
siblingContent.push(...converted.siblingContent);
|
|
2304
2190
|
}
|
|
@@ -2390,7 +2276,7 @@ const isAnthropicStrictUnsupportedKeyword = (key, value) => {
|
|
|
2390
2276
|
return typeof value !== "string" || !ANTHROPIC_STRICT_STRING_FORMATS.has(value);
|
|
2391
2277
|
return false;
|
|
2392
2278
|
};
|
|
2393
|
-
function convertTools(tools,
|
|
2279
|
+
function convertTools(tools, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, deferLoading = false) {
|
|
2394
2280
|
if (!tools)
|
|
2395
2281
|
return [];
|
|
2396
2282
|
return tools.map((tool, index) => {
|
|
@@ -2411,7 +2297,7 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
|
|
|
2411
2297
|
}
|
|
2412
2298
|
: legacyInputSchema;
|
|
2413
2299
|
return {
|
|
2414
|
-
name:
|
|
2300
|
+
name: tool.name,
|
|
2415
2301
|
description: tool.description,
|
|
2416
2302
|
...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
|
|
2417
2303
|
...(strict === true ? { strict: true } : {}),
|
|
@@ -1500,7 +1500,24 @@ export function convertMessages(model, context, compat, options = {}) {
|
|
|
1500
1500
|
return params;
|
|
1501
1501
|
}
|
|
1502
1502
|
function convertTools(tools, compat) {
|
|
1503
|
-
|
|
1503
|
+
const strictByTool = tools.map((tool) => tool.freeform || resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools)
|
|
1504
|
+
? undefined
|
|
1505
|
+
: resolveJsonSchemaStrictSampling(tool, compat.supportsStrictMode !== false));
|
|
1506
|
+
if (compat.mixedStrictTools === false) {
|
|
1507
|
+
const nonStrictTool = tools.find((tool, index) => !tool.freeform &&
|
|
1508
|
+
!resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools) &&
|
|
1509
|
+
strictByTool[index] !== true);
|
|
1510
|
+
if (nonStrictTool) {
|
|
1511
|
+
const requiredTool = tools.find((tool) => tool.constrainedSampling &&
|
|
1512
|
+
tool.constrainedSampling.type === "json_schema" &&
|
|
1513
|
+
tool.constrainedSampling.strict === "require");
|
|
1514
|
+
if (requiredTool) {
|
|
1515
|
+
throw new Error(`Tool "${requiredTool.name}" requires JSON-schema constrained sampling, but tool "${nonStrictTool.name}" cannot be strict and this provider rejects mixed strict and non-strict tools.`);
|
|
1516
|
+
}
|
|
1517
|
+
strictByTool.fill(undefined);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return tools.map((tool, index) => {
|
|
1504
1521
|
const grammar = resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools);
|
|
1505
1522
|
if (grammar) {
|
|
1506
1523
|
return {
|
|
@@ -1521,7 +1538,7 @@ function convertTools(tools, compat) {
|
|
|
1521
1538
|
if (tool.freeform) {
|
|
1522
1539
|
throw new Error("Freeform tools cannot be sent to OpenAI Chat Completions; use Responses API");
|
|
1523
1540
|
}
|
|
1524
|
-
const strict =
|
|
1541
|
+
const strict = strictByTool[index];
|
|
1525
1542
|
const schemaParameters = getJsonSchemaToolParameters(tool, strict);
|
|
1526
1543
|
const normalizedParameters = compat.toolSchemaFlavor === "moonshot-mfjs"
|
|
1527
1544
|
? normalizeToolParametersForMoonshot(schemaParameters)
|
|
@@ -2,6 +2,8 @@ import type { Tool as OpenAITool, ResponseCreateParamsStreaming, ResponseInput,
|
|
|
2
2
|
import type { Api, AssistantMessage, Message, Model, StreamOptions, Tool, TranscriptContext, Usage } from "../types.ts";
|
|
3
3
|
import type { AssistantMessageEventStream } from "../utils/event-stream.ts";
|
|
4
4
|
export interface OpenAIResponsesStreamOptions {
|
|
5
|
+
/** SIWC succeeds only on response.completed, never on a finished tool alone. */
|
|
6
|
+
siwc?: boolean;
|
|
5
7
|
onProviderStreamEvent?: StreamOptions["onProviderStreamEvent"];
|
|
6
8
|
serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast";
|
|
7
9
|
grammarToolInputProperties?: ReadonlyMap<string, string>;
|
|
@@ -1029,12 +1029,22 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
1029
1029
|
}
|
|
1030
1030
|
else if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
1031
1031
|
finalizeResponse(event.response);
|
|
1032
|
+
if (options?.siwc && event.type === "response.incomplete") {
|
|
1033
|
+
throw new Error(`Response incomplete: ${event.response.incomplete_details?.reason ?? "unknown reason"}`);
|
|
1034
|
+
}
|
|
1032
1035
|
}
|
|
1033
1036
|
else if (event.type === "error") {
|
|
1034
1037
|
const errorEvent = event;
|
|
1035
1038
|
const code = errorEvent.error?.code ?? errorEvent.code ?? errorEvent.status;
|
|
1036
1039
|
const message = errorEvent.error?.message || errorEvent.message || "Unknown error";
|
|
1037
|
-
|
|
1040
|
+
const failure = new Error(code == null ? message : `Error Code ${code}: ${message}`);
|
|
1041
|
+
throw options?.siwc
|
|
1042
|
+
? Object.assign(failure, {
|
|
1043
|
+
code: typeof code === "string" ? code : undefined,
|
|
1044
|
+
status: errorEvent.status,
|
|
1045
|
+
param: errorEvent.error?.param ?? errorEvent.param,
|
|
1046
|
+
})
|
|
1047
|
+
: failure;
|
|
1038
1048
|
}
|
|
1039
1049
|
else if (event.type === "response.failed") {
|
|
1040
1050
|
sawTerminalResponseEvent = true;
|
|
@@ -1046,11 +1056,17 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
1046
1056
|
: details?.reason
|
|
1047
1057
|
? `incomplete: ${details.reason}`
|
|
1048
1058
|
: "Unknown error (no error details in response)";
|
|
1049
|
-
|
|
1059
|
+
const failure = new Error(msg);
|
|
1060
|
+
throw options?.siwc
|
|
1061
|
+
? Object.assign(failure, {
|
|
1062
|
+
code: error?.code,
|
|
1063
|
+
param: error && "param" in error ? error.param : undefined,
|
|
1064
|
+
})
|
|
1065
|
+
: failure;
|
|
1050
1066
|
}
|
|
1051
1067
|
}
|
|
1052
1068
|
const hasFinalizedToolCall = output.content.some((block) => block.type === "toolCall" && !("partialJson" in block));
|
|
1053
|
-
if (!sawTerminalResponseEvent && !hasFinalizedToolCall) {
|
|
1069
|
+
if (!sawTerminalResponseEvent && (options?.siwc || !hasFinalizedToolCall)) {
|
|
1054
1070
|
throw new Error("OpenAI Responses stream ended before a terminal response event");
|
|
1055
1071
|
}
|
|
1056
1072
|
// The agent runs every tool call in the final message. Refuse to hand over calls whose
|