@gajae-code/ai 0.18.12 → 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 +35 -0
- package/dist/types/auth-storage.d.ts +18 -5
- package/dist/types/model-pricing.d.ts +8 -0
- package/dist/types/model-thinking.d.ts +11 -0
- 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 +15 -0
- package/dist/types/providers/google-gemini-headers.d.ts +1 -1
- package/dist/types/utils/discovery/antigravity.d.ts +5 -1
- package/dist/types/utils/oauth/glm-zcode.d.ts +0 -30
- package/package.json +3 -3
- package/src/auth-storage.ts +261 -52
- 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 +60 -5
- package/src/models.json +245 -0
- package/src/models.ts +10 -3
- package/src/provider-models/google.ts +4 -0
- package/src/providers/amazon-bedrock.d.ts +17 -2
- package/src/providers/amazon-bedrock.ts +56 -5
- package/src/providers/anthropic.d.ts +15 -0
- package/src/providers/anthropic.ts +91 -14
- package/src/providers/google-gemini-headers.d.ts +1 -1
- package/src/providers/google-gemini-headers.ts +6 -6
- package/src/providers/transform-messages.ts +32 -1
- package/src/stream.ts +4 -4
- package/src/utils/discovery/antigravity.ts +69 -26
- 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/proxy.ts +35 -6
|
@@ -117,6 +117,14 @@ export interface CpaToolAliasRestoreFailure {
|
|
|
117
117
|
*/
|
|
118
118
|
export declare function parseCpaToolAliasRestoreFailure(error: unknown): CpaToolAliasRestoreFailure | undefined;
|
|
119
119
|
export declare function isCpaToolAliasRestoreFailure(error: unknown): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* How many breakpoints we are still willing to generate after a gateway has
|
|
122
|
+
* rejected a previous attempt. `explicit` mode normally emits two (a reusable
|
|
123
|
+
* prefix anchor on the last assistant turn plus a refresh point on the current
|
|
124
|
+
* user turn), so stepping down to one still caches the prefix, and only the
|
|
125
|
+
* final step gives caching up entirely.
|
|
126
|
+
*/
|
|
127
|
+
type GeneratedCacheBudget = 2 | 1 | 0;
|
|
120
128
|
export declare const claudeCodeEntrypoint = "sdk-cli";
|
|
121
129
|
export declare const claudeToolPrefix: string;
|
|
122
130
|
export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
@@ -228,6 +236,8 @@ export type AnthropicClientOptionsResult = {
|
|
|
228
236
|
fetchOptions?: AnthropicSdkClientOptions["fetchOptions"];
|
|
229
237
|
};
|
|
230
238
|
export declare function resolveGlmZcodeAnthropicBaseUrl(): string;
|
|
239
|
+
/** Compresses request bodies with gzip for first-party Anthropic endpoints only. */
|
|
240
|
+
export declare function wrapAnthropicFetchForGzip(baseFetch: FetchImpl): FetchImpl;
|
|
231
241
|
export declare function isProviderRetryableError(error: unknown, provider?: string): boolean;
|
|
232
242
|
export type AnthropicUsageLike = {
|
|
233
243
|
cache_creation?: {
|
|
@@ -261,6 +271,11 @@ type SystemBlockOptions = {
|
|
|
261
271
|
export declare function buildAnthropicSystemBlocks(systemPrompt: readonly string[] | undefined, options?: SystemBlockOptions): AnthropicSystemBlock[] | undefined;
|
|
262
272
|
export declare function normalizeExtraBetas(betas?: string[] | string): string[];
|
|
263
273
|
export declare function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): AnthropicClientOptionsResult;
|
|
274
|
+
type AnthropicCacheParams = MessageCreateParamsStreaming & {
|
|
275
|
+
cache_control?: AnthropicCacheControl;
|
|
276
|
+
};
|
|
277
|
+
type AnthropicCacheMode = "automatic" | "explicit" | "none";
|
|
278
|
+
export declare function applyPromptCaching(params: AnthropicCacheParams, cacheMode: AnthropicCacheMode, cacheControl?: AnthropicCacheControl, budget?: GeneratedCacheBudget): void;
|
|
264
279
|
export declare function normalizeCacheControlTtlOrdering(params: MessageCreateParamsStreaming): void;
|
|
265
280
|
export declare function convertAnthropicMessages(messages: Message[], model: Model<"anthropic-messages">, isOAuthToken: boolean, options?: {
|
|
266
281
|
repairLatestAssistantThinking?: boolean;
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import { scheduler } from "node:timers/promises";
|
|
5
5
|
import * as tls from "node:tls";
|
|
6
|
+
import { gzipSync } from "node:zlib";
|
|
6
7
|
import Anthropic, { type ClientOptions as AnthropicSdkClientOptions } from "@anthropic-ai/sdk";
|
|
7
8
|
import type {
|
|
8
9
|
ContentBlockParam,
|
|
@@ -34,8 +35,9 @@ import {
|
|
|
34
35
|
} from "../adapter-internals/provider-safety-stop";
|
|
35
36
|
import {
|
|
36
37
|
getMiniMaxThinkingMode,
|
|
37
|
-
|
|
38
|
+
hasAnthropicModelSamplingRestrictions,
|
|
38
39
|
mapEffortToAnthropicAdaptiveEffort,
|
|
40
|
+
requiresAnthropicThinkingDisableWhenOff,
|
|
39
41
|
supportsAnthropicAdaptiveThinkingDisplay as supportsAdaptiveThinkingDisplay,
|
|
40
42
|
} from "../model-thinking";
|
|
41
43
|
import { calculateCost } from "../models";
|
|
@@ -1586,6 +1588,51 @@ function wrapAnthropicFetchForBoundedRateLimits(baseFetch: FetchImpl, maxRetryDe
|
|
|
1586
1588
|
);
|
|
1587
1589
|
}
|
|
1588
1590
|
|
|
1591
|
+
/** Body bytes for a request `BodyInit`, or `undefined` when the body cannot be inspected. */
|
|
1592
|
+
function requestBodyBuffer(body: RequestInit["body"]): Buffer | undefined {
|
|
1593
|
+
if (typeof body === "string") return Buffer.from(body, "utf-8");
|
|
1594
|
+
if (body instanceof ArrayBuffer) return Buffer.from(body);
|
|
1595
|
+
if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
1596
|
+
return undefined;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/** Compresses request bodies with gzip for first-party Anthropic endpoints only. */
|
|
1600
|
+
export function wrapAnthropicFetchForGzip(baseFetch: FetchImpl): FetchImpl {
|
|
1601
|
+
return Object.assign(
|
|
1602
|
+
async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
1603
|
+
const url = new URL(input instanceof Request ? input.url : String(input), "https://api.anthropic.com");
|
|
1604
|
+
const isFirstPartyAnthropicApi =
|
|
1605
|
+
url.protocol.toLowerCase() === "https:" &&
|
|
1606
|
+
url.hostname.toLowerCase() === "api.anthropic.com" &&
|
|
1607
|
+
(url.port === "" || url.port === "443");
|
|
1608
|
+
const bodyBuffer = init?.body ? requestBodyBuffer(init.body) : undefined;
|
|
1609
|
+
if (!isFirstPartyAnthropicApi || !bodyBuffer) return baseFetch(input, init);
|
|
1610
|
+
|
|
1611
|
+
let compressedBody: Buffer;
|
|
1612
|
+
try {
|
|
1613
|
+
compressedBody = gzipSync(bodyBuffer);
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
logger.debug("anthropic: gzip compression failed, falling back to uncompressed", {
|
|
1616
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1617
|
+
});
|
|
1618
|
+
return baseFetch(input, init);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// The caller's Content-Length describes the uncompressed body; drop it so
|
|
1622
|
+
// fetch recomputes the length for the compressed payload.
|
|
1623
|
+
const headers = new Headers(init?.headers ?? {});
|
|
1624
|
+
headers.set("Content-Encoding", "gzip");
|
|
1625
|
+
headers.delete("Content-Length");
|
|
1626
|
+
logger.debug("anthropic: gzip compression", {
|
|
1627
|
+
uncompressed: bodyBuffer.length,
|
|
1628
|
+
compressed: compressedBody.length,
|
|
1629
|
+
});
|
|
1630
|
+
return baseFetch(input, { ...init, body: compressedBody, headers });
|
|
1631
|
+
},
|
|
1632
|
+
baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1589
1636
|
// The Anthropic SDK logs malformed SSE frames directly before rethrowing them.
|
|
1590
1637
|
// We surface the resulting provider error ourselves, so keep the SDK quiet.
|
|
1591
1638
|
const ANTHROPIC_SDK_LOG_LEVEL = "off" as const;
|
|
@@ -2289,6 +2336,9 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2289
2336
|
}
|
|
2290
2337
|
if (droppedForcedToolChoice) {
|
|
2291
2338
|
delete nextParams.tool_choice;
|
|
2339
|
+
if (options?.thinkingEnabled === false && requiresAnthropicThinkingDisableWhenOff(model.id)) {
|
|
2340
|
+
nextParams.thinking = { type: "disabled" };
|
|
2341
|
+
}
|
|
2292
2342
|
}
|
|
2293
2343
|
if (disableStrictTools) {
|
|
2294
2344
|
dropAnthropicStrictTools(nextParams);
|
|
@@ -3477,8 +3527,9 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
3477
3527
|
const tlsFetchOptions = buildClaudeCodeTlsFetchOptions(model, baseUrl);
|
|
3478
3528
|
const baseFetch = args.fetch ?? fetch;
|
|
3479
3529
|
const boundedFetch = wrapAnthropicFetchForBoundedRateLimits(baseFetch, args.maxRetryDelayMs);
|
|
3530
|
+
const gzipFetch = wrapAnthropicFetchForGzip(boundedFetch);
|
|
3480
3531
|
const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, args.providerSessionId, "anthropic");
|
|
3481
|
-
const providerScopedFetch = wrapFetchForOpenCodeGoSession(
|
|
3532
|
+
const providerScopedFetch = wrapFetchForOpenCodeGoSession(gzipFetch, openCodeGoSessionId);
|
|
3482
3533
|
const debugFetch = onSseEvent
|
|
3483
3534
|
? wrapFetchForSseDebug(providerScopedFetch, event => onSseEvent(event, model))
|
|
3484
3535
|
: providerScopedFetch;
|
|
@@ -3614,11 +3665,22 @@ function createClient(
|
|
|
3614
3665
|
* request drops `thinking`/`output_config`. Reports whether the forced-choice branch
|
|
3615
3666
|
* applied so the caller can keep the replayed history consistent with it.
|
|
3616
3667
|
*/
|
|
3617
|
-
function disableThinkingIfToolChoiceForced(params: MessageCreateParamsStreaming): boolean {
|
|
3668
|
+
function disableThinkingIfToolChoiceForced(params: MessageCreateParamsStreaming, modelId: string): boolean {
|
|
3618
3669
|
const toolChoice = params.tool_choice;
|
|
3619
3670
|
if (!toolChoice) return false;
|
|
3620
3671
|
if (toolChoice.type !== "any" && toolChoice.type !== "tool") return false;
|
|
3621
|
-
|
|
3672
|
+
if (requiresAnthropicThinkingDisableWhenOff(modelId)) {
|
|
3673
|
+
// Haiku 5+ enables adaptive thinking when `thinking` is omitted, which a forced tool
|
|
3674
|
+
// choice rejects, and applies `output_config.effort` to all response tokens including
|
|
3675
|
+
// tool-call arguments. Send an explicit `disabled` and keep the selected effort;
|
|
3676
|
+
// effort is capped at `high` while thinking is disabled.
|
|
3677
|
+
params.thinking = { type: "disabled" };
|
|
3678
|
+
const effort = params.output_config?.effort;
|
|
3679
|
+
if (effort === "xhigh" || effort === "max") params.output_config = { ...params.output_config, effort: "high" };
|
|
3680
|
+
return true;
|
|
3681
|
+
}
|
|
3682
|
+
// An explicit `disabled` is compatible with forced tool use and is kept.
|
|
3683
|
+
if (params.thinking?.type !== "disabled") delete params.thinking;
|
|
3622
3684
|
delete params.output_config;
|
|
3623
3685
|
return true;
|
|
3624
3686
|
}
|
|
@@ -3862,7 +3924,7 @@ function applyExplicitPromptCaching(
|
|
|
3862
3924
|
}
|
|
3863
3925
|
}
|
|
3864
3926
|
|
|
3865
|
-
function applyPromptCaching(
|
|
3927
|
+
export function applyPromptCaching(
|
|
3866
3928
|
params: AnthropicCacheParams,
|
|
3867
3929
|
cacheMode: AnthropicCacheMode,
|
|
3868
3930
|
cacheControl?: AnthropicCacheControl,
|
|
@@ -3871,9 +3933,22 @@ function applyPromptCaching(
|
|
|
3871
3933
|
if (!cacheControl || cacheMode === "none" || budget === 0) return;
|
|
3872
3934
|
validateCacheControls(params);
|
|
3873
3935
|
if (cacheMode === "automatic") {
|
|
3874
|
-
//
|
|
3875
|
-
//
|
|
3876
|
-
params.cache_control
|
|
3936
|
+
// A caller layout that already holds four block markers leaves no room for a new root marker;
|
|
3937
|
+
// adding one anyway made validateCacheControls reject the request. An existing root is still replaced.
|
|
3938
|
+
if (params.cache_control || countCacheControlBreakpoints(params) < 4) {
|
|
3939
|
+
params.cache_control = { ...cacheControl };
|
|
3940
|
+
}
|
|
3941
|
+
// #2383's moving breakpoint reasoning holds within a session, not across
|
|
3942
|
+
// sessions: anchor tools + system so new messages can reuse that prefix.
|
|
3943
|
+
if (
|
|
3944
|
+
budget > 1 &&
|
|
3945
|
+
Array.isArray(params.system) &&
|
|
3946
|
+
!params.system.some(block => block.cache_control) &&
|
|
3947
|
+
countCacheControlBreakpoints(params) < 4
|
|
3948
|
+
) {
|
|
3949
|
+
const lastBlock = params.system.at(-1);
|
|
3950
|
+
if (lastBlock) lastBlock.cache_control = { ...cacheControl };
|
|
3951
|
+
}
|
|
3877
3952
|
return;
|
|
3878
3953
|
}
|
|
3879
3954
|
applyExplicitPromptCaching(params, cacheControl, budget);
|
|
@@ -3947,8 +4022,8 @@ function buildParams(
|
|
|
3947
4022
|
seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX ? seqs.slice(0, ANTHROPIC_STOP_SEQUENCES_MAX) : seqs;
|
|
3948
4023
|
}
|
|
3949
4024
|
|
|
3950
|
-
// Opus 4.7+
|
|
3951
|
-
if (
|
|
4025
|
+
// Opus 4.7+ and Haiku 5+ reject non-default sampling parameters.
|
|
4026
|
+
if (hasAnthropicModelSamplingRestrictions(model.id)) {
|
|
3952
4027
|
delete params.top_p;
|
|
3953
4028
|
delete params.top_k;
|
|
3954
4029
|
delete params.temperature;
|
|
@@ -3968,7 +4043,9 @@ function buildParams(
|
|
|
3968
4043
|
}
|
|
3969
4044
|
|
|
3970
4045
|
const miniMaxMode = getMiniMaxThinkingMode(model, baseUrl);
|
|
3971
|
-
if (
|
|
4046
|
+
if (options?.thinkingEnabled === false && requiresAnthropicThinkingDisableWhenOff(model.id)) {
|
|
4047
|
+
params.thinking = { type: "disabled" };
|
|
4048
|
+
} else if (model.reasoning && miniMaxMode === "toggle") {
|
|
3972
4049
|
// MiniMax's Messages endpoint defaults to off and only supports a
|
|
3973
4050
|
// switch. Claude budget_tokens/output_config.effort do not apply.
|
|
3974
4051
|
if (options?.thinkingEnabled !== undefined) {
|
|
@@ -3984,8 +4061,8 @@ function buildParams(
|
|
|
3984
4061
|
|
|
3985
4062
|
const compat = getAnthropicCompat(model);
|
|
3986
4063
|
if (mode === "anthropic-adaptive" && !compat.disableAdaptiveThinking) {
|
|
3987
|
-
//
|
|
3988
|
-
//
|
|
4064
|
+
// Opus 4.7+, Fable, and Haiku 5+ omit adaptive thinking content by
|
|
4065
|
+
// default. Opt into summarized reasoning so thinking deltas keep
|
|
3989
4066
|
// streaming with human-readable content for callers that rely on it.
|
|
3990
4067
|
const adaptive: { type: "adaptive"; display?: AnthropicThinkingDisplay } = { type: "adaptive" };
|
|
3991
4068
|
if (supportsAdaptiveThinkingDisplay(model.id)) {
|
|
@@ -4043,7 +4120,7 @@ function buildParams(
|
|
|
4043
4120
|
// that pair with `thinking`/`redacted_thinking` blocks "cannot be modified", so the
|
|
4044
4121
|
// replay has to degrade in the same rebuild. Runs before the billing/system payload
|
|
4045
4122
|
// snapshot so the attribution hash covers the messages actually sent.
|
|
4046
|
-
if (disableThinkingIfToolChoiceForced(params) && hasNativeThinkingBlocks(params.messages)) {
|
|
4123
|
+
if (disableThinkingIfToolChoiceForced(params, model.id) && hasNativeThinkingBlocks(params.messages)) {
|
|
4047
4124
|
params.messages = convertAnthropicMessages(context.messages, model, isOAuthToken, {
|
|
4048
4125
|
...thinkingRepair,
|
|
4049
4126
|
repairAllAssistantThinking: true,
|
|
@@ -42,7 +42,7 @@ export declare const ANTIGRAVITY_SYSTEM_INSTRUCTION = "You are Antigravity, a po
|
|
|
42
42
|
*
|
|
43
43
|
* Overridable via PI_AI_ANTIGRAVITY_USER_AGENT environment variable.
|
|
44
44
|
*/
|
|
45
|
-
export declare
|
|
45
|
+
export declare function getAntigravityUserAgent(): string;
|
|
46
46
|
export declare const getAntigravityRequestHeaders: () => {
|
|
47
47
|
"User-Agent": string;
|
|
48
48
|
};
|
|
@@ -93,12 +93,12 @@ This means you do **NOT** need to poll in a loop while waiting for messages or u
|
|
|
93
93
|
*
|
|
94
94
|
* Overridable via PI_AI_ANTIGRAVITY_USER_AGENT environment variable.
|
|
95
95
|
*/
|
|
96
|
-
export
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
96
|
+
export function getAntigravityUserAgent(): string {
|
|
97
|
+
return (
|
|
98
|
+
process.env.PI_AI_ANTIGRAVITY_USER_AGENT ||
|
|
99
|
+
"antigravity/cli/1.3.4 (os_type=darwin; arch=arm64; aidev_client; auth_method=oauth)"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
102
|
|
|
103
103
|
export const getAntigravityRequestHeaders = () => ({
|
|
104
104
|
"User-Agent": getAntigravityUserAgent(),
|
|
@@ -138,6 +138,34 @@ function compressRepeatedThinkingParagraphs(thinking: string): string {
|
|
|
138
138
|
return compressed ? compressedParts.join("") : thinking;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/** Escape untrusted text placed between the stale-tool-result tags. */
|
|
142
|
+
function escapeXmlText(input: string): string {
|
|
143
|
+
if (!/[&<>]/.test(input)) return input;
|
|
144
|
+
let output = "";
|
|
145
|
+
for (const char of input) {
|
|
146
|
+
if (char === "&") output += "&";
|
|
147
|
+
else if (char === "<") output += "<";
|
|
148
|
+
else if (char === ">") output += ">";
|
|
149
|
+
else output += char;
|
|
150
|
+
}
|
|
151
|
+
return output;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Escape an untrusted tool name or id used in a double-quoted attribute. */
|
|
155
|
+
function escapeXmlAttribute(input: string): string {
|
|
156
|
+
if (!/[&<>"']/.test(input)) return input;
|
|
157
|
+
let output = "";
|
|
158
|
+
for (const char of input) {
|
|
159
|
+
if (char === "&") output += "&";
|
|
160
|
+
else if (char === "<") output += "<";
|
|
161
|
+
else if (char === ">") output += ">";
|
|
162
|
+
else if (char === '"') output += """;
|
|
163
|
+
else if (char === "'") output += "'";
|
|
164
|
+
else output += char;
|
|
165
|
+
}
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
|
|
141
169
|
export function transformMessages<TApi extends Api>(
|
|
142
170
|
messages: Message[],
|
|
143
171
|
model: Model<TApi>,
|
|
@@ -432,9 +460,12 @@ export function transformMessages<TApi extends Api>(
|
|
|
432
460
|
}
|
|
433
461
|
if (textParts.length > 0) {
|
|
434
462
|
const errorAttr = msg.isError ? ' is-error="true"' : "";
|
|
463
|
+
const toolAttr = escapeXmlAttribute(msg.toolName);
|
|
464
|
+
const idAttr = escapeXmlAttribute(msg.toolCallId);
|
|
465
|
+
const body = escapeXmlText(textParts.join("\n"));
|
|
435
466
|
result.push({
|
|
436
467
|
role: "user",
|
|
437
|
-
content: `<stale-tool-result tool="${
|
|
468
|
+
content: `<stale-tool-result tool="${toolAttr}" id="${idAttr}"${errorAttr}>\n${body}\n</stale-tool-result>`,
|
|
438
469
|
timestamp: messageTimestamp,
|
|
439
470
|
} as UserMessage);
|
|
440
471
|
}
|
package/src/stream.ts
CHANGED
|
@@ -1005,7 +1005,7 @@ function resolveBedrockThinkingBudget(
|
|
|
1005
1005
|
model: Model<"bedrock-converse-stream">,
|
|
1006
1006
|
options?: SimpleStreamOptions,
|
|
1007
1007
|
): { budget: number; level: Effort } | null {
|
|
1008
|
-
if (!options?.reasoning || !model.reasoning) return null;
|
|
1008
|
+
if (!options?.reasoning || options.disableReasoning || !model.reasoning) return null;
|
|
1009
1009
|
const level = requireSupportedEffort(model, options.reasoning);
|
|
1010
1010
|
const budget = options.thinkingBudgets?.[level] ?? BEDROCK_CLAUDE_THINKING[level];
|
|
1011
1011
|
return { budget, level };
|
|
@@ -1133,7 +1133,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1133
1133
|
});
|
|
1134
1134
|
}
|
|
1135
1135
|
// Explicitly disable thinking when reasoning is not specified or model doesn't support it
|
|
1136
|
-
const reasoning = options?.reasoning;
|
|
1136
|
+
const reasoning = options?.disableReasoning ? undefined : options?.reasoning;
|
|
1137
1137
|
if (!reasoning || !model.reasoning) {
|
|
1138
1138
|
return castApi<"anthropic-messages">({
|
|
1139
1139
|
...base,
|
|
@@ -1144,7 +1144,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1144
1144
|
});
|
|
1145
1145
|
}
|
|
1146
1146
|
|
|
1147
|
-
let thinkingBudget = options
|
|
1147
|
+
let thinkingBudget = options?.thinkingBudgets?.[reasoning] ?? ANTHROPIC_THINKING[reasoning];
|
|
1148
1148
|
if (thinkingBudget <= 0) {
|
|
1149
1149
|
return castApi<"anthropic-messages">({
|
|
1150
1150
|
...base,
|
|
@@ -1238,7 +1238,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1238
1238
|
case "bedrock-converse-stream": {
|
|
1239
1239
|
const bedrockBase: BedrockOptions = {
|
|
1240
1240
|
...base,
|
|
1241
|
-
reasoning: options?.reasoning,
|
|
1241
|
+
reasoning: options?.disableReasoning ? undefined : options?.reasoning,
|
|
1242
1242
|
thinkingBudgets: options?.thinkingBudgets,
|
|
1243
1243
|
toolChoice: mapAnthropicToolChoice(options?.toolChoice),
|
|
1244
1244
|
thinkingDisplay: options?.hideThinkingSummary ? "omitted" : undefined,
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
|
6
7
|
/**
|
|
@@ -171,7 +172,7 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
171
172
|
token: string;
|
|
172
173
|
/** Optional endpoint override. Defaults to Antigravity fallback endpoints. */
|
|
173
174
|
endpoint?: string;
|
|
174
|
-
/**
|
|
175
|
+
/** Optional Google Cloud project ID for tier-gated model discovery. */
|
|
175
176
|
project?: string;
|
|
176
177
|
/** Optional user agent override. */
|
|
177
178
|
userAgent?: string;
|
|
@@ -185,6 +186,8 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
185
186
|
* Default: `google-antigravity`.
|
|
186
187
|
*/
|
|
187
188
|
targetProvider?: "google-antigravity" | "google-gemini-cli";
|
|
189
|
+
/** Optional logger for debug diagnostics. */
|
|
190
|
+
logger?: Pick<typeof logger, "debug">;
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
/**
|
|
@@ -199,14 +202,20 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
199
202
|
const fetcher = options.fetcher ?? fetch;
|
|
200
203
|
const signal = options.signal ?? AbortSignal.timeout(5_000);
|
|
201
204
|
const targetProvider = options.targetProvider ?? "google-antigravity";
|
|
202
|
-
const userAgent = options.userAgent ??
|
|
205
|
+
const userAgent = options.userAgent ?? getAntigravityDiscoveryUserAgent();
|
|
203
206
|
const endpoints = options.endpoint
|
|
204
207
|
? [trimTrailingSlashes(options.endpoint)]
|
|
205
208
|
: DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS.map(trimTrailingSlashes);
|
|
209
|
+
const debugLogger = options.logger ?? logger;
|
|
206
210
|
|
|
207
211
|
for (const endpoint of endpoints) {
|
|
212
|
+
const redactedEndpoint = redactAntigravityEndpoint(endpoint);
|
|
208
213
|
let response: Response;
|
|
209
214
|
try {
|
|
215
|
+
const requestBody: Record<string, unknown> = {};
|
|
216
|
+
if (options.project) {
|
|
217
|
+
requestBody.project = options.project;
|
|
218
|
+
}
|
|
210
219
|
response = await fetcher(`${endpoint}${FETCH_AVAILABLE_MODELS_PATH}`, {
|
|
211
220
|
method: "POST",
|
|
212
221
|
headers: {
|
|
@@ -214,13 +223,19 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
214
223
|
"Content-Type": "application/json",
|
|
215
224
|
"User-Agent": userAgent,
|
|
216
225
|
},
|
|
217
|
-
body: JSON.stringify(
|
|
226
|
+
body: JSON.stringify(requestBody),
|
|
218
227
|
signal,
|
|
219
228
|
});
|
|
220
229
|
} catch {
|
|
221
230
|
continue;
|
|
222
231
|
}
|
|
223
232
|
|
|
233
|
+
debugLogger.debug("Antigravity discovery endpoint response", {
|
|
234
|
+
endpoint: redactedEndpoint,
|
|
235
|
+
status: response.status,
|
|
236
|
+
statusText: response.statusText,
|
|
237
|
+
});
|
|
238
|
+
|
|
224
239
|
if (!response.ok) {
|
|
225
240
|
continue;
|
|
226
241
|
}
|
|
@@ -256,33 +271,37 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
256
271
|
const filteredModelDetails: Record<string, Partial<AntigravityDiscoveryApiModel> & { reason: string }> = {};
|
|
257
272
|
|
|
258
273
|
const models: Model<"google-gemini-cli">[] = [];
|
|
274
|
+
const modelDropReasons: Record<string, string> = {};
|
|
259
275
|
|
|
260
276
|
for (const [modelId, model] of Object.entries(parsed.models ?? {})) {
|
|
277
|
+
let dropReason: string | undefined;
|
|
278
|
+
|
|
261
279
|
if (ANTIGRAVITY_DISCOVERY_DENYLIST.has(modelId)) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (debugEnabled) {
|
|
270
|
-
filteredByRetirement.push(modelId);
|
|
271
|
-
filteredModelDetails[modelId] = { ...model, reason: `retired-${targetProvider}` };
|
|
272
|
-
}
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if (model.disabled === true) {
|
|
276
|
-
if (debugEnabled) {
|
|
277
|
-
filteredByDisabled.push(modelId);
|
|
278
|
-
filteredModelDetails[modelId] = { ...model, reason: "disabled" };
|
|
279
|
-
}
|
|
280
|
-
continue;
|
|
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";
|
|
281
287
|
}
|
|
282
|
-
|
|
288
|
+
|
|
289
|
+
if (dropReason) {
|
|
290
|
+
modelDropReasons[modelId] = dropReason;
|
|
283
291
|
if (debugEnabled) {
|
|
284
|
-
|
|
285
|
-
|
|
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
|
+
};
|
|
286
305
|
}
|
|
287
306
|
continue;
|
|
288
307
|
}
|
|
@@ -317,6 +336,18 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
317
336
|
});
|
|
318
337
|
}
|
|
319
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
|
+
|
|
320
351
|
models.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
321
352
|
|
|
322
353
|
if (debugEnabled) {
|
|
@@ -425,3 +456,15 @@ function toPositiveNumber(value: unknown, fallback: number): number {
|
|
|
425
456
|
function trimTrailingSlashes(value: string): string {
|
|
426
457
|
return value.replace(/\/+$/, "");
|
|
427
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
|
+
}
|
|
@@ -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
|
}
|