@oh-my-pi/pi-catalog 16.0.4 → 16.0.5
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 +24 -0
- package/dist/types/compat/openai.d.ts +1 -0
- package/dist/types/discovery/antigravity.d.ts +9 -0
- package/dist/types/identity/dialect.d.ts +1 -1
- package/dist/types/identity/family.d.ts +2 -0
- package/dist/types/types.d.ts +20 -1
- package/dist/types/variant-collapse.d.ts +4 -5
- package/dist/types/wire/gemini-headers.d.ts +16 -1
- package/dist/types/wire/github-copilot.d.ts +2 -0
- package/package.json +3 -3
- package/src/compat/openai.ts +7 -0
- package/src/discovery/antigravity.ts +15 -6
- package/src/identity/dialect.ts +4 -1
- package/src/identity/family.ts +8 -1
- package/src/model-cache.ts +8 -6
- package/src/models.json +40 -16
- package/src/provider-models/google.ts +2 -0
- package/src/provider-models/openai-compat.ts +7 -4
- package/src/types.ts +20 -0
- package/src/variant-collapse.ts +198 -72
- package/src/wire/gemini-headers.ts +28 -5
- package/src/wire/github-copilot.ts +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.0.5] - 2026-06-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `enableGeminiThinkingLoopGuard` to OpenAI compatibility options to allow explicit opt-in or opt-out of the Gemini thinking-loop guard for OpenAI-compatible model aliases
|
|
10
|
+
- Added `LITELLM_BASE_URL` as the LiteLLM provider discovery base URL fallback, with discovery caches scoped by the resolved proxy URL and explicit provider `baseUrl` config kept at higher precedence. ([#2726](https://github.com/can1357/oh-my-pi/issues/2726))
|
|
11
|
+
- Added `ThinkingConfig.effortBudgets` (per-effort thinking-budget contract baked into collapsed variants) and `ANTIGRAVITY_MODEL_WIRE_PROFILES` (`maxOutputTokens` + `model_enum` per Antigravity wire id) to mirror the captured Antigravity Cloud Code Assist client request shape.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Defaulted `enableGeminiThinkingLoopGuard` from Gemini family detection for both OpenAI completions and responses compatibility specs so Gemini models now enable the thinking-loop guard automatically
|
|
16
|
+
- Updated the default Gemini CLI user-agent version fallback to 0.46.0.
|
|
17
|
+
- Changed the Antigravity (`google-antigravity`, daily-cloudcode-pa) gemini-3.x collapse families to the `budget` thinking transport with the client's per-tier `thinkingBudget` (3.5 Flash low/medium/high = 1000/4000/10000, 3.1 Pro low/high = 1001/10001) and corrected 3.5 Flash effort→wire routing (medium → `gemini-3.5-flash-low`, high → `gemini-3-flash-agent`). Split the shared CCA collapse table so `google-gemini-cli` (cloudcode-pa) keeps the `google-level` `thinkingLevel` transport for official Gemini CLI parity. Stale collapsed snapshots (bundled catalog, recycled `gemini-3-flash` alias) self-heal from the hand table at collapse time, and the model cache schema is bumped to v7 to invalidate pre-budget Antigravity rows.
|
|
18
|
+
- Changed the Antigravity user-agent to the `antigravity/hub/<version>` format (default `2.1.4`) to match the captured client.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed `off` effort routing for `claude-opus-4-5` and `claude-opus-4-6` to use their base model IDs when thinking is disabled
|
|
23
|
+
- Fixed `gemini-2.5-flash` effort routing so all non-off effort levels resolve to `gemini-2.5-flash-thinking`
|
|
24
|
+
- Fixed shared variant alias provider resolution so `resolveBareVariantAlias` reports all matching providers when model aliases are present in both CCA collapse tables
|
|
25
|
+
- Routed google-antigravity default baseUrl to the stable primary daily endpoint in the catalog generator and all fallback snapshots, resolving connection drops on heavy queries.
|
|
26
|
+
- Fixed MiniMax M3 dialect selection so MiniMax-family OpenAI-compatible models use the MiniMax tool-call dialect instead of generic XML. ([#2759](https://github.com/can1357/oh-my-pi/issues/2759))
|
|
27
|
+
- Fixed GitHub Copilot dynamic discovery to honor plan-specific API endpoints stored in structured OAuth credentials. ([#2876](https://github.com/can1357/oh-my-pi/issues/2876))
|
|
28
|
+
|
|
5
29
|
## [16.0.4] - 2026-06-17
|
|
6
30
|
|
|
7
31
|
### Fixed
|
|
@@ -5,6 +5,7 @@ import type { ModelSpec, OpenAICompat, ResolvedOpenAICompat, ResolvedOpenAIRespo
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function buildOpenAICompat(spec: ModelSpec<"openai-completions">): ResolvedOpenAICompat;
|
|
7
7
|
interface OpenAIResponsesSpecLike {
|
|
8
|
+
id?: string;
|
|
8
9
|
provider: string;
|
|
9
10
|
name: string;
|
|
10
11
|
baseUrl: string;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ModelSpec } from "../types";
|
|
2
|
+
import { type VariantCollapseTable } from "../variant-collapse";
|
|
3
|
+
export declare const ANTIGRAVITY_PRIMARY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
4
|
+
export declare const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
|
2
5
|
/**
|
|
3
6
|
* Raw model metadata returned by Antigravity's `fetchAvailableModels` endpoint.
|
|
4
7
|
*/
|
|
@@ -51,6 +54,12 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
51
54
|
signal?: AbortSignal;
|
|
52
55
|
/** Optional fetch implementation override for tests. */
|
|
53
56
|
fetcher?: typeof fetch;
|
|
57
|
+
/**
|
|
58
|
+
* Hand collapse table to apply to the discovered list. Defaults to the
|
|
59
|
+
* Antigravity (budget-transport) table; `googleGeminiCli` passes the
|
|
60
|
+
* level-transport table so cloudcode-pa keeps `thinkingLevel`.
|
|
61
|
+
*/
|
|
62
|
+
collapseTable?: VariantCollapseTable;
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
65
|
* Fetches discoverable Antigravity models and normalizes them into canonical model entries.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type Dialect = "glm" | "hermes" | "kimi" | "xml" | "anthropic" | "deepseek" | "harmony" | "pi" | "qwen3" | "gemini" | "gemma";
|
|
1
|
+
export type Dialect = "glm" | "hermes" | "kimi" | "xml" | "anthropic" | "deepseek" | "harmony" | "pi" | "qwen3" | "gemini" | "gemma" | "minimax";
|
|
2
2
|
export declare const FALLBACK_DIALECT: Dialect;
|
|
3
3
|
export declare function preferredDialect(modelId: string): Dialect;
|
|
@@ -32,6 +32,8 @@ export declare function isMimoModelIdOrName(value: string): boolean;
|
|
|
32
32
|
* clamp instead. Excludes M1, M3, MiniMax-Text-01, music, hailuo, voice ids.
|
|
33
33
|
*/
|
|
34
34
|
export declare function isMinimaxM2FamilyModelId(modelId: string): boolean;
|
|
35
|
+
/** MiniMax M3 family ids in bundled/default and aggregator namespace forms. */
|
|
36
|
+
export declare function isMinimaxM3FamilyModelId(modelId: string): boolean;
|
|
35
37
|
/**
|
|
36
38
|
* OpenAI gpt-oss family (`gpt-oss-20b`, `gpt-oss-120b`, `gpt-oss:120b`,
|
|
37
39
|
* `vendor/gpt-oss-…`). The Harmony reasoning format only accepts
|
package/dist/types/types.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export interface ThinkingConfig {
|
|
|
33
33
|
* thinking is disabled. Missing keys fall back to `requestModelId ?? id`.
|
|
34
34
|
*/
|
|
35
35
|
effortRouting?: Readonly<Partial<Record<Effort | "off", string>>>;
|
|
36
|
+
/**
|
|
37
|
+
* Per-effort thinking budget in tokens, baked at build time for collapsed
|
|
38
|
+
* variants whose upstream expects an explicit `thinkingBudget` instead of a
|
|
39
|
+
* value derived from the generic ladder (Antigravity Cloud Code Assist
|
|
40
|
+
* gemini-3.x). Request mapping prefers caller `thinkingBudgets`, then this
|
|
41
|
+
* map, then the provider default ladder. Only meaningful for `mode: "budget"`.
|
|
42
|
+
*/
|
|
43
|
+
effortBudgets?: Readonly<Partial<Record<Effort, number>>>;
|
|
36
44
|
/**
|
|
37
45
|
* When true, a thinking-off request MUST explicitly suppress thinking on
|
|
38
46
|
* the wire (google-level: `thinkingLevel: "MINIMAL"` + `includeThoughts:
|
|
@@ -137,6 +145,13 @@ export interface OpenAICompat {
|
|
|
137
145
|
reasoningEffortMap?: Partial<Record<Effort, string>>;
|
|
138
146
|
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
|
|
139
147
|
supportsUsageInStreaming?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Enable the Gemini thinking-loop guard (pi-ai stream layer) for this model.
|
|
150
|
+
* Defaults to true when the model id classifies as the gemini family. Set
|
|
151
|
+
* explicitly to cover an opaque OpenAI-compat proxy alias (e.g. `my-model`)
|
|
152
|
+
* that routes to Gemini, or to false to opt a gemini-family id out.
|
|
153
|
+
*/
|
|
154
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
140
155
|
/** Which field to use for max tokens. Default: auto-detected from URL. */
|
|
141
156
|
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
|
142
157
|
/** Whether tool results require the `name` field. Default: auto-detected from URL. */
|
|
@@ -322,7 +337,7 @@ type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mix
|
|
|
322
337
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
323
338
|
* allocate.
|
|
324
339
|
*/
|
|
325
|
-
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "streamIdleTimeoutMs" | "supportsLongPromptCacheRetention" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "requiresJuiceZeroHack" | "whenThinking">> & {
|
|
340
|
+
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "streamIdleTimeoutMs" | "supportsLongPromptCacheRetention" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "requiresJuiceZeroHack" | "enableGeminiThinkingLoopGuard" | "whenThinking">> & {
|
|
326
341
|
openRouterRouting?: OpenAICompat["openRouterRouting"];
|
|
327
342
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
328
343
|
extraBody?: OpenAICompat["extraBody"];
|
|
@@ -334,6 +349,8 @@ export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRoutin
|
|
|
334
349
|
isOpenRouterHost: boolean;
|
|
335
350
|
/** The model sits behind Vercel AI Gateway. */
|
|
336
351
|
isVercelGatewayHost: boolean;
|
|
352
|
+
/** See {@link OpenAICompat.enableGeminiThinkingLoopGuard}. Set by the builder from the family classifier. */
|
|
353
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
337
354
|
/** Complete alternate view for thinking-engaged requests; swap pointers, never spread. */
|
|
338
355
|
whenThinking?: ResolvedOpenAICompat;
|
|
339
356
|
};
|
|
@@ -346,6 +363,8 @@ export interface ResolvedOpenAIResponsesCompat {
|
|
|
346
363
|
strictResponsesPairing: boolean;
|
|
347
364
|
requiresJuiceZeroHack: boolean;
|
|
348
365
|
reasoningEffortMap: Partial<Record<Effort, string>>;
|
|
366
|
+
/** See {@link OpenAICompat.enableGeminiThinkingLoopGuard}. */
|
|
367
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
349
368
|
}
|
|
350
369
|
/** Fully-resolved anthropic-messages compat view (same contract as `ResolvedOpenAICompat`). */
|
|
351
370
|
export type ResolvedAnthropicCompat = Required<AnthropicCompat> & {
|
|
@@ -46,12 +46,11 @@ export interface EffortVariantFamily {
|
|
|
46
46
|
export interface VariantCollapseTable {
|
|
47
47
|
families: readonly EffortVariantFamily[];
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Shared by `google-antigravity` and `google-gemini-cli` — both serve the
|
|
51
|
-
* Antigravity discovery list (`fetchAntigravityDiscoveryModels`).
|
|
52
|
-
*/
|
|
49
|
+
/** `google-antigravity` (daily-cloudcode-pa): Gemini 3.x on the budget transport. */
|
|
53
50
|
export declare const ANTIGRAVITY_VARIANT_COLLAPSE_TABLE: VariantCollapseTable;
|
|
54
|
-
/**
|
|
51
|
+
/** `google-gemini-cli` (cloudcode-pa): Gemini 3.x on the level transport (official CLI parity). */
|
|
52
|
+
export declare const GEMINI_CLI_VARIANT_COLLAPSE_TABLE: VariantCollapseTable;
|
|
53
|
+
/** Provider id → hand collapse table. The CCA providers diverge on thinking transport. */
|
|
55
54
|
export declare const VARIANT_COLLAPSE_TABLES: Readonly<Record<string, VariantCollapseTable>>;
|
|
56
55
|
/**
|
|
57
56
|
* The global automatic rule: derive an `X` + `X-thinking` family for every
|
|
@@ -9,7 +9,6 @@ export declare const getGeminiCliHeaders: (modelId?: string) => {
|
|
|
9
9
|
"Client-Metadata": string;
|
|
10
10
|
};
|
|
11
11
|
export declare const ANTIGRAVITY_SYSTEM_INSTRUCTION: string;
|
|
12
|
-
export declare const ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION = "CRITICAL: NEVER output rule checks, formatting guidelines, constraint checklists (e.g. \"No emdashes\"), or your thinking/personality preambles in the final response. Output only the final response.";
|
|
13
12
|
/**
|
|
14
13
|
* Antigravity / Cloud Code Assist user agent. Lives in its own file so discovery
|
|
15
14
|
* and usage code can read it without pulling the heavy google-gemini-cli provider
|
|
@@ -17,3 +16,19 @@ export declare const ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION = "CRITICAL: NEVER outp
|
|
|
17
16
|
* parse graph.
|
|
18
17
|
*/
|
|
19
18
|
export declare let getAntigravityUserAgent: () => string;
|
|
19
|
+
/**
|
|
20
|
+
* Per-wire-id Antigravity Cloud Code Assist request constants, captured from the
|
|
21
|
+
* real `antigravity/hub` client against `daily-cloudcode-pa`. `modelEnum` is the
|
|
22
|
+
* opaque `labels.model_enum` token the client tags each request with;
|
|
23
|
+
* `maxOutputTokens` is the fixed `generationConfig.maxOutputTokens` it sends
|
|
24
|
+
* regardless of the thinking budget. Keyed by the routed upstream wire id
|
|
25
|
+
* (post effort-routing), not the collapsed logical id. Checkpoint-only ids
|
|
26
|
+
* (e.g. `gemini-3.1-flash-lite`) are intentionally absent — this provider only
|
|
27
|
+
* emits agent requests.
|
|
28
|
+
*/
|
|
29
|
+
export interface AntigravityModelWireProfile {
|
|
30
|
+
modelEnum: string;
|
|
31
|
+
maxOutputTokens: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const ANTIGRAVITY_MODEL_WIRE_PROFILES: Readonly<Record<string, AntigravityModelWireProfile>>;
|
|
34
|
+
export declare function getAntigravityModelWireProfile(wireModelId: string): AntigravityModelWireProfile | undefined;
|
|
@@ -25,9 +25,11 @@ export declare const COPILOT_API_HEADERS: {
|
|
|
25
25
|
export type ParsedGitHubCopilotApiKey = {
|
|
26
26
|
accessToken: string;
|
|
27
27
|
enterpriseUrl?: string;
|
|
28
|
+
apiEndpoint?: string;
|
|
28
29
|
};
|
|
29
30
|
export declare function isPublicGitHubHost(host: string): boolean;
|
|
30
31
|
export declare function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined): string | undefined;
|
|
32
|
+
export declare function normalizeGitHubCopilotApiEndpoint(input: string | undefined): string | undefined;
|
|
31
33
|
export declare function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilotApiKey;
|
|
32
34
|
export declare function normalizeDomain(input: string): string | null;
|
|
33
35
|
export declare function getGitHubCopilotBaseUrl(enterpriseDomain?: string): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "16.0.
|
|
4
|
+
"version": "16.0.5",
|
|
5
5
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@bufbuild/protobuf": "^2.12.0",
|
|
37
|
-
"@oh-my-pi/pi-utils": "16.0.
|
|
37
|
+
"@oh-my-pi/pi-utils": "16.0.5",
|
|
38
38
|
"zod": "^4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@oh-my-pi/pi-ai": "16.0.
|
|
41
|
+
"@oh-my-pi/pi-ai": "16.0.5",
|
|
42
42
|
"@types/bun": "^1.3.14"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
package/src/compat/openai.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isKimiModelId,
|
|
18
18
|
isMimoModelIdOrName,
|
|
19
19
|
isQwenModelId,
|
|
20
|
+
modelFamilyToken,
|
|
20
21
|
} from "../identity/family";
|
|
21
22
|
import type { ModelSpec, OpenAICompat, ResolvedOpenAICompat, ResolvedOpenAIResponsesCompat } from "../types";
|
|
22
23
|
import { applyCompatOverrides } from "./apply";
|
|
@@ -211,6 +212,10 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
211
212
|
supportsReasoningParams: provider !== "github-copilot",
|
|
212
213
|
reasoningEffortMap: {},
|
|
213
214
|
supportsUsageInStreaming: !isCerebras,
|
|
215
|
+
// pi-ai's thinking-loop guard is gemini-only; default the flag from the
|
|
216
|
+
// family classifier so OpenAI-compat proxies serving Gemini are covered.
|
|
217
|
+
// An opaque alias can opt in via `compat.enableGeminiThinkingLoopGuard`.
|
|
218
|
+
enableGeminiThinkingLoopGuard: modelFamilyToken(spec.id) === "gemini",
|
|
214
219
|
// Kimi (including via OpenRouter and Fireworks router-form IDs such as
|
|
215
220
|
// `accounts/fireworks/routers/kimi-*`) calculates TPM rate limits based on
|
|
216
221
|
// max_tokens, not actual output. The official Kimi K2 model guidance
|
|
@@ -295,6 +300,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
295
300
|
}
|
|
296
301
|
|
|
297
302
|
interface OpenAIResponsesSpecLike {
|
|
303
|
+
id?: string;
|
|
298
304
|
provider: string;
|
|
299
305
|
name: string;
|
|
300
306
|
baseUrl: string;
|
|
@@ -329,6 +335,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
329
335
|
strictResponsesPairing: isAzure || spec.provider === "github-copilot",
|
|
330
336
|
requiresJuiceZeroHack: spec.name.toLowerCase().startsWith("gpt-5"),
|
|
331
337
|
reasoningEffortMap: {},
|
|
338
|
+
enableGeminiThinkingLoopGuard: modelFamilyToken(spec.id ?? "") === "gemini",
|
|
332
339
|
};
|
|
333
340
|
applyCompatOverrides(compat, spec.compat);
|
|
334
341
|
return compat;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
import type { ModelSpec } from "../types";
|
|
3
3
|
import { toPositiveNumber } from "../utils";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ANTIGRAVITY_VARIANT_COLLAPSE_TABLE,
|
|
6
|
+
collapseEffortVariants,
|
|
7
|
+
type VariantCollapseTable,
|
|
8
|
+
} from "../variant-collapse";
|
|
5
9
|
import { getAntigravityUserAgent } from "../wire/gemini-headers";
|
|
6
10
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
] as const;
|
|
11
|
+
export const ANTIGRAVITY_PRIMARY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
12
|
+
export const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
|
13
|
+
const DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS = [ANTIGRAVITY_PRIMARY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
|
|
11
14
|
const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels";
|
|
12
15
|
|
|
13
16
|
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
@@ -157,6 +160,12 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
157
160
|
signal?: AbortSignal;
|
|
158
161
|
/** Optional fetch implementation override for tests. */
|
|
159
162
|
fetcher?: typeof fetch;
|
|
163
|
+
/**
|
|
164
|
+
* Hand collapse table to apply to the discovered list. Defaults to the
|
|
165
|
+
* Antigravity (budget-transport) table; `googleGeminiCli` passes the
|
|
166
|
+
* level-transport table so cloudcode-pa keeps `thinkingLevel`.
|
|
167
|
+
*/
|
|
168
|
+
collapseTable?: VariantCollapseTable;
|
|
160
169
|
}
|
|
161
170
|
|
|
162
171
|
/**
|
|
@@ -239,7 +248,7 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
239
248
|
// Collapse effort-tier variants at the source so runtime discovery,
|
|
240
249
|
// the gemini-cli re-provision, and the catalog generator all see
|
|
241
250
|
// logical ids only.
|
|
242
|
-
const collapsed = collapseEffortVariants(models, ANTIGRAVITY_VARIANT_COLLAPSE_TABLE);
|
|
251
|
+
const collapsed = collapseEffortVariants(models, options.collapseTable ?? ANTIGRAVITY_VARIANT_COLLAPSE_TABLE);
|
|
243
252
|
collapsed.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
244
253
|
return collapsed;
|
|
245
254
|
}
|
package/src/identity/dialect.ts
CHANGED
|
@@ -11,7 +11,8 @@ export type Dialect =
|
|
|
11
11
|
| "pi"
|
|
12
12
|
| "qwen3"
|
|
13
13
|
| "gemini"
|
|
14
|
-
| "gemma"
|
|
14
|
+
| "gemma"
|
|
15
|
+
| "minimax";
|
|
15
16
|
|
|
16
17
|
export const FALLBACK_DIALECT: Dialect = "xml";
|
|
17
18
|
|
|
@@ -31,6 +32,8 @@ export function preferredDialect(modelId: string): Dialect {
|
|
|
31
32
|
return "qwen3";
|
|
32
33
|
case "deepseek":
|
|
33
34
|
return "deepseek";
|
|
35
|
+
case "minimax":
|
|
36
|
+
return "minimax";
|
|
34
37
|
case "openai":
|
|
35
38
|
case "gpt-oss":
|
|
36
39
|
return "harmony";
|
package/src/identity/family.ts
CHANGED
|
@@ -73,6 +73,13 @@ export function isMinimaxM2FamilyModelId(modelId: string): boolean {
|
|
|
73
73
|
return /(?:^|[/.-])m2\d*(?:[.-]\d+)?(?:[-.:_]|$)/i.test(lower);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** MiniMax M3 family ids in bundled/default and aggregator namespace forms. */
|
|
77
|
+
export function isMinimaxM3FamilyModelId(modelId: string): boolean {
|
|
78
|
+
const lower = modelId.toLowerCase();
|
|
79
|
+
if (!lower.includes("minimax")) return false;
|
|
80
|
+
return /(?:^|[/._-])(?:minimax[/._-])?m3(?:[-.:_]|$)/i.test(lower);
|
|
81
|
+
}
|
|
82
|
+
|
|
76
83
|
/**
|
|
77
84
|
* OpenAI gpt-oss family (`gpt-oss-20b`, `gpt-oss-120b`, `gpt-oss:120b`,
|
|
78
85
|
* `vendor/gpt-oss-…`). The Harmony reasoning format only accepts
|
|
@@ -139,7 +146,7 @@ export function modelFamilyToken(modelId: string): string {
|
|
|
139
146
|
if (isOpenAIModelId(modelId)) return "openai";
|
|
140
147
|
if (isKimiModelId(modelId)) return "kimi";
|
|
141
148
|
if (isQwenModelId(modelId)) return "qwen";
|
|
142
|
-
if (isMinimaxM2FamilyModelId(modelId)) return "minimax";
|
|
149
|
+
if (isMinimaxM2FamilyModelId(modelId) || isMinimaxM3FamilyModelId(modelId)) return "minimax";
|
|
143
150
|
if (isOpenAIGptOssModelId(modelId)) return "gpt-oss";
|
|
144
151
|
if (isDeepseekModelIdOrName(modelId)) return "deepseek";
|
|
145
152
|
if (isMimoModelIdOrName(modelId)) return "mimo";
|
package/src/model-cache.ts
CHANGED
|
@@ -7,12 +7,14 @@ import { getModelDbPath } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
import type { Api, Model, ModelSpec } from "./types";
|
|
8
8
|
|
|
9
9
|
// Rows persist ModelSpec JSON (sparse `compat`, never the resolved record);
|
|
10
|
-
// the model manager rebuilds via `buildModel` on load.
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
10
|
+
// the model manager rebuilds via `buildModel` on load. v7 invalidates rows
|
|
11
|
+
// predating the Antigravity Gemini budget-mode migration (cached specs still
|
|
12
|
+
// carrying `thinking.mode: "google-level"` and the old 3.5-flash effort
|
|
13
|
+
// routing); v6 invalidates rows that may contain the retired unknown-limit
|
|
14
|
+
// sentinels (222222/8888); v5 invalidated rows predating effort-tier variant
|
|
15
|
+
// collapsing (raw `-low`/`-high`/`-thinking` member ids); v4 dropped the
|
|
16
|
+
// pre-efforts ThinkingConfig shape.
|
|
17
|
+
const CACHE_SCHEMA_VERSION = 7;
|
|
16
18
|
|
|
17
19
|
interface CacheRow {
|
|
18
20
|
provider_id: string;
|
package/src/models.json
CHANGED
|
@@ -17726,7 +17726,7 @@
|
|
|
17726
17726
|
"name": "Claude Opus 4.5",
|
|
17727
17727
|
"api": "google-gemini-cli",
|
|
17728
17728
|
"provider": "google-antigravity",
|
|
17729
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17729
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17730
17730
|
"reasoning": true,
|
|
17731
17731
|
"input": [
|
|
17732
17732
|
"text",
|
|
@@ -17749,6 +17749,7 @@
|
|
|
17749
17749
|
"high"
|
|
17750
17750
|
],
|
|
17751
17751
|
"effortRouting": {
|
|
17752
|
+
"off": "claude-opus-4-5",
|
|
17752
17753
|
"minimal": "claude-opus-4-5-thinking",
|
|
17753
17754
|
"low": "claude-opus-4-5-thinking",
|
|
17754
17755
|
"medium": "claude-opus-4-5-thinking",
|
|
@@ -17762,7 +17763,7 @@
|
|
|
17762
17763
|
"name": "Claude Opus 4.6",
|
|
17763
17764
|
"api": "google-gemini-cli",
|
|
17764
17765
|
"provider": "google-antigravity",
|
|
17765
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17766
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17766
17767
|
"reasoning": true,
|
|
17767
17768
|
"input": [
|
|
17768
17769
|
"text",
|
|
@@ -17785,6 +17786,7 @@
|
|
|
17785
17786
|
"high"
|
|
17786
17787
|
],
|
|
17787
17788
|
"effortRouting": {
|
|
17789
|
+
"off": "claude-opus-4-6",
|
|
17788
17790
|
"minimal": "claude-opus-4-6-thinking",
|
|
17789
17791
|
"low": "claude-opus-4-6-thinking",
|
|
17790
17792
|
"medium": "claude-opus-4-6-thinking",
|
|
@@ -17798,7 +17800,7 @@
|
|
|
17798
17800
|
"name": "Claude Sonnet 4.5",
|
|
17799
17801
|
"api": "google-gemini-cli",
|
|
17800
17802
|
"provider": "google-antigravity",
|
|
17801
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17803
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17802
17804
|
"reasoning": true,
|
|
17803
17805
|
"input": [
|
|
17804
17806
|
"text",
|
|
@@ -17835,7 +17837,7 @@
|
|
|
17835
17837
|
"name": "Claude Sonnet 4.6",
|
|
17836
17838
|
"api": "google-gemini-cli",
|
|
17837
17839
|
"provider": "google-antigravity",
|
|
17838
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17840
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17839
17841
|
"reasoning": true,
|
|
17840
17842
|
"input": [
|
|
17841
17843
|
"text",
|
|
@@ -17872,7 +17874,7 @@
|
|
|
17872
17874
|
"name": "Gemini 2.5 Flash",
|
|
17873
17875
|
"api": "google-gemini-cli",
|
|
17874
17876
|
"provider": "google-antigravity",
|
|
17875
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17877
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17876
17878
|
"reasoning": true,
|
|
17877
17879
|
"input": [
|
|
17878
17880
|
"text",
|
|
@@ -17908,7 +17910,7 @@
|
|
|
17908
17910
|
"name": "Gemini 2.5 Pro",
|
|
17909
17911
|
"api": "google-gemini-cli",
|
|
17910
17912
|
"provider": "google-antigravity",
|
|
17911
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17913
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17912
17914
|
"reasoning": true,
|
|
17913
17915
|
"input": [
|
|
17914
17916
|
"text",
|
|
@@ -17938,7 +17940,7 @@
|
|
|
17938
17940
|
"name": "Gemini 3 Flash",
|
|
17939
17941
|
"api": "google-gemini-cli",
|
|
17940
17942
|
"provider": "google-antigravity",
|
|
17941
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17943
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17942
17944
|
"reasoning": true,
|
|
17943
17945
|
"input": [
|
|
17944
17946
|
"text",
|
|
@@ -17953,22 +17955,36 @@
|
|
|
17953
17955
|
"contextWindow": 1048576,
|
|
17954
17956
|
"maxTokens": 65536,
|
|
17955
17957
|
"thinking": {
|
|
17956
|
-
"mode": "
|
|
17958
|
+
"mode": "budget",
|
|
17957
17959
|
"efforts": [
|
|
17958
17960
|
"minimal",
|
|
17959
17961
|
"low",
|
|
17960
17962
|
"medium",
|
|
17961
17963
|
"high"
|
|
17962
17964
|
],
|
|
17963
|
-
"
|
|
17964
|
-
|
|
17965
|
+
"effortBudgets": {
|
|
17966
|
+
"minimal": 1000,
|
|
17967
|
+
"low": 1000,
|
|
17968
|
+
"medium": 4000,
|
|
17969
|
+
"high": 10000
|
|
17970
|
+
},
|
|
17971
|
+
"effortRouting": {
|
|
17972
|
+
"off": "gemini-3.5-flash-extra-low",
|
|
17973
|
+
"minimal": "gemini-3.5-flash-extra-low",
|
|
17974
|
+
"low": "gemini-3.5-flash-extra-low",
|
|
17975
|
+
"medium": "gemini-3.5-flash-low",
|
|
17976
|
+
"high": "gemini-3-flash-agent"
|
|
17977
|
+
},
|
|
17978
|
+
"suppressWhenOff": true
|
|
17979
|
+
},
|
|
17980
|
+
"requestModelId": "gemini-3.5-flash-extra-low"
|
|
17965
17981
|
},
|
|
17966
17982
|
"gemini-3-pro": {
|
|
17967
17983
|
"id": "gemini-3-pro",
|
|
17968
17984
|
"name": "Gemini 3 Pro",
|
|
17969
17985
|
"api": "google-gemini-cli",
|
|
17970
17986
|
"provider": "google-antigravity",
|
|
17971
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
17987
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
17972
17988
|
"reasoning": true,
|
|
17973
17989
|
"input": [
|
|
17974
17990
|
"text",
|
|
@@ -18002,7 +18018,7 @@
|
|
|
18002
18018
|
"name": "Gemini 3.1 Pro Preview",
|
|
18003
18019
|
"api": "google-gemini-cli",
|
|
18004
18020
|
"provider": "google-antigravity",
|
|
18005
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
18021
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
18006
18022
|
"reasoning": true,
|
|
18007
18023
|
"input": [
|
|
18008
18024
|
"text",
|
|
@@ -18017,11 +18033,15 @@
|
|
|
18017
18033
|
"contextWindow": 1048576,
|
|
18018
18034
|
"maxTokens": 65535,
|
|
18019
18035
|
"thinking": {
|
|
18020
|
-
"mode": "
|
|
18036
|
+
"mode": "budget",
|
|
18021
18037
|
"efforts": [
|
|
18022
18038
|
"low",
|
|
18023
18039
|
"high"
|
|
18024
18040
|
],
|
|
18041
|
+
"effortBudgets": {
|
|
18042
|
+
"low": 1001,
|
|
18043
|
+
"high": 10001
|
|
18044
|
+
},
|
|
18025
18045
|
"effortRouting": {
|
|
18026
18046
|
"off": "gemini-3.1-pro-low",
|
|
18027
18047
|
"low": "gemini-3.1-pro-low",
|
|
@@ -18036,7 +18056,7 @@
|
|
|
18036
18056
|
"name": "GPT OSS 120B",
|
|
18037
18057
|
"api": "google-gemini-cli",
|
|
18038
18058
|
"provider": "google-antigravity",
|
|
18039
|
-
"baseUrl": "https://daily-cloudcode-pa.
|
|
18059
|
+
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
|
|
18040
18060
|
"reasoning": true,
|
|
18041
18061
|
"input": [
|
|
18042
18062
|
"text"
|
|
@@ -18110,7 +18130,11 @@
|
|
|
18110
18130
|
"high"
|
|
18111
18131
|
],
|
|
18112
18132
|
"effortRouting": {
|
|
18113
|
-
"off": "gemini-2.5-flash"
|
|
18133
|
+
"off": "gemini-2.5-flash",
|
|
18134
|
+
"minimal": "gemini-2.5-flash-thinking",
|
|
18135
|
+
"low": "gemini-2.5-flash-thinking",
|
|
18136
|
+
"medium": "gemini-2.5-flash-thinking",
|
|
18137
|
+
"high": "gemini-2.5-flash-thinking"
|
|
18114
18138
|
}
|
|
18115
18139
|
}
|
|
18116
18140
|
},
|
|
@@ -69307,7 +69331,7 @@
|
|
|
69307
69331
|
"cacheRead": 0,
|
|
69308
69332
|
"cacheWrite": 0
|
|
69309
69333
|
},
|
|
69310
|
-
"contextWindow":
|
|
69334
|
+
"contextWindow": 1000000,
|
|
69311
69335
|
"maxTokens": null,
|
|
69312
69336
|
"compat": {
|
|
69313
69337
|
"supportsUsageInStreaming": false
|
|
@@ -2,6 +2,7 @@ import { fetchAntigravityDiscoveryModels } from "../discovery/antigravity";
|
|
|
2
2
|
import { fetchGeminiModels } from "../discovery/gemini";
|
|
3
3
|
import type { ModelManagerOptions } from "../model-manager";
|
|
4
4
|
import type { FetchImpl } from "../types";
|
|
5
|
+
import { GEMINI_CLI_VARIANT_COLLAPSE_TABLE } from "../variant-collapse";
|
|
5
6
|
|
|
6
7
|
export interface GoogleModelManagerConfig {
|
|
7
8
|
apiKey?: string;
|
|
@@ -89,6 +90,7 @@ export function googleGeminiCliModelManagerOptions(
|
|
|
89
90
|
token,
|
|
90
91
|
endpoint,
|
|
91
92
|
fetcher: toDiscoveryFetch(config?.fetch),
|
|
93
|
+
collapseTable: GEMINI_CLI_VARIANT_COLLAPSE_TABLE,
|
|
92
94
|
});
|
|
93
95
|
if (models === null) {
|
|
94
96
|
return null;
|
|
@@ -2522,9 +2522,10 @@ export function litellmModelManagerOptions(
|
|
|
2522
2522
|
config?: LiteLLMModelManagerConfig,
|
|
2523
2523
|
): ModelManagerOptions<"openai-completions"> {
|
|
2524
2524
|
const apiKey = config?.apiKey;
|
|
2525
|
-
const baseUrl = config?.baseUrl ?? "http://localhost:4000/v1";
|
|
2525
|
+
const baseUrl = config?.baseUrl ?? Bun.env.LITELLM_BASE_URL ?? "http://localhost:4000/v1";
|
|
2526
2526
|
return {
|
|
2527
2527
|
providerId: "litellm",
|
|
2528
|
+
cacheProviderId: `litellm:${Bun.hash(baseUrl).toString(36)}`,
|
|
2528
2529
|
// litellm is a local-only proxy whose /v1/models returns bare ids with no
|
|
2529
2530
|
// metadata, and it is never bundled in models.json (that would leak the
|
|
2530
2531
|
// machine's localhost catalog). It proxies known upstream models, so we
|
|
@@ -2807,9 +2808,11 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
2807
2808
|
const parsedApiKey = rawApiKey ? parseGitHubCopilotApiKey(rawApiKey) : undefined;
|
|
2808
2809
|
const apiKey = parsedApiKey?.accessToken;
|
|
2809
2810
|
const baseUrl =
|
|
2810
|
-
parsedApiKey?.
|
|
2811
|
-
?
|
|
2812
|
-
: configuredBaseUrl
|
|
2811
|
+
parsedApiKey?.apiEndpoint && configuredBaseUrl.includes("githubcopilot.com")
|
|
2812
|
+
? parsedApiKey.apiEndpoint
|
|
2813
|
+
: parsedApiKey?.enterpriseUrl && configuredBaseUrl.includes("githubcopilot.com")
|
|
2814
|
+
? getGitHubCopilotBaseUrl(parsedApiKey.enterpriseUrl)
|
|
2815
|
+
: configuredBaseUrl;
|
|
2813
2816
|
const providerRefs = createBundledReferenceMap<Api>("github-copilot");
|
|
2814
2817
|
const resolveReference = createReferenceResolver(providerRefs);
|
|
2815
2818
|
return {
|
package/src/types.ts
CHANGED
|
@@ -53,6 +53,14 @@ export interface ThinkingConfig {
|
|
|
53
53
|
* thinking is disabled. Missing keys fall back to `requestModelId ?? id`.
|
|
54
54
|
*/
|
|
55
55
|
effortRouting?: Readonly<Partial<Record<Effort | "off", string>>>;
|
|
56
|
+
/**
|
|
57
|
+
* Per-effort thinking budget in tokens, baked at build time for collapsed
|
|
58
|
+
* variants whose upstream expects an explicit `thinkingBudget` instead of a
|
|
59
|
+
* value derived from the generic ladder (Antigravity Cloud Code Assist
|
|
60
|
+
* gemini-3.x). Request mapping prefers caller `thinkingBudgets`, then this
|
|
61
|
+
* map, then the provider default ladder. Only meaningful for `mode: "budget"`.
|
|
62
|
+
*/
|
|
63
|
+
effortBudgets?: Readonly<Partial<Record<Effort, number>>>;
|
|
56
64
|
/**
|
|
57
65
|
* When true, a thinking-off request MUST explicitly suppress thinking on
|
|
58
66
|
* the wire (google-level: `thinkingLevel: "MINIMAL"` + `includeThoughts:
|
|
@@ -162,6 +170,13 @@ export interface OpenAICompat {
|
|
|
162
170
|
reasoningEffortMap?: Partial<Record<Effort, string>>;
|
|
163
171
|
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
|
|
164
172
|
supportsUsageInStreaming?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Enable the Gemini thinking-loop guard (pi-ai stream layer) for this model.
|
|
175
|
+
* Defaults to true when the model id classifies as the gemini family. Set
|
|
176
|
+
* explicitly to cover an opaque OpenAI-compat proxy alias (e.g. `my-model`)
|
|
177
|
+
* that routes to Gemini, or to false to opt a gemini-family id out.
|
|
178
|
+
*/
|
|
179
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
165
180
|
/** Which field to use for max tokens. Default: auto-detected from URL. */
|
|
166
181
|
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
|
167
182
|
/** Whether tool results require the `name` field. Default: auto-detected from URL. */
|
|
@@ -365,6 +380,7 @@ export type ResolvedOpenAICompat = Required<
|
|
|
365
380
|
| "thinkingKeep"
|
|
366
381
|
| "strictResponsesPairing"
|
|
367
382
|
| "requiresJuiceZeroHack"
|
|
383
|
+
| "enableGeminiThinkingLoopGuard"
|
|
368
384
|
| "whenThinking"
|
|
369
385
|
>
|
|
370
386
|
> & {
|
|
@@ -379,6 +395,8 @@ export type ResolvedOpenAICompat = Required<
|
|
|
379
395
|
isOpenRouterHost: boolean;
|
|
380
396
|
/** The model sits behind Vercel AI Gateway. */
|
|
381
397
|
isVercelGatewayHost: boolean;
|
|
398
|
+
/** See {@link OpenAICompat.enableGeminiThinkingLoopGuard}. Set by the builder from the family classifier. */
|
|
399
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
382
400
|
/** Complete alternate view for thinking-engaged requests; swap pointers, never spread. */
|
|
383
401
|
whenThinking?: ResolvedOpenAICompat;
|
|
384
402
|
};
|
|
@@ -392,6 +410,8 @@ export interface ResolvedOpenAIResponsesCompat {
|
|
|
392
410
|
strictResponsesPairing: boolean;
|
|
393
411
|
requiresJuiceZeroHack: boolean;
|
|
394
412
|
reasoningEffortMap: Partial<Record<Effort, string>>;
|
|
413
|
+
/** See {@link OpenAICompat.enableGeminiThinkingLoopGuard}. */
|
|
414
|
+
enableGeminiThinkingLoopGuard?: boolean;
|
|
395
415
|
}
|
|
396
416
|
|
|
397
417
|
/** Fully-resolved anthropic-messages compat view (same contract as `ResolvedOpenAICompat`). */
|
package/src/variant-collapse.ts
CHANGED
|
@@ -107,80 +107,132 @@ const GEMINI_3_FLASH_FAMILY_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort
|
|
|
107
107
|
const GEMINI_3_PRO_FAMILY_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High];
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
110
|
+
* Antigravity Cloud Code Assist sends an explicit `thinkingBudget` per tier
|
|
111
|
+
* (verified against captured `daily-cloudcode-pa` requests). Flash uses round
|
|
112
|
+
* budgets; Pro offsets every budget by +1. Minimal mirrors Low (the Antigravity
|
|
113
|
+
* UI exposes Low/Medium/High only) so the effort stays selectable.
|
|
112
114
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
115
|
+
const GEMINI_3_FLASH_FAMILY_BUDGETS: Readonly<Partial<Record<Effort, number>>> = {
|
|
116
|
+
[Effort.Minimal]: 1000,
|
|
117
|
+
[Effort.Low]: 1000,
|
|
118
|
+
[Effort.Medium]: 4000,
|
|
119
|
+
[Effort.High]: 10000,
|
|
120
|
+
};
|
|
121
|
+
const GEMINI_3_PRO_FAMILY_BUDGETS: Readonly<Partial<Record<Effort, number>>> = {
|
|
122
|
+
[Effort.Low]: 1001,
|
|
123
|
+
[Effort.High]: 10001,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The two Cloud Code Assist providers share the same Antigravity discovery list
|
|
128
|
+
* but disagree on the thinking transport: `google-antigravity` (daily-cloudcode-pa)
|
|
129
|
+
* sends an explicit `thinkingBudget` (verified against captured requests), while
|
|
130
|
+
* `google-gemini-cli` (cloudcode-pa) follows the official Gemini CLI and uses
|
|
131
|
+
* `thinkingLevel`. The Gemini 3.x families therefore differ only in thinking
|
|
132
|
+
* transport (and, for Flash, the per-tier wire-id routing); everything else is
|
|
133
|
+
* shared verbatim.
|
|
134
|
+
*/
|
|
135
|
+
function geminiFlashFamily(mode: "budget" | "google-level"): EffortVariantFamily {
|
|
136
|
+
const budget = mode === "budget";
|
|
137
|
+
return {
|
|
138
|
+
id: "gemini-3.5-flash",
|
|
139
|
+
name: "Gemini 3.5 Flash",
|
|
140
|
+
members: ["gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3-flash-agent"],
|
|
141
|
+
routing: budget
|
|
142
|
+
? {
|
|
143
|
+
off: "gemini-3.5-flash-extra-low",
|
|
144
|
+
[Effort.Minimal]: "gemini-3.5-flash-extra-low",
|
|
145
|
+
[Effort.Low]: "gemini-3.5-flash-extra-low",
|
|
146
|
+
[Effort.Medium]: "gemini-3.5-flash-low",
|
|
147
|
+
[Effort.High]: "gemini-3-flash-agent",
|
|
148
|
+
}
|
|
149
|
+
: {
|
|
150
|
+
off: "gemini-3.5-flash-extra-low",
|
|
151
|
+
[Effort.Minimal]: "gemini-3-flash-agent",
|
|
152
|
+
[Effort.Low]: "gemini-3.5-flash-extra-low",
|
|
153
|
+
[Effort.Medium]: "gemini-3.5-flash-extra-low",
|
|
154
|
+
[Effort.High]: "gemini-3.5-flash-low",
|
|
155
|
+
},
|
|
156
|
+
thinking: budget
|
|
157
|
+
? { mode: "budget", efforts: GEMINI_3_FLASH_FAMILY_EFFORTS, effortBudgets: GEMINI_3_FLASH_FAMILY_BUDGETS }
|
|
158
|
+
: { mode: "google-level", efforts: GEMINI_3_FLASH_FAMILY_EFFORTS },
|
|
159
|
+
suppressWhenOff: true,
|
|
160
|
+
// Retired bare id; the alias only fires when no live model holds it
|
|
161
|
+
// (exact match wins in every resolver).
|
|
162
|
+
extraAliases: ["gemini-3-flash"],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function geminiProFamily(mode: "budget" | "google-level"): EffortVariantFamily {
|
|
167
|
+
const budget = mode === "budget";
|
|
168
|
+
return {
|
|
169
|
+
id: "gemini-3.1-pro",
|
|
170
|
+
name: "Gemini 3.1 Pro",
|
|
171
|
+
// High routes to `gemini-pro-agent` — the upstream `gemini-3.1-pro-high`
|
|
172
|
+
// deployment returns INVALID_ARGUMENT on every streamGenerateContent
|
|
173
|
+
// request (both CCA endpoints) while discovery still lists it;
|
|
174
|
+
// `gemini-pro-agent` is the same model ("Gemini 3.1 Pro (High)", same
|
|
175
|
+
// thinking budget/caps) and accepts the identical request body.
|
|
176
|
+
// `gemini-3.1-pro-high` stays a member so the dead raw id is consumed.
|
|
177
|
+
members: ["gemini-3.1-pro-low", "gemini-pro-agent", "gemini-3.1-pro-high"],
|
|
178
|
+
retiredMembers: ["gemini-3.1-pro-high"],
|
|
179
|
+
routing: {
|
|
180
|
+
off: "gemini-3.1-pro-low",
|
|
181
|
+
[Effort.Low]: "gemini-3.1-pro-low",
|
|
182
|
+
[Effort.High]: "gemini-pro-agent",
|
|
163
183
|
},
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
thinking: budget
|
|
185
|
+
? { mode: "budget", efforts: GEMINI_3_PRO_FAMILY_EFFORTS, effortBudgets: GEMINI_3_PRO_FAMILY_BUDGETS }
|
|
186
|
+
: { mode: "google-level", efforts: GEMINI_3_PRO_FAMILY_EFFORTS },
|
|
187
|
+
suppressWhenOff: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** CCA families shared verbatim by both providers (transport-agnostic). */
|
|
192
|
+
const SHARED_CCA_FAMILIES: readonly EffortVariantFamily[] = [
|
|
193
|
+
{
|
|
194
|
+
// Legacy static family — covers stale snapshots and caches. Stale ids are
|
|
195
|
+
// unverified against the budget-mode CCA contract; keep them on level.
|
|
196
|
+
id: "gemini-3-pro",
|
|
197
|
+
name: "Gemini 3 Pro",
|
|
198
|
+
members: ["gemini-3-pro-low", "gemini-3-pro-high"],
|
|
199
|
+
routing: {
|
|
200
|
+
off: "gemini-3-pro-low",
|
|
201
|
+
[Effort.Low]: "gemini-3-pro-low",
|
|
202
|
+
[Effort.High]: "gemini-3-pro-high",
|
|
171
203
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
204
|
+
thinking: { mode: "google-level", efforts: GEMINI_3_PRO_FAMILY_EFFORTS },
|
|
205
|
+
suppressWhenOff: true,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
// Rename-only collapse: every effort and off fall back to the wire id.
|
|
209
|
+
id: "gpt-oss-120b",
|
|
210
|
+
name: "GPT-OSS 120B",
|
|
211
|
+
members: ["gpt-oss-120b-medium"],
|
|
212
|
+
routing: {},
|
|
213
|
+
thinking: { mode: "budget", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] },
|
|
214
|
+
},
|
|
215
|
+
thinkingPair("claude-sonnet-4-6", "Claude Sonnet 4.6"),
|
|
216
|
+
thinkingPair("claude-opus-4-6", "Claude Opus 4.6"),
|
|
217
|
+
thinkingPair("claude-sonnet-4-5", "Claude Sonnet 4.5"),
|
|
218
|
+
thinkingPair("claude-opus-4-5", "Claude Opus 4.5"),
|
|
219
|
+
thinkingPair("gemini-2.5-flash", "Gemini 2.5 Flash"),
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
/** `google-antigravity` (daily-cloudcode-pa): Gemini 3.x on the budget transport. */
|
|
223
|
+
export const ANTIGRAVITY_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
|
|
224
|
+
families: [geminiFlashFamily("budget"), geminiProFamily("budget"), ...SHARED_CCA_FAMILIES],
|
|
178
225
|
};
|
|
179
226
|
|
|
180
|
-
/**
|
|
227
|
+
/** `google-gemini-cli` (cloudcode-pa): Gemini 3.x on the level transport (official CLI parity). */
|
|
228
|
+
export const GEMINI_CLI_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
|
|
229
|
+
families: [geminiFlashFamily("google-level"), geminiProFamily("google-level"), ...SHARED_CCA_FAMILIES],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/** Provider id → hand collapse table. The CCA providers diverge on thinking transport. */
|
|
181
233
|
export const VARIANT_COLLAPSE_TABLES: Readonly<Record<string, VariantCollapseTable>> = {
|
|
182
234
|
"google-antigravity": ANTIGRAVITY_VARIANT_COLLAPSE_TABLE,
|
|
183
|
-
"google-gemini-cli":
|
|
235
|
+
"google-gemini-cli": GEMINI_CLI_VARIANT_COLLAPSE_TABLE,
|
|
184
236
|
};
|
|
185
237
|
|
|
186
238
|
/**
|
|
@@ -360,6 +412,47 @@ function reconcileRetiredRouting<TSpec extends VariantSpecLike>(
|
|
|
360
412
|
return next;
|
|
361
413
|
}
|
|
362
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Refresh a collapsed snapshot's thinking surface in place. Bundled catalog and
|
|
417
|
+
* prev-generation snapshots freeze a family's transport, budgets, and routing;
|
|
418
|
+
* discovery emits the canonical id but the exact-id merge never overwrites a
|
|
419
|
+
* stale `family.id` row (e.g. `gemini-3.1-pro`) nor a recycled `extraAliases`
|
|
420
|
+
* row (e.g. `gemini-3-flash`). This re-applies the hand-table family's thinking,
|
|
421
|
+
* routing, and default wire id while keeping the spec id (load-bearing for exact
|
|
422
|
+
* selectors and bundled lookups). Returns `spec` by reference when unchanged.
|
|
423
|
+
*/
|
|
424
|
+
function refreshCollapsedThinking<TSpec extends VariantSpecLike>(
|
|
425
|
+
spec: TSpec,
|
|
426
|
+
family: EffortVariantFamily,
|
|
427
|
+
retired: ReadonlySet<string> | undefined,
|
|
428
|
+
): TSpec {
|
|
429
|
+
// Scope snapshot self-heal to families carrying a curated per-effort budget
|
|
430
|
+
// contract (Antigravity gemini-3.x). Their routing targets are all verified
|
|
431
|
+
// live, so rebuilding routing here is safe; families without `effortBudgets`
|
|
432
|
+
// (derived `X`/`X-thinking` pairs, claude pairs) keep their presence-filtered
|
|
433
|
+
// snapshot routing untouched.
|
|
434
|
+
if (!spec.reasoning || family.thinking.effortBudgets === undefined) return spec;
|
|
435
|
+
const routing: Partial<Record<Effort | "off", string>> = {};
|
|
436
|
+
let hasRouting = false;
|
|
437
|
+
for (const effortKey in family.routing) {
|
|
438
|
+
const target = family.routing[effortKey as Effort | "off"];
|
|
439
|
+
if (target !== undefined && !retired?.has(target)) {
|
|
440
|
+
routing[effortKey as Effort | "off"] = target;
|
|
441
|
+
hasRouting = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const thinking: ThinkingConfig = { ...family.thinking };
|
|
445
|
+
if (hasRouting) thinking.effortRouting = routing;
|
|
446
|
+
if (family.suppressWhenOff) thinking.suppressWhenOff = true;
|
|
447
|
+
const offTarget = family.routing.off;
|
|
448
|
+
const requestModelId =
|
|
449
|
+
offTarget !== undefined && !retired?.has(offTarget) && offTarget !== spec.id ? offTarget : spec.requestModelId;
|
|
450
|
+
if (Bun.deepEquals(thinking, spec.thinking) && requestModelId === spec.requestModelId) {
|
|
451
|
+
return spec;
|
|
452
|
+
}
|
|
453
|
+
return { ...spec, thinking, ...(requestModelId !== undefined ? { requestModelId } : {}) };
|
|
454
|
+
}
|
|
455
|
+
|
|
363
456
|
/**
|
|
364
457
|
* Collapse every family in `table` found in `specs`. Non-member specs pass
|
|
365
458
|
* through verbatim (by reference), order preserved; the collapsed spec
|
|
@@ -394,11 +487,17 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
|
|
|
394
487
|
: existing;
|
|
395
488
|
const rawPresent = family.members.filter(id => byId.has(id) && !(id === family.id && existingCollapsed));
|
|
396
489
|
if (rawPresent.length === 0) {
|
|
397
|
-
// Inert (no members) or already collapsed (pass-through)
|
|
398
|
-
//
|
|
399
|
-
|
|
490
|
+
// Inert (no members) or already collapsed (pass-through). A stale
|
|
491
|
+
// family.id-keyed snapshot is refreshed in place from the current
|
|
492
|
+
// hand-table family (transport/budgets/routing); retired targets drop.
|
|
493
|
+
// Recycled extraAliases rows are healed in a later pass.
|
|
494
|
+
const refreshed =
|
|
495
|
+
existing !== undefined && existingCollapsed
|
|
496
|
+
? refreshCollapsedThinking(reconciled ?? existing, family, retired)
|
|
497
|
+
: reconciled;
|
|
498
|
+
if (refreshed !== undefined && refreshed !== existing) {
|
|
400
499
|
familyIdBySpecId.set(family.id, family.id);
|
|
401
|
-
replacement.set(family.id,
|
|
500
|
+
replacement.set(family.id, refreshed);
|
|
402
501
|
}
|
|
403
502
|
continue;
|
|
404
503
|
}
|
|
@@ -464,6 +563,27 @@ export function collapseEffortVariants<TSpec extends VariantSpecLike>(
|
|
|
464
563
|
replacement.set(family.id, collapsed);
|
|
465
564
|
}
|
|
466
565
|
|
|
566
|
+
// Refresh stale alias-keyed snapshots in place (recycled bare ids). Runs even
|
|
567
|
+
// when the canonical family.id row is also present, since the exact-id merge
|
|
568
|
+
// keeps the stale alias row alongside the discovered canonical one.
|
|
569
|
+
for (const family of table.families) {
|
|
570
|
+
if (family.extraAliases === undefined) continue;
|
|
571
|
+
const retired =
|
|
572
|
+
family.retiredMembers !== undefined && family.retiredMembers.length > 0
|
|
573
|
+
? new Set(family.retiredMembers)
|
|
574
|
+
: undefined;
|
|
575
|
+
for (const alias of family.extraAliases) {
|
|
576
|
+
if (alias === family.id || familyIdBySpecId.has(alias)) continue;
|
|
577
|
+
const aliasSpec = byId.get(alias);
|
|
578
|
+
if (aliasSpec === undefined) continue;
|
|
579
|
+
const refreshed = refreshCollapsedThinking(aliasSpec, family, retired);
|
|
580
|
+
if (refreshed !== aliasSpec) {
|
|
581
|
+
familyIdBySpecId.set(alias, alias);
|
|
582
|
+
replacement.set(alias, refreshed);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
467
587
|
if (replacement.size === 0) return [...specs];
|
|
468
588
|
|
|
469
589
|
const emitted = new Set<string>();
|
|
@@ -602,7 +722,13 @@ export function resolveBareVariantAlias(modelId: string): BareVariantAliasHit |
|
|
|
602
722
|
if (hit === undefined) continue;
|
|
603
723
|
const providers: Provider[] = [];
|
|
604
724
|
for (const candidate in VARIANT_COLLAPSE_TABLES) {
|
|
605
|
-
|
|
725
|
+
// Match by resolved alias target, not table identity: the CCA providers
|
|
726
|
+
// now hold distinct table objects that still share these aliases.
|
|
727
|
+
if (
|
|
728
|
+
getAliasIndex(VARIANT_COLLAPSE_TABLES[candidate] as VariantCollapseTable).forward.get(normalized) === hit
|
|
729
|
+
) {
|
|
730
|
+
providers.push(candidate);
|
|
731
|
+
}
|
|
606
732
|
}
|
|
607
733
|
return { id: hit, providers };
|
|
608
734
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* GeminiCLI/VERSION/MODEL (PLATFORM; ARCH; SURFACE)
|
|
5
5
|
*/
|
|
6
6
|
export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): string {
|
|
7
|
-
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.
|
|
7
|
+
const version = process.env.PI_AI_GEMINI_CLI_VERSION || "0.46.0";
|
|
8
8
|
const platform = process.platform === "win32" ? "win32" : process.platform;
|
|
9
9
|
const arch = process.arch === "x64" ? "x64" : process.arch;
|
|
10
10
|
return `GeminiCLI/${version}/${modelId} (${platform}; ${arch}; terminal)`;
|
|
@@ -20,8 +20,6 @@ export const ANTIGRAVITY_SYSTEM_INSTRUCTION =
|
|
|
20
20
|
"You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question." +
|
|
21
21
|
"**Absolute paths only**" +
|
|
22
22
|
"**Proactiveness**";
|
|
23
|
-
export const ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION =
|
|
24
|
-
'CRITICAL: NEVER output rule checks, formatting guidelines, constraint checklists (e.g. "No emdashes"), or your thinking/personality preambles in the final response. Output only the final response.';
|
|
25
23
|
/**
|
|
26
24
|
* Antigravity / Cloud Code Assist user agent. Lives in its own file so discovery
|
|
27
25
|
* and usage code can read it without pulling the heavy google-gemini-cli provider
|
|
@@ -29,7 +27,7 @@ export const ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION =
|
|
|
29
27
|
* parse graph.
|
|
30
28
|
*/
|
|
31
29
|
export let getAntigravityUserAgent = () => {
|
|
32
|
-
const DEFAULT_ANTIGRAVITY_VERSION = "1.
|
|
30
|
+
const DEFAULT_ANTIGRAVITY_VERSION = "2.1.4";
|
|
33
31
|
const version = process.env.PI_AI_ANTIGRAVITY_VERSION || DEFAULT_ANTIGRAVITY_VERSION;
|
|
34
32
|
// Map Node.js platform/arch to Antigravity's expected format.
|
|
35
33
|
// Verified against Antigravity source: _qn() and wqn() in main.js.
|
|
@@ -37,7 +35,32 @@ export let getAntigravityUserAgent = () => {
|
|
|
37
35
|
// process.arch: x64→amd64, ia32→386, others pass through (arm64)
|
|
38
36
|
const os = process.platform === "win32" ? "windows" : process.platform;
|
|
39
37
|
const arch = process.arch === "x64" ? "amd64" : process.arch === "ia32" ? "386" : process.arch;
|
|
40
|
-
const userAgent = `antigravity/${version} ${os}/${arch}`;
|
|
38
|
+
const userAgent = `antigravity/hub/${version} ${os}/${arch}`;
|
|
41
39
|
getAntigravityUserAgent = () => userAgent;
|
|
42
40
|
return userAgent;
|
|
43
41
|
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Per-wire-id Antigravity Cloud Code Assist request constants, captured from the
|
|
45
|
+
* real `antigravity/hub` client against `daily-cloudcode-pa`. `modelEnum` is the
|
|
46
|
+
* opaque `labels.model_enum` token the client tags each request with;
|
|
47
|
+
* `maxOutputTokens` is the fixed `generationConfig.maxOutputTokens` it sends
|
|
48
|
+
* regardless of the thinking budget. Keyed by the routed upstream wire id
|
|
49
|
+
* (post effort-routing), not the collapsed logical id. Checkpoint-only ids
|
|
50
|
+
* (e.g. `gemini-3.1-flash-lite`) are intentionally absent — this provider only
|
|
51
|
+
* emits agent requests.
|
|
52
|
+
*/
|
|
53
|
+
export interface AntigravityModelWireProfile {
|
|
54
|
+
modelEnum: string;
|
|
55
|
+
maxOutputTokens: number;
|
|
56
|
+
}
|
|
57
|
+
export const ANTIGRAVITY_MODEL_WIRE_PROFILES: Readonly<Record<string, AntigravityModelWireProfile>> = {
|
|
58
|
+
"gemini-3.5-flash-extra-low": { modelEnum: "MODEL_PLACEHOLDER_M187", maxOutputTokens: 65536 },
|
|
59
|
+
"gemini-3.5-flash-low": { modelEnum: "MODEL_PLACEHOLDER_M20", maxOutputTokens: 65536 },
|
|
60
|
+
"gemini-3-flash-agent": { modelEnum: "MODEL_PLACEHOLDER_M132", maxOutputTokens: 65536 },
|
|
61
|
+
"gemini-3.1-pro-low": { modelEnum: "MODEL_PLACEHOLDER_M36", maxOutputTokens: 65535 },
|
|
62
|
+
"gemini-pro-agent": { modelEnum: "MODEL_PLACEHOLDER_M16", maxOutputTokens: 65535 },
|
|
63
|
+
};
|
|
64
|
+
export function getAntigravityModelWireProfile(wireModelId: string): AntigravityModelWireProfile | undefined {
|
|
65
|
+
return ANTIGRAVITY_MODEL_WIRE_PROFILES[wireModelId];
|
|
66
|
+
}
|
|
@@ -30,11 +30,13 @@ export const COPILOT_API_HEADERS = {
|
|
|
30
30
|
type GitHubCopilotApiKeyPayload = {
|
|
31
31
|
token?: unknown;
|
|
32
32
|
enterpriseUrl?: unknown;
|
|
33
|
+
apiEndpoint?: unknown;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
export type ParsedGitHubCopilotApiKey = {
|
|
36
37
|
accessToken: string;
|
|
37
38
|
enterpriseUrl?: string;
|
|
39
|
+
apiEndpoint?: string;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
const PUBLIC_GITHUB_HOSTS = new Set(["api.github.com", "github.com", "www.github.com"]);
|
|
@@ -51,6 +53,18 @@ export function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined
|
|
|
51
53
|
return normalized;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
export function normalizeGitHubCopilotApiEndpoint(input: string | undefined): string | undefined {
|
|
57
|
+
const trimmed = input?.trim();
|
|
58
|
+
if (!trimmed?.startsWith("https://")) return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(trimmed);
|
|
61
|
+
if (url.protocol !== "https:" || !url.hostname) return undefined;
|
|
62
|
+
return trimmed.replace(/\/+$/, "");
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
export function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilotApiKey {
|
|
55
69
|
try {
|
|
56
70
|
const parsed = JSON.parse(apiKeyRaw) as GitHubCopilotApiKeyPayload;
|
|
@@ -61,6 +75,10 @@ export function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilot
|
|
|
61
75
|
typeof parsed.enterpriseUrl === "string"
|
|
62
76
|
? normalizeGitHubCopilotEnterpriseDomain(parsed.enterpriseUrl)
|
|
63
77
|
: undefined,
|
|
78
|
+
apiEndpoint:
|
|
79
|
+
typeof parsed.apiEndpoint === "string"
|
|
80
|
+
? normalizeGitHubCopilotApiEndpoint(parsed.apiEndpoint)
|
|
81
|
+
: undefined,
|
|
64
82
|
};
|
|
65
83
|
}
|
|
66
84
|
} catch {}
|