dsh-plugin-subscription-oauth 0.8.13 → 0.8.15
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/lib/auth/provider-controller.js +21 -17
- package/lib/auth/tokens.js +1 -1
- package/lib/client/FastModeButton.d.ts +50 -0
- package/lib/client/FastModeButton.js +94 -0
- package/lib/client/UsageChip.js +2 -1
- package/lib/client/index.d.ts +14 -1
- package/lib/client/index.js +46 -2
- package/lib/client/styles.d.ts +8 -0
- package/lib/client/styles.js +61 -0
- package/lib/client.js +205 -3
- package/lib/client.js.map +1 -1
- package/lib/index.js +2 -0
- package/lib/protocol.d.ts +13 -1
- package/lib/providers/antigravity-catalog.d.ts +29 -9
- package/lib/providers/antigravity-catalog.js +212 -56
- package/lib/providers/antigravity.d.ts +2 -0
- package/lib/providers/antigravity.js +25 -3
- package/lib/providers/ark-provider.js +4 -0
- package/lib/providers/ark.d.ts +4 -0
- package/lib/providers/ark.js +16 -2
- package/lib/providers/glm-provider.js +4 -0
- package/lib/providers/glm.d.ts +4 -0
- package/lib/providers/glm.js +16 -2
- package/lib/providers/grok.d.ts +109 -3
- package/lib/providers/grok.js +258 -34
- package/lib/providers/kimi-provider.js +9 -0
- package/lib/providers/kimi.d.ts +28 -0
- package/lib/providers/kimi.js +89 -9
- package/lib/translate/antigravity.js +3 -3
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -243,6 +243,8 @@ export function apply(ctx, config = {}) {
|
|
|
243
243
|
};
|
|
244
244
|
registerSubscriptionRpc(ctx, registry, balanceContext);
|
|
245
245
|
// Warm each route's live catalog for an install that is already signed in.
|
|
246
|
+
// Discovery uses the stored token as-is (no resolve, no refresh), so a dead grant
|
|
247
|
+
// is skipped silently rather than opening the browser.
|
|
246
248
|
for (const id of registry.ids()) {
|
|
247
249
|
void registry.discover(id, false).catch((error) => {
|
|
248
250
|
onWarn(`${id} startup discovery failed: ${error instanceof Error ? error.message : String(error)}`);
|
package/lib/protocol.d.ts
CHANGED
|
@@ -324,7 +324,7 @@ export interface RpcMap {
|
|
|
324
324
|
value: CcSwitchBalanceResult;
|
|
325
325
|
};
|
|
326
326
|
'model-variants': {
|
|
327
|
-
payload:
|
|
327
|
+
payload: ModelVariantsRequest;
|
|
328
328
|
value: ModelVariantsResult;
|
|
329
329
|
};
|
|
330
330
|
}
|
|
@@ -357,11 +357,23 @@ export type BalanceResult =
|
|
|
357
357
|
export interface ModelVariants {
|
|
358
358
|
/** Fast/lightspeed variant wire id, when the provider reported one. */
|
|
359
359
|
fast?: string;
|
|
360
|
+
/**
|
|
361
|
+
* The base model the fast variant belongs to.
|
|
362
|
+
*
|
|
363
|
+
* Sent with `fast` so the picker button can switch back off when the selection is
|
|
364
|
+
* already the fast variant, which is not itself a listed row.
|
|
365
|
+
*/
|
|
366
|
+
fastBase?: string;
|
|
360
367
|
/** Image-generation model wire id, when the provider reported one. */
|
|
361
368
|
image?: string;
|
|
362
369
|
}
|
|
363
370
|
/** Result of `model-variants`: the variants for one model id. */
|
|
364
371
|
export type ModelVariantsResult = ModelVariants | undefined;
|
|
372
|
+
/** Payload of `model-variants`. */
|
|
373
|
+
export interface ModelVariantsRequest {
|
|
374
|
+
provider: string;
|
|
375
|
+
id: string;
|
|
376
|
+
}
|
|
365
377
|
/** Payload of `deepseek-balance`. */
|
|
366
378
|
export interface DeepSeekBalanceRequest {
|
|
367
379
|
/** Client metadata the host's account service expects. */
|
|
@@ -14,20 +14,40 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { UsageWindow } from '../protocol.js';
|
|
16
16
|
import type { CatalogEntry } from './catalog.js';
|
|
17
|
-
/** True when a model id names
|
|
18
|
-
export declare function
|
|
19
|
-
/**
|
|
20
|
-
|
|
17
|
+
/** True when a model id names an image-generation model. */
|
|
18
|
+
export declare function isImageModel(id: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The wire id one gemini flash family and effort level map to when no live listing
|
|
21
|
+
* has been parsed yet.
|
|
22
|
+
*
|
|
23
|
+
* The live listing is the authority (see `wireIdFor`); this is the fallback that
|
|
24
|
+
* keeps a cold start — a persisted selection used before discovery ran — from
|
|
25
|
+
* sending a bare family id the endpoint does not serve. The service names tiers
|
|
26
|
+
* inconsistently: the High tier of 3.5 Flash is `gemini-3.5-flash-high` in some
|
|
27
|
+
* groups and `gemini-3.5-flash-agent` in others; 3.8 Flash is
|
|
28
|
+
* `gemini-3.8-flash-tiered`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function geminiTierId(base: string, effort: string): string;
|
|
21
31
|
/** Antigravity's own reasoning ladder, per model family. */
|
|
22
32
|
export declare function antigravityEffortsFor(model: string): readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* The wire id one picker row and effort level must dispatch.
|
|
35
|
+
*
|
|
36
|
+
* @param rowId - the row the picker offered.
|
|
37
|
+
* @param effort - the chosen level, when the caller picked one.
|
|
38
|
+
* @returns the wire id, or undefined when no live listing covers this row.
|
|
39
|
+
*/
|
|
40
|
+
export declare function wireIdFor(rowId: string, effort?: string): string | undefined;
|
|
41
|
+
/** Collapse families into one row each, as magpie does. */
|
|
42
|
+
export declare function collapseFamilies(raw: CatalogEntry[]): CatalogEntry[];
|
|
23
43
|
/**
|
|
24
44
|
* Read the agent-callable catalog from a `fetchAvailableModels` response.
|
|
25
45
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
46
|
+
* The response's own grouping decides which ids the agent envelope may call; this
|
|
47
|
+
* removes the surfaces it cannot (tabs, commit messages, transcription, standalone
|
|
48
|
+
* image generation) and keeps the response's own order. Tiers of one family then
|
|
49
|
+
* collapse into the single picker row that serves them, with a wire-id map so the
|
|
50
|
+
* request still names an id the account listed (see `wireIdFor`).
|
|
31
51
|
*
|
|
32
52
|
* @param payload - the decoded response.
|
|
33
53
|
* @returns the catalog entries, in the response's order.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module dsh-plugin-subscription-oauth/providers/antigravity-catalog
|
|
14
14
|
*/
|
|
15
|
+
import { baseCandidatesOf } from './grok.js';
|
|
15
16
|
/** Effort rungs that fold into one picker row. */
|
|
16
17
|
const EFFORTS = ['low', 'medium', 'high'];
|
|
17
18
|
/** True for a plain object usable as a JSON record. */
|
|
@@ -48,19 +49,33 @@ function modelInfo(raw) {
|
|
|
48
49
|
...raw.supportsImages === true ? { supportsImages: true } : {},
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
/**
|
|
52
|
-
const
|
|
53
|
-
/** True when a model id names
|
|
54
|
-
export function
|
|
55
|
-
return
|
|
52
|
+
/** Model ids that are image-generation models, never picker rows. */
|
|
53
|
+
const IMAGE_MODEL = /(?:^|[-_])(?:image|imagine|draw|pic|photo)(?:[-_]|$)/i;
|
|
54
|
+
/** True when a model id names an image-generation model. */
|
|
55
|
+
export function isImageModel(id) {
|
|
56
|
+
return IMAGE_MODEL.test(id);
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The wire id one gemini flash family and effort level map to when no live listing
|
|
60
|
+
* has been parsed yet.
|
|
61
|
+
*
|
|
62
|
+
* The live listing is the authority (see `wireIdFor`); this is the fallback that
|
|
63
|
+
* keeps a cold start — a persisted selection used before discovery ran — from
|
|
64
|
+
* sending a bare family id the endpoint does not serve. The service names tiers
|
|
65
|
+
* inconsistently: the High tier of 3.5 Flash is `gemini-3.5-flash-high` in some
|
|
66
|
+
* groups and `gemini-3.5-flash-agent` in others; 3.8 Flash is
|
|
67
|
+
* `gemini-3.8-flash-tiered`.
|
|
68
|
+
*/
|
|
69
|
+
export function geminiTierId(base, effort) {
|
|
70
|
+
if (effort === 'high') {
|
|
71
|
+
if (base === 'gemini-3.5-flash')
|
|
72
|
+
return 'gemini-3.5-flash-agent';
|
|
73
|
+
if (base === 'gemini-3.8-flash')
|
|
74
|
+
return 'gemini-3.8-flash-tiered';
|
|
75
|
+
if (base === 'gemini-3-flash')
|
|
76
|
+
return 'gemini-3-flash-agent';
|
|
77
|
+
}
|
|
78
|
+
return `${base}-${effort}`;
|
|
64
79
|
}
|
|
65
80
|
/** Antigravity's own reasoning ladder, per model family. */
|
|
66
81
|
export function antigravityEffortsFor(model) {
|
|
@@ -72,6 +87,180 @@ export function antigravityEffortsFor(model) {
|
|
|
72
87
|
return ['low', 'medium', 'high'];
|
|
73
88
|
return [];
|
|
74
89
|
}
|
|
90
|
+
// ── magpie-style family collapse ─────────────────────────────────────────
|
|
91
|
+
/** One effort word of Antigravity's ids and the level it means. */
|
|
92
|
+
const EFFORT_WORDS = [
|
|
93
|
+
{ word: 'extra-high', level: 'xhigh' },
|
|
94
|
+
{ word: 'extra-low', level: 'minimal' },
|
|
95
|
+
{ word: 'xhigh', level: 'xhigh' },
|
|
96
|
+
{ word: 'minimal', level: 'minimal' },
|
|
97
|
+
// A flash family's High tier is spelled `-high`, `-tiered` or `-agent` depending on
|
|
98
|
+
// the generation, so all three mean the same rung of the ladder.
|
|
99
|
+
{ word: 'tiered', level: 'high' },
|
|
100
|
+
{ word: 'agent', level: 'high' },
|
|
101
|
+
{ word: 'low', level: 'low' },
|
|
102
|
+
{ word: 'medium', level: 'medium' },
|
|
103
|
+
{ word: 'high', level: 'high' },
|
|
104
|
+
{ word: 'max', level: 'max' },
|
|
105
|
+
];
|
|
106
|
+
/** A ladder's canonical order. */
|
|
107
|
+
const LEVEL_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
108
|
+
/** An id's family and the level its effort word says, '' when it has none. */
|
|
109
|
+
function splitEffortId(id) {
|
|
110
|
+
for (const { word, level } of EFFORT_WORDS) {
|
|
111
|
+
if (id.endsWith(`-${word}`)) {
|
|
112
|
+
const base = id.slice(0, id.length - word.length - 1);
|
|
113
|
+
if (base.length > 0)
|
|
114
|
+
return { family: base, effort: level };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { family: id, effort: '' };
|
|
118
|
+
}
|
|
119
|
+
/** The level a display name's bracketed suffix says ("Gemini 3.7 Flash (High)" → high). */
|
|
120
|
+
function nameEffort(name) {
|
|
121
|
+
const open = name.lastIndexOf(' (');
|
|
122
|
+
if (open < 0 || !name.endsWith(')'))
|
|
123
|
+
return { base: name, effort: '' };
|
|
124
|
+
const inner = name.slice(open + 2, -1).toLowerCase();
|
|
125
|
+
let effort = '';
|
|
126
|
+
if (inner === 'minimal' || inner === 'extra low')
|
|
127
|
+
effort = 'minimal';
|
|
128
|
+
else if (inner === 'low')
|
|
129
|
+
effort = 'low';
|
|
130
|
+
else if (inner === 'medium')
|
|
131
|
+
effort = 'medium';
|
|
132
|
+
else if (inner === 'high')
|
|
133
|
+
effort = 'high';
|
|
134
|
+
else if (inner === 'extra high' || inner === 'xhigh')
|
|
135
|
+
effort = 'xhigh';
|
|
136
|
+
if (effort === '')
|
|
137
|
+
return { base: name, effort: '' };
|
|
138
|
+
return { base: name.slice(0, open).trim(), effort };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Group raw entries into the families the service names.
|
|
142
|
+
*
|
|
143
|
+
* Grouping follows the display name, not the id, because the service names the family
|
|
144
|
+
* and lets the wire id drift: the High tier of Gemini 3.5 Flash is
|
|
145
|
+
* `gemini-3-flash-agent` — an id that names a different generation than the name it
|
|
146
|
+
* carries. A level is stated by the name's own bracket when it has one, and by the
|
|
147
|
+
* id's effort word otherwise; the family a variant belongs to is the longest listed
|
|
148
|
+
* prefix of its own id, so `gemini-3.5-flash-low` folds into whichever listed row its
|
|
149
|
+
* prefix names with no hand-written family map.
|
|
150
|
+
*
|
|
151
|
+
* @param raw - one entry per wire id the listing exposed.
|
|
152
|
+
* @returns the families, in the order their first member appeared.
|
|
153
|
+
*/
|
|
154
|
+
function familiesOf(raw) {
|
|
155
|
+
const byName = new Map();
|
|
156
|
+
const order = [];
|
|
157
|
+
const listed = new Set(raw.map((entry) => entry.id));
|
|
158
|
+
const familyOf = (id, fallback) => {
|
|
159
|
+
for (const base of baseCandidatesOf(id)) {
|
|
160
|
+
if (listed.has(base))
|
|
161
|
+
return base;
|
|
162
|
+
}
|
|
163
|
+
return fallback;
|
|
164
|
+
};
|
|
165
|
+
for (const entry of raw) {
|
|
166
|
+
const { base: name, effort: named } = nameEffort(entry.name);
|
|
167
|
+
const { family: idBase, effort: idLevel } = splitEffortId(entry.id);
|
|
168
|
+
// The family's key: the name's bracket-free base, when the name states a level, so
|
|
169
|
+
// "Gemini 3.5 Flash (Medium)" is always that family even if no listed id starts
|
|
170
|
+
// with `gemini-3.5-flash`; otherwise the longest listed prefix of the id itself.
|
|
171
|
+
const familyBase = named !== '' ? name : (idBase !== entry.id ? familyOf(entry.id, name) : name);
|
|
172
|
+
let f = byName.get(familyBase);
|
|
173
|
+
if (f === undefined) {
|
|
174
|
+
f = { name: familyBase, variants: [], base: '' };
|
|
175
|
+
byName.set(familyBase, f);
|
|
176
|
+
order.push(f);
|
|
177
|
+
}
|
|
178
|
+
if (f.base === '' && idBase !== entry.id)
|
|
179
|
+
f.base = idBase;
|
|
180
|
+
f.variants.push({
|
|
181
|
+
id: entry.id,
|
|
182
|
+
contextWindow: entry.contextWindow,
|
|
183
|
+
inputModalities: entry.inputModalities ?? ['text'],
|
|
184
|
+
level: named !== '' ? named : idLevel,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return order;
|
|
188
|
+
}
|
|
189
|
+
/** The rungs a family defaults to, best first, when no variant is the bare model. */
|
|
190
|
+
const DEFAULT_ORDER = ['xhigh', 'high', 'medium', 'low', 'minimal', 'max'];
|
|
191
|
+
/** The variant whose id the family's row carries: the bare model, else its top rung. */
|
|
192
|
+
function defaultVariant(f) {
|
|
193
|
+
const bare = f.variants.find((v) => v.level === '');
|
|
194
|
+
if (bare !== undefined)
|
|
195
|
+
return bare;
|
|
196
|
+
for (const level of DEFAULT_ORDER) {
|
|
197
|
+
const found = f.variants.find((v) => v.level === level);
|
|
198
|
+
if (found !== undefined)
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
201
|
+
return f.variants[0];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The wire id each picker row must send per effort level, as the last parsed family
|
|
205
|
+
* listing reported it.
|
|
206
|
+
*
|
|
207
|
+
* Collapsing gives the picker one row per family, but the endpoint only serves the
|
|
208
|
+
* suffixed ids the listing named: the High tier of Gemini 3.5 Flash is
|
|
209
|
+
* `gemini-3-flash-agent`, and `gpt-oss-120b` only exists as `gpt-oss-120b-medium`.
|
|
210
|
+
* Nothing here is invented — a level the listing did not name falls back to the
|
|
211
|
+
* family's own default id, and only a tiered gemini family may synthesise the tier
|
|
212
|
+
* spelling the endpoint accepts (see `geminiTierId`).
|
|
213
|
+
*/
|
|
214
|
+
const wireIds = new Map();
|
|
215
|
+
/**
|
|
216
|
+
* The wire id one picker row and effort level must dispatch.
|
|
217
|
+
*
|
|
218
|
+
* @param rowId - the row the picker offered.
|
|
219
|
+
* @param effort - the chosen level, when the caller picked one.
|
|
220
|
+
* @returns the wire id, or undefined when no live listing covers this row.
|
|
221
|
+
*/
|
|
222
|
+
export function wireIdFor(rowId, effort) {
|
|
223
|
+
const row = wireIds.get(rowId);
|
|
224
|
+
if (row === undefined)
|
|
225
|
+
return undefined;
|
|
226
|
+
const chosen = effort === undefined ? undefined : row.levels.get(effort);
|
|
227
|
+
if (chosen !== undefined)
|
|
228
|
+
return chosen;
|
|
229
|
+
// A tiered gemini family the listing exposed only one way still takes its own tier
|
|
230
|
+
// spelling for the three levels this build knows (`gemini-3.8-flash` + low →
|
|
231
|
+
// `gemini-3.8-flash-low`). Nothing else is synthesised.
|
|
232
|
+
const tiered = effort === 'low' || effort === 'medium' || effort === 'high';
|
|
233
|
+
if (tiered && row.base !== '' && row.base !== rowId)
|
|
234
|
+
return geminiTierId(row.base, effort);
|
|
235
|
+
return row.levels.get('') ?? rowId;
|
|
236
|
+
}
|
|
237
|
+
/** Collapse families into one row each, as magpie does. */
|
|
238
|
+
export function collapseFamilies(raw) {
|
|
239
|
+
const out = [];
|
|
240
|
+
for (const f of familiesOf(raw)) {
|
|
241
|
+
const levels = new Map();
|
|
242
|
+
for (const v of f.variants)
|
|
243
|
+
if (!levels.has(v.level))
|
|
244
|
+
levels.set(v.level, v.id);
|
|
245
|
+
const def = defaultVariant(f);
|
|
246
|
+
levels.set('', def.id);
|
|
247
|
+
wireIds.set(def.id, { levels, base: f.base });
|
|
248
|
+
const aliases = f.variants.map((v) => v.id).filter((id) => id !== def.id);
|
|
249
|
+
out.push({
|
|
250
|
+
// The row keeps an id the account listed, so a selection made from it stays
|
|
251
|
+
// dispatchable; the name is what the picker shows.
|
|
252
|
+
id: def.id,
|
|
253
|
+
name: f.name,
|
|
254
|
+
...aliases.length === 0 ? {} : { aliases },
|
|
255
|
+
contextWindow: Math.min(...f.variants.map((v) => v.contextWindow)),
|
|
256
|
+
inputModalities: def.inputModalities,
|
|
257
|
+
// Only the rungs this listing really serves: a level with no id of its own must
|
|
258
|
+
// not be offered as a choice the request would ignore.
|
|
259
|
+
reasoningEfforts: LEVEL_ORDER.filter((level) => levels.has(level)),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
75
264
|
/**
|
|
76
265
|
* The ids the agent can actually call, in the order the response groups them.
|
|
77
266
|
*
|
|
@@ -154,11 +343,11 @@ const NON_AGENT = /(?:^|[-_])(?:tab|tabs|commit|transcription|audio|embedding|co
|
|
|
154
343
|
/**
|
|
155
344
|
* Read the agent-callable catalog from a `fetchAvailableModels` response.
|
|
156
345
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
346
|
+
* The response's own grouping decides which ids the agent envelope may call; this
|
|
347
|
+
* removes the surfaces it cannot (tabs, commit messages, transcription, standalone
|
|
348
|
+
* image generation) and keeps the response's own order. Tiers of one family then
|
|
349
|
+
* collapse into the single picker row that serves them, with a wire-id map so the
|
|
350
|
+
* request still names an id the account listed (see `wireIdFor`).
|
|
162
351
|
*
|
|
163
352
|
* @param payload - the decoded response.
|
|
164
353
|
* @returns the catalog entries, in the response's order.
|
|
@@ -167,54 +356,21 @@ export function parseAntigravityModels(payload) {
|
|
|
167
356
|
if (!isRecord(payload) || !isRecord(payload.models))
|
|
168
357
|
return [];
|
|
169
358
|
const models = payload.models;
|
|
170
|
-
|
|
171
|
-
// inspectable after a discovery run, since the node half's stdout is unreachable in
|
|
172
|
-
// the desktop shell.
|
|
173
|
-
try {
|
|
174
|
-
const all = Object.keys(models);
|
|
175
|
-
const kept = agentIds(payload);
|
|
176
|
-
const { writeFileSync } = require('node:fs');
|
|
177
|
-
const { homedir } = require('node:os');
|
|
178
|
-
const { join } = require('node:path');
|
|
179
|
-
writeFileSync(join(homedir(), '.dsh', 'antigravity-models-debug.json'), JSON.stringify({ all, kept, dropped: all.filter((id) => !kept.includes(id)) }, null, 2), 'utf8');
|
|
180
|
-
}
|
|
181
|
-
catch { /* diagnostics never break parsing */ }
|
|
182
|
-
const entries = [];
|
|
183
|
-
const seenBase = new Set();
|
|
359
|
+
const raw = [];
|
|
184
360
|
for (const wireId of agentIds(payload)) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// fixed tiers; they fold into the base family row so the picker shows one entry with
|
|
188
|
-
// a ladder, not one row per tier. The base row is inserted the first time a tier of
|
|
189
|
-
// it is seen.
|
|
190
|
-
if (isTierMarked(wireId)) {
|
|
191
|
-
const base = baseFamilyOf(wireId);
|
|
192
|
-
if (seenBase.has(base))
|
|
193
|
-
continue;
|
|
194
|
-
seenBase.add(base);
|
|
195
|
-
const baseInfo = modelInfo(models[base]) ?? info;
|
|
196
|
-
entries.push({
|
|
197
|
-
id: base,
|
|
198
|
-
name: displayNameOf(base, models[base] ?? {}),
|
|
199
|
-
contextWindow: baseInfo.maxTokens ?? baseInfo.maxInputTokens ?? baseInfo.inputTokenLimit ?? 1_048_576,
|
|
200
|
-
inputModalities: baseInfo.supportsImages === true ? ['text', 'image'] : ['text'],
|
|
201
|
-
reasoningEfforts: antigravityEffortsFor(base),
|
|
202
|
-
});
|
|
361
|
+
// Image-generation models are never picker rows: they are agent-called, always on.
|
|
362
|
+
if (isImageModel(wireId))
|
|
203
363
|
continue;
|
|
204
|
-
|
|
205
|
-
seenBase.add(wireId);
|
|
206
|
-
const tiers = /-(?:low|medium|high)$/.test(wireId);
|
|
207
|
-
const efforts = tiers ? [] : antigravityEffortsFor(wireId);
|
|
364
|
+
const info = modelInfo(models[wireId]);
|
|
208
365
|
const contextWindow = info.maxTokens ?? info.maxInputTokens ?? info.inputTokenLimit ?? 1_048_576;
|
|
209
|
-
|
|
366
|
+
raw.push({
|
|
210
367
|
id: wireId,
|
|
211
368
|
name: displayNameOf(wireId, models[wireId] ?? {}),
|
|
212
369
|
contextWindow,
|
|
213
370
|
inputModalities: info.supportsImages === true ? ['text', 'image'] : ['text'],
|
|
214
|
-
...efforts.length === 0 ? {} : { reasoningEfforts: efforts },
|
|
215
371
|
});
|
|
216
372
|
}
|
|
217
|
-
return
|
|
373
|
+
return collapseFamilies(raw);
|
|
218
374
|
}
|
|
219
375
|
/** A quota reading normalised to a percentage spent. */
|
|
220
376
|
function usedPercentOf(raw) {
|
|
@@ -228,6 +228,8 @@ export declare class AntigravityAdapter extends LlmAdapter {
|
|
|
228
228
|
private resetAt;
|
|
229
229
|
/** Remember an account as spent (its exact reset is learned separately). */
|
|
230
230
|
private noteSpent;
|
|
231
|
+
/** Resolve the default account's token without the preempt window. */
|
|
232
|
+
private resolveNoPreempt;
|
|
231
233
|
/** Translate an auth failure into the message a user can act on. */
|
|
232
234
|
private refreshHint;
|
|
233
235
|
/** Build and send one streaming request for one model. */
|
|
@@ -20,7 +20,7 @@ import { ProviderController } from '../auth/provider-controller.js';
|
|
|
20
20
|
import { proxiedFetch } from '../http.js';
|
|
21
21
|
import { fallbackEntry, findEntry, resolveCatalog } from './catalog.js';
|
|
22
22
|
import { idleWatchdog, mapFetchFailure, httpLlmError } from './common.js';
|
|
23
|
-
import { parseAntigravityModelQuota, parseAntigravityModels, parseAntigravityQuotaSummary, } from './antigravity-catalog.js';
|
|
23
|
+
import { parseAntigravityModelQuota, parseAntigravityModels, parseAntigravityQuotaSummary, geminiTierId, wireIdFor, } from './antigravity-catalog.js';
|
|
24
24
|
import { toAntigravityRequest, streamAntigravity, antigravityReasoning } from '../translate/antigravity.js';
|
|
25
25
|
/** Route key this module registers. */
|
|
26
26
|
export const ANTIGRAVITY_PROVIDER_ID = 'antigravity';
|
|
@@ -456,7 +456,14 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
456
456
|
// `auto` owns the fallback chain now, so this route serves exactly the
|
|
457
457
|
// model it was asked for — but it draws on every account the provider holds, so a
|
|
458
458
|
// spent allowance moves to the next account rather than failing the call.
|
|
459
|
-
|
|
459
|
+
// A picker row is the family, not the wire id: `gemini-3.8-flash` is served as
|
|
460
|
+
// `gemini-3.8-flash-tiered` and `gpt-oss-120b` only exists as
|
|
461
|
+
// `gpt-oss-120b-medium`. The live listing's own mapping is the authority; the
|
|
462
|
+
// built-in tier names are the fallback for a cold start, before discovery ran.
|
|
463
|
+
const model = wireIdFor(options.model, options.reasoningEffort)
|
|
464
|
+
?? (/^gemini-.*-flash$/.test(options.model)
|
|
465
|
+
? geminiTierId(options.model, options.reasoningEffort ?? 'medium')
|
|
466
|
+
: options.model);
|
|
460
467
|
// Accounts come from the manager when it can list them; a minimal manager (or a
|
|
461
468
|
// test double) without `accounts()` means a single account, which is just the
|
|
462
469
|
// default one.
|
|
@@ -467,7 +474,10 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
467
474
|
// diagnosable from the browser where the node stdout is hidden.
|
|
468
475
|
const diag = [`pool=${accounts.join(',') || '(none)'}`];
|
|
469
476
|
const tried = new Set();
|
|
470
|
-
|
|
477
|
+
// Resolve without preempt: use the stored token if it is still within its life,
|
|
478
|
+
// refresh only when it has actually expired. The 5-minute preempt was causing
|
|
479
|
+
// a refresh on every stream, which made Google show the consent page.
|
|
480
|
+
let token = await this.resolveNoPreempt();
|
|
471
481
|
// Stay on the default account until it refuses. opencodex keeps account affinity —
|
|
472
482
|
// a request stays on one account and only moves after a 429 — because switching
|
|
473
483
|
// accounts on every request is what reads to Google as two accounts in play at once
|
|
@@ -571,6 +581,18 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
571
581
|
noteSpent(account) {
|
|
572
582
|
this.options.onWarn?.(`antigravity: account ${account} allowance spent; moving to another account`);
|
|
573
583
|
}
|
|
584
|
+
/** Resolve the default account's token without the preempt window. */
|
|
585
|
+
async resolveNoPreempt() {
|
|
586
|
+
const found = await this.options.tokens.session();
|
|
587
|
+
if (found === undefined) {
|
|
588
|
+
throw new ProviderError(`${ANTIGRAVITY_PROVIDER_NAME} no account is signed in`, REAUTH_REQUIRED);
|
|
589
|
+
}
|
|
590
|
+
const { account, session } = found;
|
|
591
|
+
if (Date.now() < session.expiresAt) {
|
|
592
|
+
return { account, accessToken: session.accessToken, expiresAt: session.expiresAt };
|
|
593
|
+
}
|
|
594
|
+
return await this.options.tokens.resolve(account, true);
|
|
595
|
+
}
|
|
574
596
|
/** Translate an auth failure into the message a user can act on. */
|
|
575
597
|
refreshHint(error) {
|
|
576
598
|
if (error instanceof ProviderError && error.code === REAUTH_REQUIRED) {
|
|
@@ -170,5 +170,9 @@ export function createArkProvider(options) {
|
|
|
170
170
|
removeModel: (id) => controller.removeModel(id),
|
|
171
171
|
usage: (force) => controller.usage(force),
|
|
172
172
|
reload: () => controller.reload(),
|
|
173
|
+
modelVariants: (id) => {
|
|
174
|
+
const image = adapter.imageModelId();
|
|
175
|
+
return image === undefined ? undefined : { image };
|
|
176
|
+
},
|
|
173
177
|
};
|
|
174
178
|
}
|
package/lib/providers/ark.d.ts
CHANGED
|
@@ -100,10 +100,14 @@ export interface ArkAdapterOptions {
|
|
|
100
100
|
export declare class ArkAdapter extends LlmAdapter {
|
|
101
101
|
private readonly options;
|
|
102
102
|
private live;
|
|
103
|
+
/** Image-generation model id, when discovery reported one. */
|
|
104
|
+
private imageModel;
|
|
103
105
|
constructor(options: ArkAdapterOptions);
|
|
104
106
|
providerInfo(provider: string): LlmProviderInfo;
|
|
105
107
|
providerRetryPolicy(): undefined;
|
|
106
108
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
109
|
+
/** The image-generation model id, when discovery reported one. */
|
|
110
|
+
imageModelId(): string | undefined;
|
|
107
111
|
resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
108
112
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
109
113
|
/** Translate an auth failure into the message a user can act on. */
|
package/lib/providers/ark.js
CHANGED
|
@@ -23,6 +23,9 @@ import { proxiedFetch } from '../http.js';
|
|
|
23
23
|
import { findEntry, resolveCatalog } from './catalog.js';
|
|
24
24
|
import { streamProviderResponses, accountPoolHooks } from './responses-stream.js';
|
|
25
25
|
import { toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
26
|
+
import { isImageModel } from './grok.js';
|
|
27
|
+
/** Model ids that are clearly not chat models. */
|
|
28
|
+
const NON_CHAT_ID = /(?:^|[-_/])(?:image|video|imagine|embed|embedding|whisper|tts|vision-only)/i;
|
|
26
29
|
/** Provider route this module serves. */
|
|
27
30
|
export const ARK_PROVIDER_ID = 'ark';
|
|
28
31
|
/** Display name for the picker group and the Settings card. */
|
|
@@ -113,6 +116,8 @@ function extraEntry(id) {
|
|
|
113
116
|
export class ArkAdapter extends LlmAdapter {
|
|
114
117
|
options;
|
|
115
118
|
live;
|
|
119
|
+
/** Image-generation model id, when discovery reported one. */
|
|
120
|
+
imageModel;
|
|
116
121
|
constructor(options) {
|
|
117
122
|
super();
|
|
118
123
|
this.options = options;
|
|
@@ -129,7 +134,9 @@ export class ArkAdapter extends LlmAdapter {
|
|
|
129
134
|
if (await this.options.tokens.session() === undefined)
|
|
130
135
|
return [];
|
|
131
136
|
const hidden = await this.hidden();
|
|
132
|
-
|
|
137
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
138
|
+
const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
|
|
139
|
+
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({
|
|
133
140
|
provider: ARK_PROVIDER_ID,
|
|
134
141
|
id: entry.id,
|
|
135
142
|
name: entry.name,
|
|
@@ -137,6 +144,10 @@ export class ArkAdapter extends LlmAdapter {
|
|
|
137
144
|
...entry.inputModalities === undefined ? {} : { inputModalities: entry.inputModalities },
|
|
138
145
|
}));
|
|
139
146
|
}
|
|
147
|
+
/** The image-generation model id, when discovery reported one. */
|
|
148
|
+
imageModelId() {
|
|
149
|
+
return this.imageModel;
|
|
150
|
+
}
|
|
140
151
|
async resolveModel(_provider, model) {
|
|
141
152
|
const entry = findEntry(this.catalog(), model);
|
|
142
153
|
if (entry === undefined) {
|
|
@@ -246,9 +257,12 @@ export class ArkAdapter extends LlmAdapter {
|
|
|
246
257
|
return undefined;
|
|
247
258
|
}
|
|
248
259
|
const body = await response.json();
|
|
249
|
-
const
|
|
260
|
+
const allIds = (body.data ?? [])
|
|
250
261
|
.map((entry) => (typeof entry?.id === 'string' ? entry.id.trim() : ''))
|
|
251
262
|
.filter((id) => id !== '');
|
|
263
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
264
|
+
this.imageModel = allIds.find((id) => isImageModel(id));
|
|
265
|
+
const ids = allIds.filter((id) => !NON_CHAT_ID.test(id));
|
|
252
266
|
return ids.length === 0 ? undefined : ids;
|
|
253
267
|
}
|
|
254
268
|
catch (error) {
|
|
@@ -166,5 +166,9 @@ export function createGlmProvider(options) {
|
|
|
166
166
|
removeModel: (id) => controller.removeModel(id),
|
|
167
167
|
usage: (force) => controller.usage(force),
|
|
168
168
|
reload: () => controller.reload(),
|
|
169
|
+
modelVariants: (id) => {
|
|
170
|
+
const image = adapter.imageModelId();
|
|
171
|
+
return image === undefined ? undefined : { image };
|
|
172
|
+
},
|
|
169
173
|
};
|
|
170
174
|
}
|
package/lib/providers/glm.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ export declare class GlmAdapter extends LlmAdapter {
|
|
|
120
120
|
private readonly options;
|
|
121
121
|
/** Live catalog ids, once discovery succeeded this session. */
|
|
122
122
|
private live;
|
|
123
|
+
/** Image-generation model id, when discovery reported one. */
|
|
124
|
+
private imageModel;
|
|
123
125
|
constructor(options: GlmAdapterOptions);
|
|
124
126
|
providerInfo(provider: string): LlmProviderInfo;
|
|
125
127
|
providerRetryPolicy(): undefined;
|
|
@@ -134,6 +136,8 @@ export declare class GlmAdapter extends LlmAdapter {
|
|
|
134
136
|
private shippedCatalog;
|
|
135
137
|
/** Ids the user published by hand that this route does not already serve. */
|
|
136
138
|
private addedEntries;
|
|
139
|
+
/** The image-generation model id, when discovery reported one. */
|
|
140
|
+
imageModelId(): string | undefined;
|
|
137
141
|
/** The ids the picker must currently omit. */
|
|
138
142
|
private hidden;
|
|
139
143
|
/**
|
package/lib/providers/glm.js
CHANGED
|
@@ -15,6 +15,9 @@ import { proxiedFetch } from '../http.js';
|
|
|
15
15
|
import { findEntry, resolveCatalog } from './catalog.js';
|
|
16
16
|
import { streamProviderResponses, accountPoolHooks } from './responses-stream.js';
|
|
17
17
|
import { toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
18
|
+
import { isImageModel } from './grok.js';
|
|
19
|
+
/** Model ids that are clearly not chat models. */
|
|
20
|
+
const NON_CHAT_ID = /(?:^|[-_/])(?:image|video|imagine|embed|embedding|whisper|tts|vision-only)/i;
|
|
18
21
|
/** Provider route this module serves. */
|
|
19
22
|
export const GLM_PROVIDER_ID = 'glm';
|
|
20
23
|
/** Display name for the picker group and the Settings card. */
|
|
@@ -220,6 +223,8 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
220
223
|
options;
|
|
221
224
|
/** Live catalog ids, once discovery succeeded this session. */
|
|
222
225
|
live;
|
|
226
|
+
/** Image-generation model id, when discovery reported one. */
|
|
227
|
+
imageModel;
|
|
223
228
|
constructor(options) {
|
|
224
229
|
super();
|
|
225
230
|
this.options = options;
|
|
@@ -238,7 +243,9 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
238
243
|
if (await this.options.tokens.session() === undefined)
|
|
239
244
|
return [];
|
|
240
245
|
const hidden = await this.hidden();
|
|
241
|
-
|
|
246
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
247
|
+
const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
|
|
248
|
+
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({
|
|
242
249
|
provider: GLM_PROVIDER_ID,
|
|
243
250
|
id: entry.id,
|
|
244
251
|
name: entry.name,
|
|
@@ -326,6 +333,10 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
326
333
|
.filter((id) => findEntry(own, id) === undefined)
|
|
327
334
|
.map((id) => extraEntry(id));
|
|
328
335
|
}
|
|
336
|
+
/** The image-generation model id, when discovery reported one. */
|
|
337
|
+
imageModelId() {
|
|
338
|
+
return this.imageModel;
|
|
339
|
+
}
|
|
329
340
|
/** The ids the picker must currently omit. */
|
|
330
341
|
async hidden() {
|
|
331
342
|
if (this.options.hiddenModels === undefined)
|
|
@@ -367,9 +378,12 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
367
378
|
return undefined;
|
|
368
379
|
}
|
|
369
380
|
const body = await response.json();
|
|
370
|
-
const
|
|
381
|
+
const allIds = (body.data ?? [])
|
|
371
382
|
.map((entry) => (typeof entry?.id === 'string' ? entry.id.trim() : ''))
|
|
372
383
|
.filter((id) => id !== '');
|
|
384
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
385
|
+
this.imageModel = allIds.find((id) => isImageModel(id));
|
|
386
|
+
const ids = allIds.filter((id) => !NON_CHAT_ID.test(id));
|
|
373
387
|
return ids.length === 0 ? undefined : ids;
|
|
374
388
|
}
|
|
375
389
|
catch (error) {
|