dsh-plugin-subscription-oauth 0.16.2 → 0.17.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/README.dev.md +1 -1
- package/README.md +7 -6
- package/README.zh.dev.md +1 -1
- package/README.zh.md +7 -6
- package/lib/auth/workbuddy-flow.d.ts +1 -1
- package/lib/auth/workbuddy-flow.js +14 -9
- package/lib/client/index.js +10 -1
- package/lib/client.js +6 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +21 -0
- package/lib/providers/antigravity.d.ts +34 -4
- package/lib/providers/antigravity.js +100 -28
- package/lib/providers/ark-provider.d.ts +10 -0
- package/lib/providers/ark-provider.js +6 -0
- package/lib/providers/ark.d.ts +96 -10
- package/lib/providers/ark.js +176 -48
- package/lib/providers/catalog.d.ts +91 -0
- package/lib/providers/catalog.js +190 -0
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +23 -0
- package/lib/providers/glm-provider.d.ts +10 -0
- package/lib/providers/glm-provider.js +6 -0
- package/lib/providers/glm.d.ts +92 -16
- package/lib/providers/glm.js +172 -85
- package/lib/providers/grok.d.ts +106 -27
- package/lib/providers/grok.js +312 -128
- package/lib/providers/kimi-provider.d.ts +10 -0
- package/lib/providers/kimi-provider.js +8 -3
- package/lib/providers/kimi.d.ts +104 -32
- package/lib/providers/kimi.js +198 -129
- package/lib/providers/mimo-provider.d.ts +14 -1
- package/lib/providers/mimo-provider.js +17 -6
- package/lib/providers/mimo.d.ts +164 -19
- package/lib/providers/mimo.js +318 -19
- package/lib/providers/model-cache.d.ts +57 -0
- package/lib/providers/model-cache.js +105 -0
- package/lib/providers/responses-stream.d.ts +8 -0
- package/lib/providers/responses-stream.js +21 -5
- package/lib/providers/workbuddy-provider.d.ts +17 -0
- package/lib/providers/workbuddy-provider.js +14 -6
- package/lib/providers/workbuddy.d.ts +140 -13
- package/lib/providers/workbuddy.js +520 -55
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { createArkProvider } from './providers/ark-provider.js';
|
|
|
25
25
|
import { createMimoProvider } from './providers/mimo-provider.js';
|
|
26
26
|
import { createWorkBuddyProvider } from './providers/workbuddy-provider.js';
|
|
27
27
|
import { ModelPreferences } from './settings.js';
|
|
28
|
+
import { ModelCatalogCache } from './providers/model-cache.js';
|
|
28
29
|
/** Plugin id; also the name every effect label and log line is prefixed with. */
|
|
29
30
|
export const name = 'dsh-plugin-subscription-oauth';
|
|
30
31
|
/** The provider routes need the LLM runtime; everything else is optional. */
|
|
@@ -68,6 +69,10 @@ export function apply(ctx, config = {}) {
|
|
|
68
69
|
const dataDir = dshHomePath(...KIMI_DATA_DIR);
|
|
69
70
|
const credentialFile = path.join(dataDir, 'auth.json');
|
|
70
71
|
const preferences = new ModelPreferences(path.join(dataDir, 'preferences.json'));
|
|
72
|
+
// The endpoint's own last answer per route. It is written whenever a route reads its
|
|
73
|
+
// live listing — at startup, after a login, and on the page's "refresh models" action
|
|
74
|
+
// — and read as the route's model table, so a picker query is never a request.
|
|
75
|
+
const catalogCache = new ModelCatalogCache(path.join(dataDir, 'model-catalog.json'));
|
|
71
76
|
/** Registration handles, so a catalog change can re-announce one route. */
|
|
72
77
|
const handles = new Map();
|
|
73
78
|
const reannounce = (id) => { handles.get(id)?.replace([id]); };
|
|
@@ -84,6 +89,8 @@ export function apply(ctx, config = {}) {
|
|
|
84
89
|
streamIdleTimeoutMs,
|
|
85
90
|
...config.defaultModel === undefined ? {} : { defaultModel: config.defaultModel },
|
|
86
91
|
...config.extraModels === undefined ? {} : { extraModels: config.extraModels },
|
|
92
|
+
cachedListing: () => catalogCache.read('kimi'),
|
|
93
|
+
saveListing: (rows) => catalogCache.write('kimi', rows),
|
|
87
94
|
preferences,
|
|
88
95
|
// Resolved per request: the attachment service may start after this plugin.
|
|
89
96
|
resolveAttachments: () => ctx.get('attachments'),
|
|
@@ -98,6 +105,8 @@ export function apply(ctx, config = {}) {
|
|
|
98
105
|
discovery: config.discovery ?? true,
|
|
99
106
|
streamIdleTimeoutMs,
|
|
100
107
|
preferences,
|
|
108
|
+
cachedListing: () => catalogCache.read('grok'),
|
|
109
|
+
saveListing: (payload) => catalogCache.write('grok', payload),
|
|
101
110
|
resolveAttachments: () => ctx.get('attachments'),
|
|
102
111
|
onCatalogChanged: () => { reannounce('grok'); },
|
|
103
112
|
onWarn,
|
|
@@ -113,6 +122,8 @@ export function apply(ctx, config = {}) {
|
|
|
113
122
|
resolveAttachments: () => ctx.get('attachments'),
|
|
114
123
|
onCatalogChanged: () => { reannounce('antigravity'); },
|
|
115
124
|
onWarn,
|
|
125
|
+
cachedListing: () => catalogCache.read('antigravity'),
|
|
126
|
+
saveListing: (payload) => catalogCache.write('antigravity', payload),
|
|
116
127
|
fetchFn: proxiedFetch,
|
|
117
128
|
}));
|
|
118
129
|
break;
|
|
@@ -122,6 +133,8 @@ export function apply(ctx, config = {}) {
|
|
|
122
133
|
discovery: config.discovery ?? true,
|
|
123
134
|
streamIdleTimeoutMs,
|
|
124
135
|
preferences,
|
|
136
|
+
cachedListing: () => catalogCache.read('glm'),
|
|
137
|
+
saveListing: (payload) => catalogCache.write('glm', payload),
|
|
125
138
|
resolveAttachments: () => ctx.get('attachments'),
|
|
126
139
|
onCatalogChanged: () => { reannounce('glm'); },
|
|
127
140
|
onWarn,
|
|
@@ -134,6 +147,8 @@ export function apply(ctx, config = {}) {
|
|
|
134
147
|
discovery: config.discovery ?? true,
|
|
135
148
|
streamIdleTimeoutMs,
|
|
136
149
|
preferences,
|
|
150
|
+
cachedListing: () => catalogCache.read('ark'),
|
|
151
|
+
saveListing: (payload) => catalogCache.write('ark', payload),
|
|
137
152
|
resolveAttachments: () => ctx.get('attachments'),
|
|
138
153
|
onCatalogChanged: () => { reannounce('ark'); },
|
|
139
154
|
onWarn,
|
|
@@ -143,8 +158,11 @@ export function apply(ctx, config = {}) {
|
|
|
143
158
|
case 'mimo':
|
|
144
159
|
routes.push(createMimoProvider({
|
|
145
160
|
credentialFile,
|
|
161
|
+
discovery: config.discovery ?? true,
|
|
146
162
|
streamIdleTimeoutMs,
|
|
147
163
|
preferences,
|
|
164
|
+
cachedListing: () => catalogCache.read('mimo'),
|
|
165
|
+
saveListing: (payload) => catalogCache.write('mimo', payload),
|
|
148
166
|
resolveAttachments: () => ctx.get('attachments'),
|
|
149
167
|
onCatalogChanged: () => { reannounce('mimo'); },
|
|
150
168
|
onWarn,
|
|
@@ -154,8 +172,11 @@ export function apply(ctx, config = {}) {
|
|
|
154
172
|
case 'workbuddy':
|
|
155
173
|
routes.push(createWorkBuddyProvider({
|
|
156
174
|
credentialFile,
|
|
175
|
+
discovery: config.discovery ?? true,
|
|
157
176
|
streamIdleTimeoutMs,
|
|
158
177
|
preferences,
|
|
178
|
+
cachedListing: () => catalogCache.read('workbuddy'),
|
|
179
|
+
saveListing: (payload) => catalogCache.write('workbuddy', payload),
|
|
159
180
|
onCatalogChanged: () => { reannounce('workbuddy'); },
|
|
160
181
|
onWarn,
|
|
161
182
|
fetchFn: proxiedFetch,
|
|
@@ -195,6 +195,17 @@ export interface AntigravityAdapterOptions {
|
|
|
195
195
|
modelOrder?: () => Promise<readonly string[]>;
|
|
196
196
|
fetchFn?: FetchFn;
|
|
197
197
|
onCredentialRejected?: (account: string) => void;
|
|
198
|
+
/**
|
|
199
|
+
* The endpoint's last live listing, from this plugin's cache.
|
|
200
|
+
*
|
|
201
|
+
* Read once per session, before the first catalog query, so the picker offers real
|
|
202
|
+
* models without asking the endpoint anything. A route that fetched once and cached
|
|
203
|
+
* the answer has no reason to fetch again on every refresh; the Settings "refresh
|
|
204
|
+
* models" action is what re-reads it.
|
|
205
|
+
*/
|
|
206
|
+
cachedListing?: () => Promise<unknown>;
|
|
207
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
208
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
198
209
|
}
|
|
199
210
|
/** Antigravity wire adapter serving the `antigravity` route. */
|
|
200
211
|
export declare class AntigravityAdapter extends LlmAdapter {
|
|
@@ -203,6 +214,8 @@ export declare class AntigravityAdapter extends LlmAdapter {
|
|
|
203
214
|
private discovered;
|
|
204
215
|
/** Live catalog entries, so the Settings list matches what the account has. */
|
|
205
216
|
private live;
|
|
217
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
218
|
+
private hydrated;
|
|
206
219
|
/** Per-model quota windows from the last catalog read. */
|
|
207
220
|
private quotas;
|
|
208
221
|
/** Ids the user added by hand, cached so the sync catalog can name them. */
|
|
@@ -213,12 +226,20 @@ export declare class AntigravityAdapter extends LlmAdapter {
|
|
|
213
226
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
214
227
|
private hidden;
|
|
215
228
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* the
|
|
229
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
230
|
+
*
|
|
231
|
+
* The cache is this route's last live catalog — the entries the endpoint reported, which
|
|
232
|
+
* is also what a fresh read produces — so adopting it is indistinguishable from having
|
|
233
|
+
* just fetched. A read failure is a cache miss, never a failure.
|
|
219
234
|
*/
|
|
235
|
+
hydrate(): Promise<void>;
|
|
236
|
+
/** Publish one listing as this route's catalog. */
|
|
237
|
+
private adopt;
|
|
238
|
+
/** The catalog the route serves: its own entries, then the ids the user added
|
|
239
|
+
* by hand. The two are built separately so the hand-added check can look at
|
|
240
|
+
* the shipped list without recursing. */
|
|
220
241
|
private catalog;
|
|
221
|
-
/**
|
|
242
|
+
/** The live entries: the cached listing until a fresh one replaces it. */
|
|
222
243
|
private shippedCatalog;
|
|
223
244
|
resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
224
245
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
@@ -332,6 +353,15 @@ export interface AntigravityProviderOptions {
|
|
|
332
353
|
fetchFn?: FetchFn;
|
|
333
354
|
/** Activate an eligible account when `loadCodeAssist` reports no project. */
|
|
334
355
|
onboard?: boolean;
|
|
356
|
+
/**
|
|
357
|
+
* The endpoint's last live catalog, from this plugin's cache.
|
|
358
|
+
*
|
|
359
|
+
* This route has no curated list — what an account may use is plan-dependent — so the
|
|
360
|
+
* cache is what makes a cold start (or an unreachable endpoint) still show models.
|
|
361
|
+
*/
|
|
362
|
+
cachedListing?: () => Promise<unknown>;
|
|
363
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
364
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
335
365
|
}
|
|
336
366
|
/**
|
|
337
367
|
* Build the `antigravity` route: its adapter and its login/lifecycle controller.
|
|
@@ -329,16 +329,6 @@ export async function fetchAntigravityModels(input) {
|
|
|
329
329
|
if (typeof models !== 'object' || models === null) {
|
|
330
330
|
throw new LlmError('Antigravity models endpoint returned no models object', 'MALFORMED_RESPONSE');
|
|
331
331
|
}
|
|
332
|
-
// TEMP diagnostics: write the raw model-id list to a file so an over-eager filter is
|
|
333
|
-
// inspectable after a discovery run, since the node half's stdout is unreachable in
|
|
334
|
-
// the desktop shell.
|
|
335
|
-
try {
|
|
336
|
-
const { writeFileSync } = require('node:fs');
|
|
337
|
-
const { homedir } = require('node:os');
|
|
338
|
-
const { join } = require('node:path');
|
|
339
|
-
writeFileSync(join(homedir(), '.dsh', 'antigravity-raw-models.json'), JSON.stringify(Object.keys(models), null, 2), 'utf8');
|
|
340
|
-
}
|
|
341
|
-
catch { /* diagnostics never break parsing */ }
|
|
342
332
|
// The response names far more than the agent may call; the filter keeps the
|
|
343
333
|
// callable grouping and reports the per-model quota for the fallback path.
|
|
344
334
|
const entries = parseAntigravityModels(payload);
|
|
@@ -365,6 +355,33 @@ function quotaWindow(scope, quota) {
|
|
|
365
355
|
...Number.isFinite(resetsAt) ? { resetsAt } : {},
|
|
366
356
|
};
|
|
367
357
|
}
|
|
358
|
+
/** Whether one cached value is a usable catalog entry. */
|
|
359
|
+
function isCachedEntry(value) {
|
|
360
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
361
|
+
return false;
|
|
362
|
+
const record = value;
|
|
363
|
+
return typeof record['id'] === 'string' && record['id'] !== ''
|
|
364
|
+
&& typeof record['name'] === 'string'
|
|
365
|
+
&& typeof record['contextWindow'] === 'number' && Number.isFinite(record['contextWindow']);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Read a cached catalog payload.
|
|
369
|
+
*
|
|
370
|
+
* The route writes the entries themselves; a whole `{ entries: [...] }` document is
|
|
371
|
+
* accepted too, so a payload written in either shape cannot silently empty the picker.
|
|
372
|
+
* Anything unusable is dropped rather than trusted.
|
|
373
|
+
*
|
|
374
|
+
* @param payload - whatever the cache returned.
|
|
375
|
+
* @returns the usable entries, possibly none.
|
|
376
|
+
*/
|
|
377
|
+
function cachedEntries(payload) {
|
|
378
|
+
const rows = Array.isArray(payload)
|
|
379
|
+
? payload
|
|
380
|
+
: typeof payload === 'object' && payload !== null && Array.isArray(payload.entries)
|
|
381
|
+
? payload.entries
|
|
382
|
+
: [];
|
|
383
|
+
return rows.filter(isCachedEntry);
|
|
384
|
+
}
|
|
368
385
|
/** Antigravity wire adapter serving the `antigravity` route. */
|
|
369
386
|
export class AntigravityAdapter extends LlmAdapter {
|
|
370
387
|
options;
|
|
@@ -372,6 +389,8 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
372
389
|
discovered;
|
|
373
390
|
/** Live catalog entries, so the Settings list matches what the account has. */
|
|
374
391
|
live;
|
|
392
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
393
|
+
hydrated = false;
|
|
375
394
|
/** Per-model quota windows from the last catalog read. */
|
|
376
395
|
quotas = new Map();
|
|
377
396
|
/** Ids the user added by hand, cached so the sync catalog can name them. */
|
|
@@ -392,6 +411,7 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
392
411
|
// must not appear in the picker, and Auto must not try it.
|
|
393
412
|
if (await this.options.tokens.session() === undefined)
|
|
394
413
|
return [];
|
|
414
|
+
await this.hydrate();
|
|
395
415
|
await this.syncUserModels();
|
|
396
416
|
const hidden = await this.hidden();
|
|
397
417
|
return resolveCatalog(this.catalog(), [], hidden).entries.map((entry) => ({
|
|
@@ -414,18 +434,44 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
414
434
|
}
|
|
415
435
|
}
|
|
416
436
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* the
|
|
437
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
438
|
+
*
|
|
439
|
+
* The cache is this route's last live catalog — the entries the endpoint reported, which
|
|
440
|
+
* is also what a fresh read produces — so adopting it is indistinguishable from having
|
|
441
|
+
* just fetched. A read failure is a cache miss, never a failure.
|
|
420
442
|
*/
|
|
443
|
+
async hydrate() {
|
|
444
|
+
if (this.hydrated)
|
|
445
|
+
return;
|
|
446
|
+
this.hydrated = true;
|
|
447
|
+
if (this.live !== undefined || this.options.cachedListing === undefined)
|
|
448
|
+
return;
|
|
449
|
+
try {
|
|
450
|
+
const entries = cachedEntries(await this.options.cachedListing());
|
|
451
|
+
if (entries.length > 0)
|
|
452
|
+
this.adopt(entries);
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
this.options.onWarn?.(`cached model catalog unavailable (${errorChain(error)})`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/** Publish one listing as this route's catalog. */
|
|
459
|
+
adopt(entries) {
|
|
460
|
+
this.live = entries;
|
|
461
|
+
this.discovered = entries.map((entry) => entry.id);
|
|
462
|
+
}
|
|
463
|
+
/** The catalog the route serves: its own entries, then the ids the user added
|
|
464
|
+
* by hand. The two are built separately so the hand-added check can look at
|
|
465
|
+
* the shipped list without recursing. */
|
|
421
466
|
catalog() {
|
|
422
467
|
return [...this.shippedCatalog(), ...this.addedEntries()];
|
|
423
468
|
}
|
|
424
|
-
/**
|
|
469
|
+
/** The live entries: the cached listing until a fresh one replaces it. */
|
|
425
470
|
shippedCatalog() {
|
|
426
471
|
return this.live ?? [];
|
|
427
472
|
}
|
|
428
473
|
async resolveModel(_provider, model) {
|
|
474
|
+
await this.hydrate();
|
|
429
475
|
await this.syncUserModels();
|
|
430
476
|
const entries = this.catalog();
|
|
431
477
|
const entry = findEntry(entries, model);
|
|
@@ -591,7 +637,13 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
591
637
|
if (Date.now() < session.expiresAt) {
|
|
592
638
|
return { account, accessToken: session.accessToken, expiresAt: session.expiresAt };
|
|
593
639
|
}
|
|
594
|
-
|
|
640
|
+
try {
|
|
641
|
+
const resolved = await this.options.tokens.resolve(account, true);
|
|
642
|
+
return resolved;
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
595
647
|
}
|
|
596
648
|
/** Translate an auth failure into the message a user can act on. */
|
|
597
649
|
refreshHint(error) {
|
|
@@ -611,17 +663,23 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
611
663
|
const envelope = await toAntigravityRequest({ ...options, model }, projectId, options.system, this.imageResolver(signal), true);
|
|
612
664
|
const fetchFn = this.options.fetchFn ?? proxiedFetch;
|
|
613
665
|
const origin = this.options.baseUrl ?? ANTIGRAVITY_BASE_URL;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
666
|
+
try {
|
|
667
|
+
const response = await fetchFn(`${origin}/v1internal:streamGenerateContent?alt=sse`, {
|
|
668
|
+
method: 'POST',
|
|
669
|
+
headers: attributedHeaders({
|
|
670
|
+
'authorization': `Bearer ${accessToken}`,
|
|
671
|
+
'accept': 'text/event-stream',
|
|
672
|
+
'content-type': 'application/json',
|
|
673
|
+
'user-agent': ANTIGRAVITY_USER_AGENT,
|
|
674
|
+
}),
|
|
675
|
+
body: JSON.stringify(envelope),
|
|
676
|
+
signal,
|
|
677
|
+
});
|
|
678
|
+
return response;
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
625
683
|
}
|
|
626
684
|
/** Resolve image blocks through the attachment service, when it is present. */
|
|
627
685
|
imageResolver(signal) {
|
|
@@ -664,8 +722,17 @@ export class AntigravityAdapter extends LlmAdapter {
|
|
|
664
722
|
...signal === undefined ? {} : { signal },
|
|
665
723
|
});
|
|
666
724
|
this.quotas = quotas;
|
|
667
|
-
this.
|
|
668
|
-
|
|
725
|
+
this.adopt(entries);
|
|
726
|
+
// The catalog the endpoint just reported is what the next session starts from, so
|
|
727
|
+
// the picker does not have to ask again. A cache failure never fails this read.
|
|
728
|
+
if (this.options.saveListing !== undefined) {
|
|
729
|
+
try {
|
|
730
|
+
await this.options.saveListing(entries);
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
this.options.onWarn?.(`model catalog cache write failed (${errorChain(error)})`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
669
736
|
return this.discovered;
|
|
670
737
|
}
|
|
671
738
|
catch (error) {
|
|
@@ -911,8 +978,13 @@ export function createAntigravityProvider(options) {
|
|
|
911
978
|
hiddenModels: () => options.preferences.hiddenIds(ANTIGRAVITY_PROVIDER_ID),
|
|
912
979
|
userModels: () => options.preferences.extraIds(ANTIGRAVITY_PROVIDER_ID),
|
|
913
980
|
modelOrder: () => options.preferences.orderOf(ANTIGRAVITY_PROVIDER_ID),
|
|
981
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
982
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
914
983
|
fetchFn,
|
|
915
984
|
});
|
|
985
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
986
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
987
|
+
void adapter.hydrate().catch(() => undefined);
|
|
916
988
|
const spec = {
|
|
917
989
|
id: ANTIGRAVITY_PROVIDER_ID,
|
|
918
990
|
displayName: ANTIGRAVITY_PROVIDER_NAME,
|
|
@@ -27,6 +27,16 @@ export interface ArkProviderOptions {
|
|
|
27
27
|
streamIdleTimeoutMs: number;
|
|
28
28
|
defaultModel?: string;
|
|
29
29
|
extraModels?: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* The endpoint's last live listing, from this plugin's cache.
|
|
32
|
+
*
|
|
33
|
+
* The cached table is what the picker shows before any request: the endpoint's own
|
|
34
|
+
* answer from the last read, so a release does not need a plugin update and a refresh
|
|
35
|
+
* does not need a request.
|
|
36
|
+
*/
|
|
37
|
+
cachedListing?: () => Promise<unknown>;
|
|
38
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
39
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
30
40
|
preferences: ModelPreferences;
|
|
31
41
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
32
42
|
onCatalogChanged?: () => void;
|
|
@@ -113,8 +113,14 @@ export function createArkProvider(options) {
|
|
|
113
113
|
hiddenModels: () => options.preferences.hiddenIds(ARK_PROVIDER_ID),
|
|
114
114
|
userModels: () => options.preferences.extraIds(ARK_PROVIDER_ID),
|
|
115
115
|
...options.extraModels === undefined ? {} : { extraModels: options.extraModels },
|
|
116
|
+
...options.defaultModel === undefined ? {} : { defaultModel: options.defaultModel },
|
|
117
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
118
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
116
119
|
fetchFn,
|
|
117
120
|
});
|
|
121
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
122
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
123
|
+
void adapter.hydrate().catch(() => undefined);
|
|
118
124
|
const spec = {
|
|
119
125
|
id: ARK_PROVIDER_ID,
|
|
120
126
|
displayName: ARK_PROVIDER_NAME,
|
package/lib/providers/ark.d.ts
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
* malformed one is told so (`401 The API key format is incorrect`). So the
|
|
12
12
|
* credential is a UUID key, sent as a plain bearer — no request signing.
|
|
13
13
|
* - `/api/coding/v3` is documented by the official CLI as "OpenTOP + CodingPlan
|
|
14
|
-
* API", and
|
|
14
|
+
* API", and its `/models` listing — read once and kept in this plugin's cache, see
|
|
15
|
+
* `hydrate` and `fetchCatalog` — is the roster this route publishes. Nothing in this
|
|
16
|
+
* module decides which models the plan serves, so one released, renamed or retired
|
|
17
|
+
* server-side reaches the picker without a plugin update.
|
|
15
18
|
*
|
|
16
19
|
* @module dsh-plugin-subscription-oauth/providers/ark
|
|
17
20
|
*/
|
|
@@ -42,14 +45,12 @@ export declare const ARK_DEFAULT_MODEL = "ark-code-latest";
|
|
|
42
45
|
/**
|
|
43
46
|
* Context capacity advertised for the plan's models.
|
|
44
47
|
*
|
|
45
|
-
* The
|
|
46
|
-
* discovery refines this where
|
|
48
|
+
* The plan states no window in this build; the endpoint's own listing supplies one
|
|
49
|
+
* where it names a model, and discovery refines this where it does not.
|
|
47
50
|
*/
|
|
48
51
|
export declare const ARK_CONTEXT_WINDOW = 262144;
|
|
49
52
|
/** Output budget used when the caller sets no cap. */
|
|
50
53
|
export declare const ARK_DEFAULT_MAX_TOKENS = 32000;
|
|
51
|
-
/** The plan's model roster, as the reference registry records it. */
|
|
52
|
-
export declare const ARK_MODELS: readonly CatalogEntry[];
|
|
53
54
|
/** One signed-in Ark account. */
|
|
54
55
|
export interface ArkSession extends SessionBase {
|
|
55
56
|
/**
|
|
@@ -89,6 +90,25 @@ export interface ArkAdapterOptions {
|
|
|
89
90
|
hiddenModels?: () => Promise<ReadonlySet<string>> | ReadonlySet<string>;
|
|
90
91
|
userModels?: () => Promise<readonly string[]> | readonly string[];
|
|
91
92
|
extraModels?: readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* Model this route preselects, and therefore always offers.
|
|
95
|
+
*
|
|
96
|
+
* Configuration rather than a shipped fact: the plan re-points its alias at a new
|
|
97
|
+
* release on its own schedule, and the picker must be able to preselect it. Defaults
|
|
98
|
+
* to {@link ARK_DEFAULT_MODEL}.
|
|
99
|
+
*/
|
|
100
|
+
defaultModel?: string;
|
|
101
|
+
/**
|
|
102
|
+
* The endpoint's last live listing, from this plugin's cache.
|
|
103
|
+
*
|
|
104
|
+
* Read once per session, before the first catalog query, so the picker offers real
|
|
105
|
+
* models without asking the endpoint anything. A route that fetched once and cached
|
|
106
|
+
* the answer has no reason to fetch again on every refresh; the Settings "refresh
|
|
107
|
+
* models" action is what re-reads it.
|
|
108
|
+
*/
|
|
109
|
+
cachedListing?: () => Promise<unknown>;
|
|
110
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
111
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
92
112
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
93
113
|
onWarn?: (message: string) => void;
|
|
94
114
|
onCredentialRejected?: (account: string) => void;
|
|
@@ -99,10 +119,45 @@ export interface ArkAdapterOptions {
|
|
|
99
119
|
/** The adapter behind the `ark` route. */
|
|
100
120
|
export declare class ArkAdapter extends LlmAdapter {
|
|
101
121
|
private readonly options;
|
|
122
|
+
/**
|
|
123
|
+
* The catalog this route serves: entries derived from the endpoint's own listing,
|
|
124
|
+
* either the one just read or the one the cache kept from the last read.
|
|
125
|
+
*
|
|
126
|
+
* Undefined means neither source has produced a listing yet, so only the configured
|
|
127
|
+
* default model is offered until one does.
|
|
128
|
+
*/
|
|
102
129
|
private live;
|
|
103
|
-
/**
|
|
130
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
131
|
+
private hydrated;
|
|
132
|
+
/** Image-generation model id, when the listing reported one. */
|
|
104
133
|
private imageModel;
|
|
134
|
+
/**
|
|
135
|
+
* Fast-variant wire id per picker row.
|
|
136
|
+
*
|
|
137
|
+
* Derived from the listing rather than a curated alias map: a row whose name is a
|
|
138
|
+
* listed model plus the provider's own fast word rides that row instead of becoming
|
|
139
|
+
* one of its own.
|
|
140
|
+
*/
|
|
141
|
+
private readonly fastVariants;
|
|
105
142
|
constructor(options: ArkAdapterOptions);
|
|
143
|
+
/**
|
|
144
|
+
* Adopt the rows of one live listing as this route's catalog.
|
|
145
|
+
*
|
|
146
|
+
* Everything the picker shows is derived here from the endpoint's own answer: the
|
|
147
|
+
* rows that become picker entries and the fast variant that pairs with each row.
|
|
148
|
+
* Nothing about which models exist, or what each one is, is decided by this build.
|
|
149
|
+
*
|
|
150
|
+
* @param rows - decoded rows of the endpoint's listing, in the endpoint's order.
|
|
151
|
+
* @returns the entries to publish, in picker order.
|
|
152
|
+
*/
|
|
153
|
+
private adoptListing;
|
|
154
|
+
/**
|
|
155
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
156
|
+
*
|
|
157
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
158
|
+
* failure: the route simply has no listing until one is fetched.
|
|
159
|
+
*/
|
|
160
|
+
hydrate(): Promise<void>;
|
|
106
161
|
providerInfo(provider: string): LlmProviderInfo;
|
|
107
162
|
providerRetryPolicy(): undefined;
|
|
108
163
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
@@ -113,18 +168,49 @@ export declare class ArkAdapter extends LlmAdapter {
|
|
|
113
168
|
/** Translate an auth failure into the message a user can act on. */
|
|
114
169
|
private refreshHint;
|
|
115
170
|
private catalog;
|
|
171
|
+
/**
|
|
172
|
+
* The catalog to present: the models the endpoint's listing named, in its own order,
|
|
173
|
+
* each with the metadata that listing stated, then the configured extras.
|
|
174
|
+
*
|
|
175
|
+
* There is no shipped roster. A listing is read once and cached (see {@link hydrate}
|
|
176
|
+
* and {@link fetchCatalog}), and that cached table is what the picker shows until the
|
|
177
|
+
* next read, so a model released, renamed or retired server-side is reflected without
|
|
178
|
+
* a plugin update. Only the configured default is always offered, because the picker
|
|
179
|
+
* preselects it.
|
|
180
|
+
*/
|
|
116
181
|
private shippedCatalog;
|
|
182
|
+
/** The model the picker preselects, from configuration. */
|
|
183
|
+
private defaultModel;
|
|
117
184
|
private addedEntries;
|
|
118
185
|
private hidden;
|
|
119
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
188
|
+
*
|
|
189
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
190
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
191
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
192
|
+
* for them.
|
|
193
|
+
*
|
|
194
|
+
* @param ids - model ids the caller reports.
|
|
195
|
+
*/
|
|
120
196
|
discover(ids: readonly string[]): void;
|
|
121
197
|
/**
|
|
122
|
-
* Read the plan's live model list.
|
|
198
|
+
* Read the plan's live model list and adopt it as this route's catalog.
|
|
199
|
+
*
|
|
200
|
+
* The Coding Plan answers the OpenAI models shape on its own base, so the rows are
|
|
201
|
+
* parsed exactly as the other routes parse theirs: what the listing names is what the
|
|
202
|
+
* picker offers, with the display name, window, modalities and ladder it states. The
|
|
203
|
+
* rows are also handed to {@link ArkAdapterOptions.saveListing}, so the next session
|
|
204
|
+
* starts from this table instead of asking again — the Settings "refresh models"
|
|
205
|
+
* action is what re-reads it.
|
|
123
206
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
207
|
+
* @param apiKey - bearer for the account.
|
|
208
|
+
* @param signal - optional cancellation.
|
|
209
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
126
210
|
*/
|
|
127
211
|
fetchCatalog(apiKey: string, signal?: AbortSignal): Promise<readonly string[] | undefined>;
|
|
212
|
+
/** Persist one live listing, reporting a cache failure without failing the read. */
|
|
213
|
+
private saveListing;
|
|
128
214
|
/** Send one streaming request over the Responses wire. */
|
|
129
215
|
private request;
|
|
130
216
|
/** Resolve image blocks through the attachment service, when it is present. */
|