@gajae-code/ai 0.18.11 → 0.19.0
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 +103 -0
- package/README.md +1 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +30 -0
- package/dist/types/auth-storage.d.ts +98 -13
- package/dist/types/bedrock-claude-cache-policy.d.ts +1 -1
- package/dist/types/model-cache.d.ts +6 -4
- package/dist/types/model-manager.d.ts +2 -0
- package/dist/types/model-pricing.d.ts +8 -0
- package/dist/types/model-thinking.d.ts +11 -0
- package/dist/types/openai-completions-compat.d.ts +2 -1
- package/dist/types/provider-models/google.d.ts +2 -0
- package/dist/types/providers/amazon-bedrock.d.ts +17 -2
- package/dist/types/providers/anthropic.d.ts +25 -0
- package/dist/types/providers/aws-credential-config.d.ts +5 -0
- package/dist/types/providers/google-auth.d.ts +7 -0
- package/dist/types/providers/google-gemini-headers.d.ts +11 -6
- package/dist/types/providers/kiro-api-key.d.ts +5 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +2 -1
- package/dist/types/providers/openai-completions.d.ts +1 -1
- package/dist/types/providers/openai-responses-shared.d.ts +2 -1
- package/dist/types/stream.d.ts +33 -1
- package/dist/types/types.d.ts +26 -3
- package/dist/types/usage.d.ts +3 -0
- package/dist/types/utils/discovery/antigravity.d.ts +16 -1
- package/dist/types/utils/idle-iterator.d.ts +3 -2
- package/dist/types/utils/oauth/glm-zcode.d.ts +0 -30
- package/dist/types/utils/oauth/types.d.ts +9 -0
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +62 -11
- package/src/auth-broker/wire-schemas.ts +2 -0
- package/src/auth-gateway/server.ts +3 -2
- package/src/auth-storage.ts +908 -246
- package/src/bedrock-claude-cache-policy.d.ts +1 -1
- package/src/bedrock-claude-cache-policy.ts +2 -2
- package/src/model-cache.ts +45 -10
- package/src/model-manager.ts +18 -6
- package/src/model-pricing.d.ts +8 -0
- package/src/model-pricing.ts +87 -0
- package/src/model-thinking.d.ts +11 -0
- package/src/model-thinking.ts +97 -12
- package/src/models.json +3176 -1670
- package/src/models.ts +10 -3
- package/src/openai-completions-compat.d.ts +2 -1
- package/src/openai-completions-compat.ts +5 -0
- package/src/provider-models/google.ts +11 -1
- package/src/provider-models/openai-compat.ts +26 -0
- package/src/providers/amazon-bedrock.d.ts +17 -2
- package/src/providers/amazon-bedrock.ts +76 -16
- package/src/providers/anthropic.d.ts +25 -0
- package/src/providers/anthropic.ts +230 -18
- package/src/providers/aws-credential-config.d.ts +5 -0
- package/src/providers/aws-credential-config.ts +29 -1
- package/src/providers/google-auth.d.ts +7 -0
- package/src/providers/google-auth.ts +165 -4
- package/src/providers/google-gemini-headers.d.ts +11 -6
- package/src/providers/google-gemini-headers.ts +16 -11
- package/src/providers/google-vertex.ts +2 -4
- package/src/providers/kiro-api-key.d.ts +5 -0
- package/src/providers/kiro-api-key.ts +379 -68
- package/src/providers/ollama.ts +19 -9
- package/src/providers/openai-anthropic-shim.ts +1 -0
- package/src/providers/openai-chat-server-schema.ts +1 -1
- package/src/providers/openai-chat-server.ts +14 -2
- package/src/providers/openai-codex-responses.ts +21 -14
- package/src/providers/openai-completions.d.ts +1 -1
- package/src/providers/openai-completions.ts +65 -4
- package/src/providers/openai-responses-server.ts +10 -1
- package/src/providers/openai-responses-shared.d.ts +2 -1
- package/src/providers/openai-responses-shared.ts +42 -1
- package/src/providers/openai-responses.ts +51 -10
- package/src/providers/pi-native-server.ts +1 -1
- package/src/providers/transform-messages.ts +32 -1
- package/src/stream.d.ts +33 -1
- package/src/stream.ts +111 -17
- package/src/types.d.ts +26 -3
- package/src/types.ts +55 -2
- package/src/usage/claude.ts +4 -2
- package/src/usage.ts +3 -0
- package/src/utils/discovery/antigravity.ts +192 -7
- package/src/utils/idle-iterator.d.ts +3 -2
- package/src/utils/idle-iterator.ts +30 -10
- package/src/utils/oauth/anthropic.ts +23 -22
- package/src/utils/oauth/api-key-validation.ts +34 -11
- package/src/utils/oauth/glm-zcode.d.ts +0 -30
- package/src/utils/oauth/glm-zcode.ts +15 -4
- package/src/utils/oauth/openai-codex.ts +41 -6
- package/src/utils/oauth/types.d.ts +9 -0
- package/src/utils/oauth/types.ts +9 -0
- package/src/utils/parse-bind.d.ts +35 -0
- package/src/utils/proxy.ts +35 -6
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { logger } from "@gajae-code/utils";
|
|
1
2
|
import * as z from "zod/v4";
|
|
2
3
|
import { isRetiredModelKey } from "../../model-retirements";
|
|
3
|
-
import { getAntigravityUserAgent } from "../../providers/google-gemini-headers";
|
|
4
|
+
import { getAntigravityUserAgent, getGeminiCliUserAgent } from "../../providers/google-gemini-headers";
|
|
4
5
|
import type { Model } from "../../types";
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Returns the default Antigravity discovery User-Agent.
|
|
9
|
+
* Can be overridden via PI_AI_ANTIGRAVITY_USER_AGENT environment variable.
|
|
10
|
+
*/
|
|
11
|
+
export function getDefaultAntigravityUserAgent(): string {
|
|
12
|
+
return getAntigravityUserAgent();
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
const DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS = [
|
|
7
16
|
"https://daily-cloudcode-pa.googleapis.com",
|
|
8
17
|
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
@@ -35,6 +44,12 @@ export interface AntigravityDiscoveryApiModel {
|
|
|
35
44
|
modelProvider?: string;
|
|
36
45
|
isInternal?: boolean;
|
|
37
46
|
supportsVideo?: boolean;
|
|
47
|
+
/** Account-specific entitlement status */
|
|
48
|
+
entitlement?: string;
|
|
49
|
+
/** Whether the model is disabled for this account */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/** Per-account quota/tier information */
|
|
52
|
+
quota?: string;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
/**
|
|
@@ -81,6 +96,9 @@ const AntigravityDiscoveryApiModelSchema: z.ZodType<AntigravityDiscoveryApiModel
|
|
|
81
96
|
modelProvider: z.preprocess(value => (typeof value === "string" ? value : undefined), z.string().optional()),
|
|
82
97
|
isInternal: z.preprocess(value => (typeof value === "boolean" ? value : undefined), z.boolean().optional()),
|
|
83
98
|
supportsVideo: z.preprocess(value => (typeof value === "boolean" ? value : undefined), z.boolean().optional()),
|
|
99
|
+
entitlement: z.preprocess(value => (typeof value === "string" ? value : undefined), z.string().optional()),
|
|
100
|
+
disabled: z.preprocess(value => (typeof value === "boolean" ? value : undefined), z.boolean().optional()),
|
|
101
|
+
quota: z.preprocess(value => (typeof value === "string" ? value : undefined), z.string().optional()),
|
|
84
102
|
})
|
|
85
103
|
.loose();
|
|
86
104
|
const AntigravityDiscoveryAgentModelGroupSchema: z.ZodType<AntigravityDiscoveryAgentModelGroup> = z
|
|
@@ -154,7 +172,7 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
154
172
|
token: string;
|
|
155
173
|
/** Optional endpoint override. Defaults to Antigravity fallback endpoints. */
|
|
156
174
|
endpoint?: string;
|
|
157
|
-
/**
|
|
175
|
+
/** Optional Google Cloud project ID for tier-gated model discovery. */
|
|
158
176
|
project?: string;
|
|
159
177
|
/** Optional user agent override. */
|
|
160
178
|
userAgent?: string;
|
|
@@ -168,6 +186,8 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
168
186
|
* Default: `google-antigravity`.
|
|
169
187
|
*/
|
|
170
188
|
targetProvider?: "google-antigravity" | "google-gemini-cli";
|
|
189
|
+
/** Optional logger for debug diagnostics. */
|
|
190
|
+
logger?: Pick<typeof logger, "debug">;
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
/**
|
|
@@ -182,27 +202,40 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
182
202
|
const fetcher = options.fetcher ?? fetch;
|
|
183
203
|
const signal = options.signal ?? AbortSignal.timeout(5_000);
|
|
184
204
|
const targetProvider = options.targetProvider ?? "google-antigravity";
|
|
205
|
+
const userAgent = options.userAgent ?? getAntigravityDiscoveryUserAgent();
|
|
185
206
|
const endpoints = options.endpoint
|
|
186
207
|
? [trimTrailingSlashes(options.endpoint)]
|
|
187
208
|
: DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS.map(trimTrailingSlashes);
|
|
209
|
+
const debugLogger = options.logger ?? logger;
|
|
188
210
|
|
|
189
211
|
for (const endpoint of endpoints) {
|
|
212
|
+
const redactedEndpoint = redactAntigravityEndpoint(endpoint);
|
|
190
213
|
let response: Response;
|
|
191
214
|
try {
|
|
215
|
+
const requestBody: Record<string, unknown> = {};
|
|
216
|
+
if (options.project) {
|
|
217
|
+
requestBody.project = options.project;
|
|
218
|
+
}
|
|
192
219
|
response = await fetcher(`${endpoint}${FETCH_AVAILABLE_MODELS_PATH}`, {
|
|
193
220
|
method: "POST",
|
|
194
221
|
headers: {
|
|
195
222
|
Authorization: `Bearer ${options.token}`,
|
|
196
223
|
"Content-Type": "application/json",
|
|
197
|
-
"User-Agent":
|
|
224
|
+
"User-Agent": userAgent,
|
|
198
225
|
},
|
|
199
|
-
body: JSON.stringify(
|
|
226
|
+
body: JSON.stringify(requestBody),
|
|
200
227
|
signal,
|
|
201
228
|
});
|
|
202
229
|
} catch {
|
|
203
230
|
continue;
|
|
204
231
|
}
|
|
205
232
|
|
|
233
|
+
debugLogger.debug("Antigravity discovery endpoint response", {
|
|
234
|
+
endpoint: redactedEndpoint,
|
|
235
|
+
status: response.status,
|
|
236
|
+
statusText: response.statusText,
|
|
237
|
+
});
|
|
238
|
+
|
|
206
239
|
if (!response.ok) {
|
|
207
240
|
continue;
|
|
208
241
|
}
|
|
@@ -228,15 +261,60 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
228
261
|
}
|
|
229
262
|
}
|
|
230
263
|
|
|
264
|
+
const debugEnabled = process.env.GJC_DEBUG_ANTIGRAVITY_DISCOVERY === "1";
|
|
265
|
+
const keptModelIds: string[] = [];
|
|
266
|
+
const filteredByDenylist: string[] = [];
|
|
267
|
+
const filteredByRetirement: string[] = [];
|
|
268
|
+
const filteredByInternal: string[] = [];
|
|
269
|
+
const filteredByDisabled: string[] = [];
|
|
270
|
+
const keptModelDetails: Record<string, Partial<AntigravityDiscoveryApiModel>> = {};
|
|
271
|
+
const filteredModelDetails: Record<string, Partial<AntigravityDiscoveryApiModel> & { reason: string }> = {};
|
|
272
|
+
|
|
231
273
|
const models: Model<"google-gemini-cli">[] = [];
|
|
274
|
+
const modelDropReasons: Record<string, string> = {};
|
|
232
275
|
|
|
233
276
|
for (const [modelId, model] of Object.entries(parsed.models ?? {})) {
|
|
234
|
-
|
|
235
|
-
|
|
277
|
+
let dropReason: string | undefined;
|
|
278
|
+
|
|
279
|
+
if (ANTIGRAVITY_DISCOVERY_DENYLIST.has(modelId)) {
|
|
280
|
+
dropReason = "denylist";
|
|
281
|
+
} else if (isRetiredModelKey(targetProvider, modelId)) {
|
|
282
|
+
dropReason = "retired";
|
|
283
|
+
} else if (model.disabled === true) {
|
|
284
|
+
dropReason = "disabled";
|
|
285
|
+
} else if (model.isInternal === true && !surfacedModelIds.has(modelId)) {
|
|
286
|
+
dropReason = "isInternal-not-surfaced";
|
|
236
287
|
}
|
|
237
|
-
|
|
288
|
+
|
|
289
|
+
if (dropReason) {
|
|
290
|
+
modelDropReasons[modelId] = dropReason;
|
|
291
|
+
if (debugEnabled) {
|
|
292
|
+
if (dropReason === "denylist") filteredByDenylist.push(modelId);
|
|
293
|
+
else if (dropReason === "retired") filteredByRetirement.push(modelId);
|
|
294
|
+
else if (dropReason === "disabled") filteredByDisabled.push(modelId);
|
|
295
|
+
else filteredByInternal.push(modelId);
|
|
296
|
+
filteredModelDetails[modelId] = {
|
|
297
|
+
...model,
|
|
298
|
+
reason:
|
|
299
|
+
dropReason === "retired"
|
|
300
|
+
? `retired-${targetProvider}`
|
|
301
|
+
: dropReason === "isInternal-not-surfaced"
|
|
302
|
+
? "internal-not-surfaced"
|
|
303
|
+
: dropReason,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
238
306
|
continue;
|
|
239
307
|
}
|
|
308
|
+
if (debugEnabled) {
|
|
309
|
+
keptModelIds.push(modelId);
|
|
310
|
+
keptModelDetails[modelId] = {
|
|
311
|
+
displayName: model.displayName,
|
|
312
|
+
isInternal: model.isInternal,
|
|
313
|
+
entitlement: model.entitlement,
|
|
314
|
+
disabled: model.disabled,
|
|
315
|
+
quota: model.quota,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
240
318
|
|
|
241
319
|
const supportsImages = model.supportsImages === true;
|
|
242
320
|
models.push({
|
|
@@ -258,7 +336,102 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
258
336
|
});
|
|
259
337
|
}
|
|
260
338
|
|
|
339
|
+
if (Object.keys(modelDropReasons).length > 0) {
|
|
340
|
+
debugLogger.debug("Antigravity discovery dropped models", {
|
|
341
|
+
endpoint: redactedEndpoint,
|
|
342
|
+
dropReasons: modelDropReasons,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
debugLogger.debug("Antigravity discovery kept models", {
|
|
347
|
+
endpoint: redactedEndpoint,
|
|
348
|
+
keptModelIds: models.map(m => m.id),
|
|
349
|
+
});
|
|
350
|
+
|
|
261
351
|
models.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
352
|
+
|
|
353
|
+
if (debugEnabled) {
|
|
354
|
+
const allRawModelIds = Object.keys(parsed.models ?? {}).sort();
|
|
355
|
+
console.error("[GJC_DEBUG_ANTIGRAVITY_DISCOVERY]");
|
|
356
|
+
console.error(` HTTP Status: ${response.status}`);
|
|
357
|
+
console.error(` User-Agent: ${userAgent}`);
|
|
358
|
+
console.error(` Endpoint: ${endpoint}`);
|
|
359
|
+
console.error(` Total raw models from response: ${allRawModelIds.length}`);
|
|
360
|
+
console.error("\n === KEPT MODELS ===");
|
|
361
|
+
console.error(` Count: ${keptModelIds.length}`);
|
|
362
|
+
for (const modelId of keptModelIds) {
|
|
363
|
+
const details = keptModelDetails[modelId];
|
|
364
|
+
const attrs = [];
|
|
365
|
+
if (details?.entitlement) attrs.push(`entitlement=${details.entitlement}`);
|
|
366
|
+
if (details?.disabled === true) attrs.push("disabled=true");
|
|
367
|
+
if (details?.quota) attrs.push(`quota=${details.quota}`);
|
|
368
|
+
if (details?.isInternal === true) attrs.push("isInternal=true");
|
|
369
|
+
const attrStr = attrs.length > 0 ? ` [${attrs.join(", ")}]` : "";
|
|
370
|
+
console.error(` ${modelId}${attrStr}`);
|
|
371
|
+
}
|
|
372
|
+
if (filteredByDenylist.length > 0) {
|
|
373
|
+
console.error(`\n === FILTERED BY DENYLIST ===`);
|
|
374
|
+
console.error(` Count: ${filteredByDenylist.length}`);
|
|
375
|
+
for (const modelId of filteredByDenylist) {
|
|
376
|
+
const details = filteredModelDetails[modelId];
|
|
377
|
+
const attrs = [];
|
|
378
|
+
if (details?.entitlement) attrs.push(`entitlement=${details.entitlement}`);
|
|
379
|
+
if (details?.disabled === true) attrs.push("disabled=true");
|
|
380
|
+
if (details?.quota) attrs.push(`quota=${details.quota}`);
|
|
381
|
+
if (details?.isInternal === true) attrs.push("isInternal=true");
|
|
382
|
+
const attrStr = attrs.length > 0 ? ` [${attrs.join(", ")}]` : "";
|
|
383
|
+
console.error(` ${modelId}${attrStr}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (filteredByRetirement.length > 0) {
|
|
387
|
+
console.error(`\n === FILTERED BY RETIREMENT (${targetProvider}) ===`);
|
|
388
|
+
console.error(` Count: ${filteredByRetirement.length}`);
|
|
389
|
+
for (const modelId of filteredByRetirement) {
|
|
390
|
+
const details = filteredModelDetails[modelId];
|
|
391
|
+
const attrs = [];
|
|
392
|
+
if (details?.entitlement) attrs.push(`entitlement=${details.entitlement}`);
|
|
393
|
+
if (details?.disabled === true) attrs.push("disabled=true");
|
|
394
|
+
if (details?.quota) attrs.push(`quota=${details.quota}`);
|
|
395
|
+
if (details?.isInternal === true) attrs.push("isInternal=true");
|
|
396
|
+
const attrStr = attrs.length > 0 ? ` [${attrs.join(", ")}]` : "";
|
|
397
|
+
console.error(` ${modelId}${attrStr}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (filteredByDisabled.length > 0) {
|
|
401
|
+
console.error(`\n === FILTERED (disabled) ===`);
|
|
402
|
+
console.error(` Count: ${filteredByDisabled.length}`);
|
|
403
|
+
for (const modelId of filteredByDisabled) {
|
|
404
|
+
const details = filteredModelDetails[modelId];
|
|
405
|
+
const attrs = [];
|
|
406
|
+
if (details?.entitlement) attrs.push(`entitlement=${details.entitlement}`);
|
|
407
|
+
if (details?.disabled === true) attrs.push("disabled=true");
|
|
408
|
+
if (details?.quota) attrs.push(`quota=${details.quota}`);
|
|
409
|
+
if (details?.isInternal === true) attrs.push("isInternal=true");
|
|
410
|
+
const attrStr = attrs.length > 0 ? ` [${attrs.join(", ")}]` : "";
|
|
411
|
+
console.error(` ${modelId}${attrStr}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (filteredByInternal.length > 0) {
|
|
415
|
+
console.error(`\n === FILTERED (internal, not surfaced) ===`);
|
|
416
|
+
console.error(` Count: ${filteredByInternal.length}`);
|
|
417
|
+
for (const modelId of filteredByInternal) {
|
|
418
|
+
const details = filteredModelDetails[modelId];
|
|
419
|
+
const attrs = [];
|
|
420
|
+
if (details?.entitlement) attrs.push(`entitlement=${details.entitlement}`);
|
|
421
|
+
if (details?.disabled === true) attrs.push("disabled=true");
|
|
422
|
+
if (details?.quota) attrs.push(`quota=${details.quota}`);
|
|
423
|
+
if (details?.isInternal === true) attrs.push("isInternal=true");
|
|
424
|
+
const attrStr = attrs.length > 0 ? ` [${attrs.join(", ")}]` : "";
|
|
425
|
+
console.error(` ${modelId}${attrStr}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
console.error(`\n === SURFACE INFO ===`);
|
|
429
|
+
console.error(` agentModelSorts surfaced: ${surfacedModelIds.size}`);
|
|
430
|
+
if (surfacedModelIds.size > 0) {
|
|
431
|
+
console.error(` ${Array.from(surfacedModelIds).sort().join(", ")}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
262
435
|
return models;
|
|
263
436
|
}
|
|
264
437
|
|
|
@@ -283,3 +456,15 @@ function toPositiveNumber(value: unknown, fallback: number): number {
|
|
|
283
456
|
function trimTrailingSlashes(value: string): string {
|
|
284
457
|
return value.replace(/\/+$/, "");
|
|
285
458
|
}
|
|
459
|
+
|
|
460
|
+
export function getAntigravityDiscoveryUserAgent(): string {
|
|
461
|
+
return process.env.PI_AI_ANTIGRAVITY_USER_AGENT ? getAntigravityUserAgent() : getGeminiCliUserAgent();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function redactAntigravityEndpoint(value: string): string {
|
|
465
|
+
try {
|
|
466
|
+
return new URL(value).origin;
|
|
467
|
+
} catch {
|
|
468
|
+
return "invalid endpoint";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -23,11 +23,12 @@ export declare function getStreamIdleTimeoutRemediationMs(): number;
|
|
|
23
23
|
* Returns the idle timeout used for OpenAI-family streaming transports.
|
|
24
24
|
*
|
|
25
25
|
* Honors `GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS` first (`PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` is the legacy alias). Set `=0` to disable.
|
|
26
|
+
* A configured model timeout takes precedence over the built-in provider/model floors; environment overrides win over it.
|
|
26
27
|
* When `provider` is given, long-reasoning hosts (xAI Grok and Grok Build) use that floor instead of the 120s default.
|
|
27
28
|
* Grok models reached through other OpenAI-compatible hosts (`openrouter/x-ai/grok-*`, kilo, litellm, …) get the
|
|
28
29
|
* same floor keyed on the model id, because long-reasoning silence is a property of the model (#4797).
|
|
29
30
|
*/
|
|
30
|
-
export declare function getOpenAIStreamIdleTimeoutMs(provider?: string, modelId?: string): number | undefined;
|
|
31
|
+
export declare function getOpenAIStreamIdleTimeoutMs(provider?: string, modelId?: string, configuredTimeoutMs?: number): number | undefined;
|
|
31
32
|
/**
|
|
32
33
|
* Returns the timeout used while waiting for the first stream event.
|
|
33
34
|
* The first token can legitimately take longer than later inter-event gaps,
|
|
@@ -57,7 +58,7 @@ export declare function getStreamFirstEventTimeoutMs(idleTimeoutMs?: number, fal
|
|
|
57
58
|
* window so a short post-connect first-event budget cannot kill legitimate
|
|
58
59
|
* slow setup.
|
|
59
60
|
*/
|
|
60
|
-
export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number, modelId?: string): number | undefined;
|
|
61
|
+
export declare function resolveOpenAISdkRequestTimeoutMs(provider: string, streamFirstEventTimeoutOverride?: number, modelId?: string, configuredIdleTimeoutMs?: number, effectiveIdleTimeoutOverride?: number): number | undefined;
|
|
61
62
|
/**
|
|
62
63
|
* Resolves the Anthropic SDK client `timeout` so stalled-before-headers requests
|
|
63
64
|
* are bounded. The Anthropic first-event watchdog deliberately arms only once
|
|
@@ -46,7 +46,7 @@ export function getProviderFirstEventTimeoutFallbackMs(provider: string): number
|
|
|
46
46
|
return provider === "kimi-code" ? KIMI_CODE_FIRST_EVENT_TIMEOUT_MS : undefined;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function normalizeIdleTimeoutMs(value: string | undefined, fallback: number): number | undefined {
|
|
49
|
+
function normalizeIdleTimeoutMs(value: string | undefined, fallback: number | undefined): number | undefined {
|
|
50
50
|
if (value === undefined) return fallback;
|
|
51
51
|
const parsed = Number(value);
|
|
52
52
|
if (!Number.isFinite(parsed)) return fallback;
|
|
@@ -86,16 +86,29 @@ export function getStreamIdleTimeoutRemediationMs(): number {
|
|
|
86
86
|
* Returns the idle timeout used for OpenAI-family streaming transports.
|
|
87
87
|
*
|
|
88
88
|
* Honors `GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS` first (`PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` is the legacy alias). Set `=0` to disable.
|
|
89
|
+
* A configured model timeout takes precedence over the built-in provider/model floors; environment overrides win over it.
|
|
89
90
|
* When `provider` is given, long-reasoning hosts (xAI Grok and Grok Build) use that floor instead of the 120s default.
|
|
90
91
|
* Grok models reached through other OpenAI-compatible hosts (`openrouter/x-ai/grok-*`, kilo, litellm, …) get the
|
|
91
92
|
* same floor keyed on the model id, because long-reasoning silence is a property of the model (#4797).
|
|
92
93
|
*/
|
|
93
|
-
export function getOpenAIStreamIdleTimeoutMs(
|
|
94
|
+
export function getOpenAIStreamIdleTimeoutMs(
|
|
95
|
+
provider?: string,
|
|
96
|
+
modelId?: string,
|
|
97
|
+
configuredTimeoutMs?: number,
|
|
98
|
+
): number | undefined {
|
|
99
|
+
const builtInFallback =
|
|
100
|
+
getProviderStreamIdleTimeoutFallbackMs(provider ?? "") ??
|
|
101
|
+
(isGrokModelId(modelId) ? LONG_REASONING_STREAM_IDLE_TIMEOUT_MS : undefined) ??
|
|
102
|
+
DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
103
|
+
let configuredFallback: number | undefined = builtInFallback;
|
|
104
|
+
if (configuredTimeoutMs === 0) {
|
|
105
|
+
configuredFallback = undefined;
|
|
106
|
+
} else if (configuredTimeoutMs !== undefined && Number.isFinite(configuredTimeoutMs) && configuredTimeoutMs > 0) {
|
|
107
|
+
configuredFallback = Math.max(1, Math.trunc(configuredTimeoutMs));
|
|
108
|
+
}
|
|
94
109
|
return normalizeIdleTimeoutMs(
|
|
95
110
|
$env.GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS ?? $env.PI_STREAM_IDLE_TIMEOUT_MS ?? $env.PI_OPENAI_STREAM_IDLE_TIMEOUT_MS,
|
|
96
|
-
|
|
97
|
-
(isGrokModelId(modelId) ? LONG_REASONING_STREAM_IDLE_TIMEOUT_MS : undefined) ??
|
|
98
|
-
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
111
|
+
configuredFallback,
|
|
99
112
|
);
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -139,19 +152,26 @@ export function resolveOpenAISdkRequestTimeoutMs(
|
|
|
139
152
|
provider: string,
|
|
140
153
|
streamFirstEventTimeoutOverride?: number,
|
|
141
154
|
modelId?: string,
|
|
155
|
+
configuredIdleTimeoutMs?: number,
|
|
156
|
+
effectiveIdleTimeoutOverride?: number,
|
|
142
157
|
): number | undefined {
|
|
143
158
|
const providerFirstEventFallbackMs = getProviderFirstEventTimeoutFallbackMs(provider);
|
|
144
159
|
const envSdkTimeoutMs = getStreamFirstEventTimeoutMs(
|
|
145
|
-
getOpenAIStreamIdleTimeoutMs(provider, modelId),
|
|
160
|
+
effectiveIdleTimeoutOverride ?? getOpenAIStreamIdleTimeoutMs(provider, modelId, configuredIdleTimeoutMs),
|
|
146
161
|
providerFirstEventFallbackMs,
|
|
147
162
|
);
|
|
148
163
|
if (streamFirstEventTimeoutOverride === 0) return undefined;
|
|
164
|
+
let sdkTimeoutMs: number | undefined;
|
|
149
165
|
if (streamFirstEventTimeoutOverride !== undefined) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
sdkTimeoutMs =
|
|
167
|
+
providerFirstEventFallbackMs !== undefined
|
|
168
|
+
? streamFirstEventTimeoutOverride
|
|
169
|
+
: Math.max(envSdkTimeoutMs ?? 0, streamFirstEventTimeoutOverride);
|
|
170
|
+
} else {
|
|
171
|
+
sdkTimeoutMs = envSdkTimeoutMs;
|
|
153
172
|
}
|
|
154
|
-
return
|
|
173
|
+
if (sdkTimeoutMs === undefined || !Number.isFinite(sdkTimeoutMs) || sdkTimeoutMs <= 0) return undefined;
|
|
174
|
+
return Math.min(Math.trunc(sdkTimeoutMs), MAX_TIMER_DELAY_MS);
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
/**
|
|
@@ -62,15 +62,16 @@ async function postJson(url: string, body: Record<string, string | number>): Pro
|
|
|
62
62
|
/**
|
|
63
63
|
* Decoded shape of Anthropic's `/v1/oauth/token` response (both
|
|
64
64
|
* `authorization_code` exchange and `refresh_token` refresh return the same
|
|
65
|
-
* envelope). The `account`
|
|
66
|
-
* surface `accountId` / `email`
|
|
67
|
-
* `/api/oauth/profile` round-trip.
|
|
65
|
+
* envelope). The `account` and `organization` blocks are inlined alongside the
|
|
66
|
+
* tokens, so we can surface `accountId` / `email` / `organizationId` on
|
|
67
|
+
* {@link OAuthCredentials} without a separate `/api/oauth/profile` round-trip.
|
|
68
68
|
*/
|
|
69
69
|
interface AnthropicTokenResponse {
|
|
70
70
|
access_token: string;
|
|
71
71
|
refresh_token: string;
|
|
72
72
|
expires_in: number;
|
|
73
73
|
account?: { uuid?: string; email_address?: string };
|
|
74
|
+
organization?: { uuid?: string; name?: string };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function parseOAuthTokenResponse(responseBody: string, operation: string): AnthropicTokenResponse {
|
|
@@ -83,22 +84,26 @@ function parseOAuthTokenResponse(responseBody: string, operation: string): Anthr
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function nonEmptyString(value: unknown): string | undefined {
|
|
88
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
/**
|
|
87
|
-
* Lift the OAuth response's `account: { uuid, email_address }`
|
|
88
|
-
* {@link OAuthCredentials} so
|
|
89
|
-
* `metadata.user_id.account_uuid`, usage
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
+
* Lift the OAuth response's `account: { uuid, email_address }` and
|
|
93
|
+
* `organization: { uuid, name }` blocks onto {@link OAuthCredentials} so
|
|
94
|
+
* downstream identity propagation (e.g. `metadata.user_id.account_uuid`, usage
|
|
95
|
+
* tracking, one stored credential per organization) works without a separate
|
|
96
|
+
* `/api/oauth/profile` round-trip. Returns `undefined` for any field the
|
|
97
|
+
* response omits or carries as a non-string / empty value.
|
|
92
98
|
*/
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} {
|
|
97
|
-
const accountUuid = data.account?.uuid;
|
|
98
|
-
const emailAddress = data.account?.email_address;
|
|
99
|
+
function extractIdentityFromTokenResponse(
|
|
100
|
+
data: AnthropicTokenResponse,
|
|
101
|
+
): Pick<OAuthCredentials, "accountId" | "email" | "organizationId" | "organizationName"> {
|
|
99
102
|
return {
|
|
100
|
-
accountId:
|
|
101
|
-
email:
|
|
103
|
+
accountId: nonEmptyString(data.account?.uuid),
|
|
104
|
+
email: nonEmptyString(data.account?.email_address),
|
|
105
|
+
organizationId: nonEmptyString(data.organization?.uuid),
|
|
106
|
+
organizationName: nonEmptyString(data.organization?.name),
|
|
102
107
|
};
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -182,14 +187,12 @@ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
|
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
const tokenData = parseOAuthTokenResponse(responseBody, "token exchange");
|
|
185
|
-
const { accountId, email } = extractAccountFromTokenResponse(tokenData);
|
|
186
190
|
|
|
187
191
|
return {
|
|
188
192
|
refresh: tokenData.refresh_token,
|
|
189
193
|
access: tokenData.access_token,
|
|
190
194
|
expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
|
|
191
|
-
|
|
192
|
-
email,
|
|
195
|
+
...extractIdentityFromTokenResponse(tokenData),
|
|
193
196
|
};
|
|
194
197
|
}
|
|
195
198
|
}
|
|
@@ -221,13 +224,11 @@ export async function refreshAnthropicToken(refreshToken: string): Promise<OAuth
|
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
const data = parseOAuthTokenResponse(responseBody, "token refresh");
|
|
224
|
-
const { accountId, email } = extractAccountFromTokenResponse(data);
|
|
225
227
|
|
|
226
228
|
return {
|
|
227
229
|
refresh: data.refresh_token || refreshToken,
|
|
228
230
|
access: data.access_token,
|
|
229
231
|
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
230
|
-
|
|
231
|
-
email,
|
|
232
|
+
...extractIdentityFromTokenResponse(data),
|
|
232
233
|
};
|
|
233
234
|
}
|
|
@@ -193,19 +193,42 @@ export async function validateOpenAICompatibleApiKey(options: OpenAICompatibleVa
|
|
|
193
193
|
typeof parsed === "object" && parsed !== null && "choices" in parsed
|
|
194
194
|
? (parsed as { choices?: unknown }).choices
|
|
195
195
|
: undefined;
|
|
196
|
-
|
|
197
|
-
const message =
|
|
198
|
-
typeof firstChoice === "object" &&
|
|
199
|
-
firstChoice !== null &&
|
|
200
|
-
"message" in firstChoice &&
|
|
201
|
-
typeof (firstChoice as { message?: unknown }).message === "object" &&
|
|
202
|
-
!Array.isArray((firstChoice as { message?: unknown }).message)
|
|
203
|
-
? (firstChoice as { message: Record<string, unknown> }).message
|
|
204
|
-
: undefined;
|
|
205
|
-
const content = message?.content;
|
|
206
|
-
if (!(typeof content === "string" && content.trim().length > 0)) {
|
|
196
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
207
197
|
throw validationFailure(options.provider, options.apiKey, "the inference probe returned no choices");
|
|
208
198
|
}
|
|
199
|
+
const firstChoice: unknown = choices[0];
|
|
200
|
+
if (
|
|
201
|
+
typeof firstChoice !== "object" ||
|
|
202
|
+
firstChoice === null ||
|
|
203
|
+
Array.isArray(firstChoice) ||
|
|
204
|
+
!("message" in firstChoice)
|
|
205
|
+
) {
|
|
206
|
+
throw validationFailure(
|
|
207
|
+
options.provider,
|
|
208
|
+
options.apiKey,
|
|
209
|
+
"the inference probe returned choices without a valid message",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const choice = firstChoice as Record<string, unknown>;
|
|
213
|
+
const rawMessage = choice.message;
|
|
214
|
+
if (typeof rawMessage !== "object" || rawMessage === null || Array.isArray(rawMessage)) {
|
|
215
|
+
throw validationFailure(
|
|
216
|
+
options.provider,
|
|
217
|
+
options.apiKey,
|
|
218
|
+
"the inference probe returned choices without a valid message",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const message = rawMessage as Record<string, unknown>;
|
|
222
|
+
const hasContent = typeof message.content === "string" && message.content.trim().length > 0;
|
|
223
|
+
const hasReasoningContent =
|
|
224
|
+
typeof message.reasoning_content === "string" && message.reasoning_content.trim().length > 0;
|
|
225
|
+
if (!hasContent && !hasReasoningContent && choice.finish_reason !== "length") {
|
|
226
|
+
throw validationFailure(
|
|
227
|
+
options.provider,
|
|
228
|
+
options.apiKey,
|
|
229
|
+
"the inference probe returned choices with empty content",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
209
232
|
}
|
|
210
233
|
return;
|
|
211
234
|
}
|
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GLM ZCode OAuth flow (UNOFFICIAL, opt-in).
|
|
3
|
-
*
|
|
4
|
-
* Replicates how the ZCode desktop app turns a Z.AI login into usable GLM model
|
|
5
|
-
* access. This is NOT an official Z.AI OAuth client: it reuses ZCode's authorize
|
|
6
|
-
* page, broker, and a custom-protocol redirect. It may break at any time and may
|
|
7
|
-
* violate ZCode/Z.AI Terms of Service. Endpoints/client id are overridable via
|
|
8
|
-
* `ZCODE_OAUTH_*` environment variables.
|
|
9
|
-
*
|
|
10
|
-
* Verified end-to-end against the ZCode host bundle (`resolveZaiApiKey` /
|
|
11
|
-
* `resolveBizApiKey`) and live traffic:
|
|
12
|
-
* 1. Authorize: GET {authorize}?redirect_uri=zcode://oauth/callback&response_type=code&client_id=...&state=...
|
|
13
|
-
* (custom-protocol redirect → a CLI cannot catch it, so the user pastes the code/redirect URL)
|
|
14
|
-
* 2. Broker: POST {broker} { provider:"zai", code, redirect_uri, state }
|
|
15
|
-
* → { data: { token: <ZCode JWT>, zai: { access_token: <upstream Z.AI token> } } }
|
|
16
|
-
* 3. Business: POST {z/login} { token: <upstream Z.AI token> } → { data: { access_token: <business token> } }
|
|
17
|
-
* 4. Provision: with the business token, GET getCustomerInfo → default org/project,
|
|
18
|
-
* GET/POST .../api_keys (find/create a key named "zcode-api-key"),
|
|
19
|
-
* GET .../api_keys/copy/{id} → secretKey ⇒ a real Z.AI API key "{id}.{secret}".
|
|
20
|
-
*
|
|
21
|
-
* Credential mapping:
|
|
22
|
-
* - `access` = the provisioned **Z.AI API key** ("{id}.{secret}"). Model requests go to
|
|
23
|
-
* `https://api.z.ai/api/anthropic/v1/messages` with `Authorization: Bearer <key>`
|
|
24
|
-
* (exactly like a dashboard key) — NO zcode.z.ai gateway, NO captcha.
|
|
25
|
-
* - `refresh` = the upstream Z.AI OAuth access token (used to re-provision the key).
|
|
26
|
-
* The API key is long-lived, so `expires` is set far in the future.
|
|
27
|
-
*
|
|
28
|
-
* This provider must NEVER force `isOAuth=true`: the key is a plain Z.AI API key, and
|
|
29
|
-
* api.z.ai is not api.anthropic.com, so the Anthropic path already emits a plain bearer.
|
|
30
|
-
*/
|
|
31
1
|
import { OAuthCallbackFlow } from "./callback-server";
|
|
32
2
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
33
3
|
export declare const GLM_ZCODE_REFRESH_SKEW_MS: number;
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Replicates how the ZCode desktop app turns a Z.AI login into usable GLM model
|
|
5
5
|
* access. This is NOT an official Z.AI OAuth client: it reuses ZCode's authorize
|
|
6
6
|
* page, broker, and a custom-protocol redirect. It may break at any time and may
|
|
7
|
-
* violate ZCode/Z.AI Terms of Service. Endpoints
|
|
8
|
-
* `ZCODE_OAUTH_*`
|
|
7
|
+
* violate ZCode/Z.AI Terms of Service. Endpoints and the client id are
|
|
8
|
+
* overridable via `ZCODE_OAUTH_*` from the launching shell or user-owned env
|
|
9
|
+
* files. A project `.env` is not a trusted source for those values.
|
|
9
10
|
*
|
|
10
11
|
* Verified end-to-end against the ZCode host bundle (`resolveZaiApiKey` /
|
|
11
12
|
* `resolveBizApiKey`) and live traffic:
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
* This provider must NEVER force `isOAuth=true`: the key is a plain Z.AI API key, and
|
|
29
30
|
* api.z.ai is not api.anthropic.com, so the Anthropic path already emits a plain bearer.
|
|
30
31
|
*/
|
|
32
|
+
import { $credentialEnv } from "@gajae-code/utils";
|
|
31
33
|
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions, parseCallbackInput } from "./callback-server";
|
|
32
34
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
33
35
|
|
|
@@ -52,9 +54,18 @@ export const GLM_ZCODE_ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
|
52
54
|
|
|
53
55
|
type FetchImpl = typeof globalThis.fetch;
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Trusted override for one `ZCODE_OAUTH_*` name.
|
|
59
|
+
*
|
|
60
|
+
* These values choose where the authorization code and upstream token are sent.
|
|
61
|
+
* `$credentialEnv` keeps the launching shell and user-owned env files and skips
|
|
62
|
+
* a project `.env`. A shell value is also skipped when it repeats that file
|
|
63
|
+
* or the project declaration is dynamic, and when no other trusted source
|
|
64
|
+
* sets the name an empty value still uses the built-in endpoint.
|
|
65
|
+
*/
|
|
55
66
|
function envOr(name: string, fallback: string): string {
|
|
56
|
-
const value =
|
|
57
|
-
return value
|
|
67
|
+
const value = $credentialEnv(name)?.trim();
|
|
68
|
+
return value ? value : fallback;
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
function resolveAuthorizeUrl(): string {
|
|
@@ -63,6 +63,41 @@ interface PKCE {
|
|
|
63
63
|
challenge: string;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Extract a readable error message from OpenAI OAuth error response.
|
|
68
|
+
* Handles both string errors (error + error_description) and object errors (code/type + message).
|
|
69
|
+
*/
|
|
70
|
+
function extractOpenAIErrorMessage(body: unknown): string {
|
|
71
|
+
if (typeof body !== "object" || body === null) return "";
|
|
72
|
+
|
|
73
|
+
const obj = body as Record<string, unknown>;
|
|
74
|
+
const error = obj.error;
|
|
75
|
+
|
|
76
|
+
if (typeof error === "string") {
|
|
77
|
+
// String error: "invalid_grant", optionally explained by error_description.
|
|
78
|
+
// Parts are joined so an empty error string never leaves a dangling separator.
|
|
79
|
+
const desc = obj.error_description;
|
|
80
|
+
const parts = desc !== undefined && typeof desc === "string" && desc.length > 0 ? [error, desc] : [error];
|
|
81
|
+
return parts.filter(part => part.length > 0).join(": ");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof error === "object" && error !== null) {
|
|
85
|
+
// Object error: {message, type, code}. `code` wins over `type`, but only when
|
|
86
|
+
// it is a usable string — a numeric or empty code must fall back to the type
|
|
87
|
+
// rather than silently dropping both.
|
|
88
|
+
const err = error as Record<string, unknown>;
|
|
89
|
+
const code = typeof err.code === "string" && err.code.length > 0 ? err.code : err.type;
|
|
90
|
+
const msg = err.message;
|
|
91
|
+
|
|
92
|
+
const parts: string[] = [];
|
|
93
|
+
if (typeof code === "string" && code.length > 0) parts.push(code);
|
|
94
|
+
if (typeof msg === "string" && msg.length > 0) parts.push(msg);
|
|
95
|
+
return parts.join(": ");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
class OpenAICodexOAuthFlow extends OAuthCallbackFlow {
|
|
67
102
|
constructor(
|
|
68
103
|
ctrl: OAuthController,
|
|
@@ -120,9 +155,9 @@ async function exchangeCodeForToken(code: string, verifier: string, redirectUri:
|
|
|
120
155
|
if (!tokenResponse.ok) {
|
|
121
156
|
let detail = `${tokenResponse.status}`;
|
|
122
157
|
try {
|
|
123
|
-
const body =
|
|
124
|
-
|
|
125
|
-
|
|
158
|
+
const body = await tokenResponse.json();
|
|
159
|
+
const errorMsg = extractOpenAIErrorMessage(body);
|
|
160
|
+
if (errorMsg) detail = `${tokenResponse.status} ${errorMsg}`;
|
|
126
161
|
} catch {}
|
|
127
162
|
throw new Error(`Token exchange failed: ${detail}`);
|
|
128
163
|
}
|
|
@@ -272,9 +307,9 @@ export async function refreshOpenAICodexToken(refreshToken: string): Promise<OAu
|
|
|
272
307
|
if (!response.ok) {
|
|
273
308
|
let detail = `${response.status}`;
|
|
274
309
|
try {
|
|
275
|
-
const body =
|
|
276
|
-
|
|
277
|
-
|
|
310
|
+
const body = await response.json();
|
|
311
|
+
const errorMsg = extractOpenAIErrorMessage(body);
|
|
312
|
+
if (errorMsg) detail = `${response.status} ${errorMsg}`;
|
|
278
313
|
} catch {}
|
|
279
314
|
throw new Error(`OpenAI Codex token refresh failed: ${detail}`);
|
|
280
315
|
}
|