@oh-my-pi/pi-ai 18.2.8 → 18.2.9
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 +33 -18
- package/dist/types/auth-gateway/types.d.ts +5 -0
- package/dist/types/auth-storage.d.ts +35 -32
- package/dist/types/index.d.ts +1 -0
- package/dist/types/providers/amazon-bedrock.d.ts +7 -0
- package/dist/types/providers/claude-code-fingerprint.d.ts +22 -3
- package/dist/types/providers/google-gemini-cli.d.ts +0 -2
- package/dist/types/providers/openai-chat-server-schema.d.ts +2 -2
- package/dist/types/usage/claude-api.d.ts +22 -0
- package/dist/types/usage/claude-reset.d.ts +44 -0
- package/dist/types/usage.d.ts +111 -5
- package/dist/types/utils/schema/json-schema-validator.d.ts +5 -2
- package/dist/types/utils/tool-call-loop-guard.d.ts +1 -1
- package/package.json +6 -6
- package/src/auth/sqlite-credential-store.ts +44 -1
- package/src/auth-broker/remote-store.ts +6 -6
- package/src/auth-broker/wire-schemas.ts +14 -0
- package/src/auth-gateway/server.ts +4 -0
- package/src/auth-gateway/types.ts +5 -0
- package/src/auth-storage.ts +263 -140
- package/src/error/flags.ts +10 -0
- package/src/index.ts +1 -0
- package/src/providers/amazon-bedrock.ts +55 -5
- package/src/providers/anthropic.ts +45 -11
- package/src/providers/aws-credentials.ts +124 -11
- package/src/providers/claude-code-fingerprint.ts +55 -3
- package/src/providers/gitlab-duo.ts +20 -4
- package/src/providers/google-gemini-cli.ts +0 -8
- package/src/providers/google-shared.ts +1 -18
- package/src/providers/openai-chat-server-schema.ts +1 -1
- package/src/providers/openai-chat-server.ts +3 -1
- package/src/providers/openai-codex-responses.ts +27 -5
- package/src/providers/openai-completions.ts +122 -19
- package/src/providers/pi-native-server.ts +1 -0
- package/src/registry/oauth/anthropic.ts +2 -3
- package/src/stream.ts +13 -4
- package/src/usage/alibaba-token-plan.ts +7 -1
- package/src/usage/claude-api.ts +66 -0
- package/src/usage/claude-reset.ts +638 -0
- package/src/usage/claude.ts +37 -59
- package/src/usage/kimi.ts +32 -1
- package/src/usage.ts +52 -5
- package/src/utils/schema/json-schema-validator.ts +23 -10
- package/src/utils/tool-call-loop-guard.ts +2 -2
- package/src/utils/validation.ts +145 -50
|
@@ -4290,11 +4290,22 @@ async function openCodexSseEventStream(
|
|
|
4290
4290
|
// an internal timeout stays retryable while an explicit abort fails fast.
|
|
4291
4291
|
let clearPreResponseTimeout: (() => void) | undefined;
|
|
4292
4292
|
const fetchAttempt: FetchImpl = async (input, init) => {
|
|
4293
|
+
let response: Response | undefined;
|
|
4293
4294
|
try {
|
|
4294
|
-
|
|
4295
|
+
response = await (fetchOverride ?? fetch)(input, init);
|
|
4296
|
+
return response;
|
|
4295
4297
|
} finally {
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
+
// A successful streaming body is governed by the iterator-level idle
|
|
4299
|
+
// watchdog, so disarm the pre-response guard the instant headers arrive.
|
|
4300
|
+
// Keep it armed for a non-2xx response: the error body is still
|
|
4301
|
+
// consumed under this deadline — by fetchWithRetry's
|
|
4302
|
+
// retry-status inspection and by CodexApiError.fromResponse — otherwise a
|
|
4303
|
+
// server that sends headers then stalls the body hangs the turn past every
|
|
4304
|
+
// configured first-event/idle deadline (issue #12664).
|
|
4305
|
+
if (!response || response.ok) {
|
|
4306
|
+
clearPreResponseTimeout?.();
|
|
4307
|
+
clearPreResponseTimeout = undefined;
|
|
4308
|
+
}
|
|
4298
4309
|
}
|
|
4299
4310
|
};
|
|
4300
4311
|
const bodyJson = JSON.stringify(body);
|
|
@@ -4318,6 +4329,9 @@ async function openCodexSseEventStream(
|
|
|
4318
4329
|
body: requestBody,
|
|
4319
4330
|
signal,
|
|
4320
4331
|
prepareInit: () => {
|
|
4332
|
+
// A retried non-2xx attempt leaves its guard armed for the retry-status
|
|
4333
|
+
// body read; disarm it before arming the next attempt's guard.
|
|
4334
|
+
clearPreResponseTimeout?.();
|
|
4321
4335
|
const watchdog = armPreResponseTimeout(signal, firstEventTimeoutMs);
|
|
4322
4336
|
clearPreResponseTimeout = watchdog.clear;
|
|
4323
4337
|
return { signal: watchdog.signal };
|
|
@@ -4342,8 +4356,9 @@ async function openCodexSseEventStream(
|
|
|
4342
4356
|
});
|
|
4343
4357
|
response = await send(bodyJson);
|
|
4344
4358
|
}
|
|
4345
|
-
}
|
|
4359
|
+
} catch (error) {
|
|
4346
4360
|
clearPreResponseTimeout?.();
|
|
4361
|
+
throw error;
|
|
4347
4362
|
}
|
|
4348
4363
|
CODEX_DEBUG &&
|
|
4349
4364
|
logger.debug("[codex] codex response", {
|
|
@@ -4354,7 +4369,14 @@ async function openCodexSseEventStream(
|
|
|
4354
4369
|
cfRay: response.headers.get("cf-ray") || null,
|
|
4355
4370
|
});
|
|
4356
4371
|
if (!response.ok) {
|
|
4357
|
-
|
|
4372
|
+
// The pre-response guard is still armed for a non-2xx response; keep it live
|
|
4373
|
+
// across the error-body read so a stalled body is bounded, then disarm once
|
|
4374
|
+
// the read settles (issue #12664).
|
|
4375
|
+
try {
|
|
4376
|
+
throw await CodexApiError.fromResponse(response);
|
|
4377
|
+
} finally {
|
|
4378
|
+
clearPreResponseTimeout?.();
|
|
4379
|
+
}
|
|
4358
4380
|
}
|
|
4359
4381
|
updateCodexSessionMetadataFromHeaders(turnState, state, response.headers);
|
|
4360
4382
|
if (!response.body) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveModelPolicy } from "@oh-my-pi/pi-catalog/compat/resolve";
|
|
1
2
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
2
3
|
import { resolveWireModelId } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
3
4
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
@@ -133,6 +134,13 @@ type OpenAICompletionsDeltaWithReasoningDetails = ChatCompletionChunk.Choice["de
|
|
|
133
134
|
reasoning_details?: unknown;
|
|
134
135
|
};
|
|
135
136
|
|
|
137
|
+
type GeminiMessageThoughtSignatureField = "thinking_signature" | "thought_signature";
|
|
138
|
+
|
|
139
|
+
type GeminiMessageThoughtSignature = {
|
|
140
|
+
field: GeminiMessageThoughtSignatureField;
|
|
141
|
+
signature: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
136
144
|
type GeminiThoughtSignatureNamespace = "google" | "vertex";
|
|
137
145
|
|
|
138
146
|
type GeminiThoughtSignatureExtraContent = Partial<
|
|
@@ -145,6 +153,11 @@ type OpenAICompletionsFunctionToolCall = ChatCompletionMessageFunctionToolCall &
|
|
|
145
153
|
|
|
146
154
|
const GEMINI_THOUGHT_SIGNATURE_NAMESPACES: readonly GeminiThoughtSignatureNamespace[] = ["google", "vertex"];
|
|
147
155
|
|
|
156
|
+
const GEMINI_MESSAGE_THOUGHT_SIGNATURE_FIELDS: readonly GeminiMessageThoughtSignatureField[] = [
|
|
157
|
+
"thinking_signature",
|
|
158
|
+
"thought_signature",
|
|
159
|
+
];
|
|
160
|
+
|
|
148
161
|
function getGeminiThoughtSignatureExtraContent(value: unknown): GeminiThoughtSignatureExtraContent | undefined {
|
|
149
162
|
if (typeof value !== "object" || value === null) return undefined;
|
|
150
163
|
for (const namespace of GEMINI_THOUGHT_SIGNATURE_NAMESPACES) {
|
|
@@ -159,20 +172,66 @@ function getGeminiThoughtSignatureExtraContent(value: unknown): GeminiThoughtSig
|
|
|
159
172
|
return undefined;
|
|
160
173
|
}
|
|
161
174
|
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
function getGeminiMessageThoughtSignature(value: unknown): GeminiMessageThoughtSignature | undefined {
|
|
176
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
177
|
+
for (const field of GEMINI_MESSAGE_THOUGHT_SIGNATURE_FIELDS) {
|
|
178
|
+
const signature = Reflect.get(value, field);
|
|
179
|
+
if (typeof signature === "string" && signature.length > 0) return { field, signature };
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseStoredThoughtSignature(thoughtSignature: string | undefined): unknown {
|
|
165
185
|
if (!thoughtSignature) return undefined;
|
|
166
186
|
try {
|
|
167
|
-
|
|
168
|
-
return getGeminiThoughtSignatureExtraContent(parsed);
|
|
187
|
+
return JSON.parse(thoughtSignature);
|
|
169
188
|
} catch {
|
|
170
189
|
return undefined;
|
|
171
190
|
}
|
|
172
191
|
}
|
|
173
192
|
|
|
193
|
+
// A single tool-call turn on an OpenAI-compatible Gemini wire can carry two
|
|
194
|
+
// independent signatures: a per-call one (`extra_content.google|vertex` or an
|
|
195
|
+
// encrypted `reasoning_details` entry) and a message-level `thinking_signature`
|
|
196
|
+
// / `thought_signature`. Both are stashed together on the originating tool
|
|
197
|
+
// call's `thoughtSignature` so persistence and replay preserve each field.
|
|
198
|
+
// `perCall` holds the raw extra_content object or reasoning detail; `message`
|
|
199
|
+
// holds the message-level signature in its wire shape.
|
|
200
|
+
type StoredGeminiSignature = {
|
|
201
|
+
perCall?: unknown;
|
|
202
|
+
message?: Partial<Record<GeminiMessageThoughtSignatureField, string>>;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Reads the stored envelope, also accepting the legacy raw shapes emitted before
|
|
206
|
+
// the envelope existed (a bare extra_content object, reasoning detail, or
|
|
207
|
+
// message-level signature) so persisted history keeps replaying.
|
|
208
|
+
function normalizeStoredGeminiSignature(value: unknown): StoredGeminiSignature | undefined {
|
|
209
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
210
|
+
const perCall = Reflect.get(value, "perCall");
|
|
211
|
+
const envelopeMessage = getGeminiMessageThoughtSignature(Reflect.get(value, "message"));
|
|
212
|
+
if (perCall !== undefined || envelopeMessage) {
|
|
213
|
+
const normalized: StoredGeminiSignature = {};
|
|
214
|
+
if (perCall !== undefined) normalized.perCall = perCall;
|
|
215
|
+
if (envelopeMessage) normalized.message = { [envelopeMessage.field]: envelopeMessage.signature };
|
|
216
|
+
return normalized;
|
|
217
|
+
}
|
|
218
|
+
const legacyMessage = getGeminiMessageThoughtSignature(value);
|
|
219
|
+
if (legacyMessage) return { message: { [legacyMessage.field]: legacyMessage.signature } };
|
|
220
|
+
return { perCall: value };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Merges a new per-call or message-level signature into whatever is already
|
|
224
|
+
// stored, so a later message-level signature never clobbers an earlier per-call
|
|
225
|
+
// one (and vice versa).
|
|
226
|
+
function mergeStoredGeminiSignature(existing: string | undefined, update: StoredGeminiSignature): string {
|
|
227
|
+
const merged = normalizeStoredGeminiSignature(parseStoredThoughtSignature(existing)) ?? {};
|
|
228
|
+
if (update.perCall !== undefined) merged.perCall = update.perCall;
|
|
229
|
+
if (update.message) merged.message = update.message;
|
|
230
|
+
return JSON.stringify(merged);
|
|
231
|
+
}
|
|
232
|
+
|
|
174
233
|
type OpenAICompletionsAssistantMessageParam = ChatCompletionAssistantMessageParam &
|
|
175
|
-
Partial<Record<OpenAICompletionsReasoningField, string>> & {
|
|
234
|
+
Partial<Record<OpenAICompletionsReasoningField | GeminiMessageThoughtSignatureField, string>> & {
|
|
176
235
|
reasoning_details?: unknown[];
|
|
177
236
|
};
|
|
178
237
|
|
|
@@ -919,6 +978,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
919
978
|
}
|
|
920
979
|
};
|
|
921
980
|
let currentBlock: OpenAIStreamBlock | undefined;
|
|
981
|
+
let messageThoughtSignature: GeminiMessageThoughtSignature | undefined;
|
|
922
982
|
const blockIndex = (block: OpenAIStreamBlock | undefined): number => {
|
|
923
983
|
if (!block) return Math.max(0, output.content.length - 1);
|
|
924
984
|
return output.content.indexOf(block);
|
|
@@ -1347,7 +1407,11 @@ const streamOpenAICompletionsOnce = (
|
|
|
1347
1407
|
if (toolCall.id) block.id = toolCall.id;
|
|
1348
1408
|
if (incomingName) block.name = incomingName;
|
|
1349
1409
|
const extraContent = getGeminiThoughtSignatureExtraContent(Reflect.get(toolCall, "extra_content"));
|
|
1350
|
-
if (extraContent)
|
|
1410
|
+
if (extraContent) {
|
|
1411
|
+
block.thoughtSignature = mergeStoredGeminiSignature(block.thoughtSignature, {
|
|
1412
|
+
perCall: extraContent,
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1351
1415
|
let delta = "";
|
|
1352
1416
|
// The OpenAI SDK types `function.arguments` as a JSON string, but MiniMax-compatible
|
|
1353
1417
|
// hosts stream a fully-formed object instead. Model both shapes so the branches below
|
|
@@ -1411,11 +1475,29 @@ const streamOpenAICompletionsOnce = (
|
|
|
1411
1475
|
b => b.type === "toolCall" && b.id === detailObject.id,
|
|
1412
1476
|
) as ToolCall | undefined;
|
|
1413
1477
|
if (matchingToolCall) {
|
|
1414
|
-
matchingToolCall.thoughtSignature =
|
|
1478
|
+
matchingToolCall.thoughtSignature = mergeStoredGeminiSignature(
|
|
1479
|
+
matchingToolCall.thoughtSignature,
|
|
1480
|
+
{ perCall: detailObject },
|
|
1481
|
+
);
|
|
1415
1482
|
}
|
|
1416
1483
|
}
|
|
1417
1484
|
}
|
|
1418
1485
|
}
|
|
1486
|
+
|
|
1487
|
+
const incomingMessageThoughtSignature = getGeminiMessageThoughtSignature(choice.delta);
|
|
1488
|
+
if (incomingMessageThoughtSignature) messageThoughtSignature = incomingMessageThoughtSignature;
|
|
1489
|
+
if (messageThoughtSignature) {
|
|
1490
|
+
for (const block of output.content) {
|
|
1491
|
+
if (block.type !== "toolCall") continue;
|
|
1492
|
+
block.thoughtSignature = mergeStoredGeminiSignature(block.thoughtSignature, {
|
|
1493
|
+
message: {
|
|
1494
|
+
[messageThoughtSignature.field]: messageThoughtSignature.signature,
|
|
1495
|
+
},
|
|
1496
|
+
});
|
|
1497
|
+
messageThoughtSignature = undefined;
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1419
1501
|
}
|
|
1420
1502
|
|
|
1421
1503
|
// If usage arrived on the finish chunk without cache-read fields,
|
|
@@ -1542,16 +1624,33 @@ const streamOpenAICompletionsOnce = (
|
|
|
1542
1624
|
return stream;
|
|
1543
1625
|
};
|
|
1544
1626
|
|
|
1627
|
+
/**
|
|
1628
|
+
* Custom APIs deliberately have no catalog compat type. Once an extension
|
|
1629
|
+
* explicitly delegates to this streamer, resolve the OpenAI wire policy on a
|
|
1630
|
+
* request-local clone while preserving the custom API id on the original model.
|
|
1631
|
+
*/
|
|
1632
|
+
function resolveOpenAICompletionsCompat(model: Model<"openai-completions">): Model<"openai-completions"> {
|
|
1633
|
+
if (model.compat !== undefined) return model;
|
|
1634
|
+
const compat = resolveModelPolicy({
|
|
1635
|
+
...model,
|
|
1636
|
+
api: "openai-completions",
|
|
1637
|
+
compat: model.compatConfig,
|
|
1638
|
+
}).compat;
|
|
1639
|
+
return { ...model, compat };
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1545
1642
|
/**
|
|
1546
1643
|
* Retries benign empty completions and transient provider failures only before
|
|
1547
1644
|
* assistant output commits the attempt.
|
|
1548
1645
|
*/
|
|
1549
|
-
export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (model, context, options) =>
|
|
1550
|
-
|
|
1646
|
+
export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (model, context, options) => {
|
|
1647
|
+
const resolvedModel = resolveOpenAICompletionsCompat(model);
|
|
1648
|
+
return withReplaySafeStreamRetry(resolvedModel, context, options, streamOpenAICompletionsOnce, {
|
|
1551
1649
|
retryEmptyCompletion: true,
|
|
1552
1650
|
retryProviderErrors: true,
|
|
1553
1651
|
maxProviderErrorRetries: 1,
|
|
1554
1652
|
});
|
|
1653
|
+
};
|
|
1555
1654
|
|
|
1556
1655
|
function createRequestSetup(
|
|
1557
1656
|
model: Model<"openai-completions">,
|
|
@@ -2350,19 +2449,23 @@ export function convertMessages(
|
|
|
2350
2449
|
arguments: serializeToolArguments(tc.arguments),
|
|
2351
2450
|
},
|
|
2352
2451
|
};
|
|
2353
|
-
const
|
|
2452
|
+
const stored = normalizeStoredGeminiSignature(parseStoredThoughtSignature(tc.thoughtSignature));
|
|
2453
|
+
const extraContent = getGeminiThoughtSignatureExtraContent(stored?.perCall);
|
|
2354
2454
|
if (extraContent) replayedToolCall.extra_content = extraContent;
|
|
2355
2455
|
return replayedToolCall;
|
|
2356
2456
|
});
|
|
2457
|
+
for (const toolCall of toolCalls) {
|
|
2458
|
+
const stored = normalizeStoredGeminiSignature(parseStoredThoughtSignature(toolCall.thoughtSignature));
|
|
2459
|
+
const messageSignature = getGeminiMessageThoughtSignature(stored?.message);
|
|
2460
|
+
if (!messageSignature) continue;
|
|
2461
|
+
assistantMsg[messageSignature.field] = messageSignature.signature;
|
|
2462
|
+
break;
|
|
2463
|
+
}
|
|
2357
2464
|
const reasoningDetails = toolCalls.flatMap(tc => {
|
|
2358
|
-
const
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
return getGeminiThoughtSignatureExtraContent(parsed) ? [] : [parsed];
|
|
2363
|
-
} catch {
|
|
2364
|
-
return [];
|
|
2365
|
-
}
|
|
2465
|
+
const stored = normalizeStoredGeminiSignature(parseStoredThoughtSignature(tc.thoughtSignature));
|
|
2466
|
+
const perCall = stored?.perCall;
|
|
2467
|
+
if (perCall === undefined || getGeminiThoughtSignatureExtraContent(perCall)) return [];
|
|
2468
|
+
return [perCall];
|
|
2366
2469
|
});
|
|
2367
2470
|
if (reasoningDetails.length > 0) {
|
|
2368
2471
|
assistantMsg.reasoning_details = reasoningDetails;
|
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as AIError from "../../error";
|
|
10
|
-
import {
|
|
10
|
+
import { getClaudeCodeVersion } from "../../providers/claude-code-fingerprint";
|
|
11
11
|
import type { FetchImpl } from "../../types";
|
|
12
12
|
import type { AfterExchangeHook } from "../hooks/types";
|
|
13
13
|
import type { OAuthCredentials } from "./types";
|
|
14
14
|
|
|
15
15
|
const BOOTSTRAP_URL = "https://api.anthropic.com/api/claude_cli/bootstrap";
|
|
16
16
|
const CLAUDE_CODE_BOOTSTRAP_MODEL = "claude-opus-4-8";
|
|
17
|
-
const CLAUDE_CODE_BOOTSTRAP_USER_AGENT = `claude-code/${claudeCodeVersion}`;
|
|
18
17
|
|
|
19
18
|
export { ANTHROPIC_OAUTH_GRANT_TTL_MS } from "./anthropic-constants";
|
|
20
19
|
|
|
@@ -56,7 +55,7 @@ export async function fetchAnthropicBootstrapIdentity(
|
|
|
56
55
|
Accept: "application/json, text/plain, */*",
|
|
57
56
|
Authorization: `Bearer ${accessToken}`,
|
|
58
57
|
"Content-Type": "application/json",
|
|
59
|
-
"User-Agent":
|
|
58
|
+
"User-Agent": `claude-code/${getClaudeCodeVersion()}`,
|
|
60
59
|
"anthropic-beta": "oauth-2025-04-20",
|
|
61
60
|
},
|
|
62
61
|
signal: AbortSignal.timeout(30_000),
|
package/src/stream.ts
CHANGED
|
@@ -1841,7 +1841,7 @@ function resolveBedrockThinkingBudget(
|
|
|
1841
1841
|
model: Model<"bedrock-converse-stream">,
|
|
1842
1842
|
options?: SimpleStreamOptions,
|
|
1843
1843
|
): { budget: number; level: Effort } | null {
|
|
1844
|
-
if (!options?.reasoning || !model.reasoning) return null;
|
|
1844
|
+
if (!options?.reasoning || !model.reasoning || options.disableReasoning || options.forceReasoningOff) return null;
|
|
1845
1845
|
const level = requireSupportedEffort(model, options.reasoning);
|
|
1846
1846
|
const budget = options.thinkingBudgets?.[level] ?? BEDROCK_CLAUDE_THINKING[level];
|
|
1847
1847
|
return { budget, level };
|
|
@@ -2167,7 +2167,11 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
2167
2167
|
case "bedrock-converse-stream": {
|
|
2168
2168
|
const bedrockBase: BedrockOptions = {
|
|
2169
2169
|
...base,
|
|
2170
|
-
reasoning
|
|
2170
|
+
// Explicit reasoning-off must fold here like the anthropic-messages
|
|
2171
|
+
// branch: the provider gates thinking only on `reasoning`, and the
|
|
2172
|
+
// budget path below must not inflate a capped request for thinking
|
|
2173
|
+
// that was turned off.
|
|
2174
|
+
reasoning: options?.disableReasoning || options?.forceReasoningOff ? undefined : options?.reasoning,
|
|
2171
2175
|
thinkingBudgets: options?.thinkingBudgets,
|
|
2172
2176
|
toolChoice: mapAnthropicToolChoice(options?.toolChoice),
|
|
2173
2177
|
thinkingDisplay: options?.hideThinkingSummary ? "omitted" : undefined,
|
|
@@ -2212,6 +2216,9 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
2212
2216
|
openrouterVariant: options?.openrouterVariant,
|
|
2213
2217
|
maxTokensExplicit: rawOptions?.maxTokens !== undefined,
|
|
2214
2218
|
disableReasoning: options?.disableReasoning,
|
|
2219
|
+
// Forwarded, not folded: the Responses record reads both flags
|
|
2220
|
+
// itself (`applyResponsesCompatPolicy`).
|
|
2221
|
+
forceReasoningOff: options?.forceReasoningOff,
|
|
2215
2222
|
textVerbosity: options?.textVerbosity,
|
|
2216
2223
|
promptCache: options?.promptCache,
|
|
2217
2224
|
statefulResponses: options?.statefulResponses,
|
|
@@ -2220,7 +2227,8 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
2220
2227
|
return castApi<"openai-completions">({
|
|
2221
2228
|
...base,
|
|
2222
2229
|
reasoning: resolveOpenAiReasoningEffort(model, options),
|
|
2223
|
-
|
|
2230
|
+
// `OpenAICompletionsOptions` carries no forceReasoningOff; fold it.
|
|
2231
|
+
disableReasoning: options?.disableReasoning || options?.forceReasoningOff,
|
|
2224
2232
|
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
|
2225
2233
|
serviceTier: options?.serviceTier,
|
|
2226
2234
|
openrouterVariant: options?.openrouterVariant,
|
|
@@ -2233,7 +2241,8 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
2233
2241
|
return castApi<"openai-completions">({
|
|
2234
2242
|
...base,
|
|
2235
2243
|
reasoning: resolveOpenAiReasoningEffort(model, options),
|
|
2236
|
-
|
|
2244
|
+
// `OpenAICompletionsOptions` carries no forceReasoningOff; fold it.
|
|
2245
|
+
disableReasoning: options?.disableReasoning || options?.forceReasoningOff,
|
|
2237
2246
|
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
|
2238
2247
|
serviceTier: options?.serviceTier,
|
|
2239
2248
|
openrouterVariant: options?.openrouterVariant,
|
|
@@ -46,7 +46,6 @@ const CHINA_CONSOLE = {
|
|
|
46
46
|
protocol: "V2",
|
|
47
47
|
console: "ONE_CONSOLE",
|
|
48
48
|
productCode: "p_efm",
|
|
49
|
-
switchAgent: 12608464,
|
|
50
49
|
switchUserType: 3,
|
|
51
50
|
domain: "bailian.console.aliyun.com",
|
|
52
51
|
consoleSite: "BAILIAN_ALIYUN",
|
|
@@ -221,6 +220,13 @@ async function fetchAlibabaTokenPlanUsage(
|
|
|
221
220
|
ctx.logger?.warn("Alibaba Token Plan usage response invalid", { provider: PROVIDER });
|
|
222
221
|
return null;
|
|
223
222
|
}
|
|
223
|
+
if (payload.data.success === false) {
|
|
224
|
+
ctx.logger?.warn("Alibaba Token Plan usage request rejected", {
|
|
225
|
+
provider: PROVIDER,
|
|
226
|
+
errorCode: typeof payload.data.errorCode === "string" ? payload.data.errorCode : "unknown",
|
|
227
|
+
});
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
224
230
|
const responseData = unwrapGatewayData(payload.data);
|
|
225
231
|
const limits = [
|
|
226
232
|
buildLimit(
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getClaudeCodeUserAgent } from "../providers/claude-code-fingerprint";
|
|
2
|
+
|
|
3
|
+
/** Canonical host for Claude's first-party account API. */
|
|
4
|
+
export const DEFAULT_CLAUDE_API_BASE_URL = "https://api.anthropic.com";
|
|
5
|
+
/** Canonical subscription usage and profile endpoint. */
|
|
6
|
+
export const DEFAULT_CLAUDE_OAUTH_BASE_URL = `${DEFAULT_CLAUDE_API_BASE_URL}/api/oauth`;
|
|
7
|
+
/** OAuth protocol beta required by Claude's account routes. */
|
|
8
|
+
export const CLAUDE_OAUTH_BETA = "oauth-2025-04-20";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalize Messages (`/v1`) and OAuth (`/api/oauth`) endpoints to the common
|
|
12
|
+
* API root used by both usage and reset routes. Custom path prefixes survive.
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeClaudeApiBaseUrl(baseUrl?: string): string {
|
|
15
|
+
if (!baseUrl?.trim()) return DEFAULT_CLAUDE_API_BASE_URL;
|
|
16
|
+
let url: URL;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(baseUrl.trim());
|
|
19
|
+
} catch {
|
|
20
|
+
return DEFAULT_CLAUDE_API_BASE_URL;
|
|
21
|
+
}
|
|
22
|
+
let path = url.pathname.replace(/\/+$/, "");
|
|
23
|
+
if (path === "/") path = "";
|
|
24
|
+
const lower = path.toLowerCase();
|
|
25
|
+
if (lower.endsWith("/api/oauth")) {
|
|
26
|
+
path = path.slice(0, -"/api/oauth".length);
|
|
27
|
+
} else if (lower.endsWith("/v1")) {
|
|
28
|
+
path = path.slice(0, -"/v1".length);
|
|
29
|
+
}
|
|
30
|
+
return `${url.origin}${path}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resolve the OAuth API base while retaining a configured proxy path prefix. */
|
|
34
|
+
export function claudeOAuthBaseUrl(baseUrl?: string): string {
|
|
35
|
+
return `${normalizeClaudeApiBaseUrl(baseUrl)}/api/oauth`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Configured OAuth endpoint followed by canonical fallback, without duplicates. */
|
|
39
|
+
export function claudeOAuthBaseUrls(baseUrl?: string): readonly string[] {
|
|
40
|
+
const configured = claudeOAuthBaseUrl(baseUrl);
|
|
41
|
+
return configured === DEFAULT_CLAUDE_OAUTH_BASE_URL
|
|
42
|
+
? [DEFAULT_CLAUDE_OAUTH_BASE_URL]
|
|
43
|
+
: [configured, DEFAULT_CLAUDE_OAUTH_BASE_URL];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve a first-party account route against the configured Claude API root. */
|
|
47
|
+
export function claudeApiUrl(baseUrl: string | undefined, path: string): string {
|
|
48
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
49
|
+
return `${normalizeClaudeApiBaseUrl(baseUrl)}${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Shared OAuth and CLI identity headers for Claude usage, profile, and resets. */
|
|
53
|
+
export function buildClaudeOAuthHeaders(
|
|
54
|
+
accessToken: string,
|
|
55
|
+
options: { beta?: string; json?: boolean } = {},
|
|
56
|
+
): Record<string, string> {
|
|
57
|
+
return {
|
|
58
|
+
accept: "application/json, text/plain, */*",
|
|
59
|
+
"accept-encoding": "gzip, compress, deflate, br",
|
|
60
|
+
"anthropic-beta": options.beta ?? CLAUDE_OAUTH_BETA,
|
|
61
|
+
...(options.json === false ? {} : { "content-type": "application/json" }),
|
|
62
|
+
connection: "keep-alive",
|
|
63
|
+
"user-agent": getClaudeCodeUserAgent(),
|
|
64
|
+
authorization: `Bearer ${accessToken}`,
|
|
65
|
+
};
|
|
66
|
+
}
|