dsh-plugin-subscription-oauth 0.17.0 → 0.17.2
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/client/FastModeButton.d.ts +0 -1
- package/lib/client/FastModeButton.js +48 -11
- package/lib/client.js +32 -9
- package/lib/client.js.map +1 -1
- package/lib/protocol.d.ts +9 -0
- package/lib/providers/ark-provider.js +10 -2
- package/lib/providers/ark.d.ts +8 -0
- package/lib/providers/ark.js +10 -0
- package/lib/providers/catalog.d.ts +2 -1
- package/lib/providers/catalog.js +2 -1
- package/lib/providers/glm-provider.js +10 -2
- package/lib/providers/glm.d.ts +8 -0
- package/lib/providers/glm.js +10 -0
- package/lib/providers/grok.d.ts +8 -0
- package/lib/providers/grok.js +18 -1
- package/lib/providers/kimi-provider.js +8 -1
- package/lib/providers/kimi.d.ts +18 -0
- package/lib/providers/kimi.js +54 -2
- package/lib/providers/workbuddy.d.ts +10 -8
- package/lib/providers/workbuddy.js +46 -33
- package/package.json +1 -1
package/lib/protocol.d.ts
CHANGED
|
@@ -397,6 +397,15 @@ export interface ModelVariants {
|
|
|
397
397
|
fastRung?: string;
|
|
398
398
|
/** Image-generation model wire id, when the provider reported one. */
|
|
399
399
|
image?: string;
|
|
400
|
+
/**
|
|
401
|
+
* The route has not read its model listing yet, so "no variant" is not yet an answer.
|
|
402
|
+
*
|
|
403
|
+
* A listing is read once per session and its variants are derived from it, so an early
|
|
404
|
+
* read of this endpoint cannot tell "this model has no fast variant" from "the route has
|
|
405
|
+
* not looked yet". Saying so lets the caller ask again later instead of hiding a control
|
|
406
|
+
* that should be there — and, once a listing has been read, lets it stop asking.
|
|
407
|
+
*/
|
|
408
|
+
pending?: boolean;
|
|
400
409
|
}
|
|
401
410
|
/** Result of `model-variants`: the variants for one model id. */
|
|
402
411
|
export type ModelVariantsResult = ModelVariants | undefined;
|
|
@@ -176,9 +176,17 @@ export function createArkProvider(options) {
|
|
|
176
176
|
removeModel: (id) => controller.removeModel(id),
|
|
177
177
|
usage: (force) => controller.usage(force),
|
|
178
178
|
reload: () => controller.reload(),
|
|
179
|
-
modelVariants: (id) => {
|
|
179
|
+
modelVariants: async (id) => {
|
|
180
|
+
// The image model is a row of the listing, so the read is awaited before answering:
|
|
181
|
+
// the composer asks once, and "no image model" must mean there is none.
|
|
182
|
+
void id;
|
|
183
|
+
await adapter.hydrate();
|
|
180
184
|
const image = adapter.imageModelId();
|
|
181
|
-
return
|
|
185
|
+
return {
|
|
186
|
+
...image === undefined ? {} : { image },
|
|
187
|
+
// Nothing read yet: the answer above is provisional, and the caller is told so.
|
|
188
|
+
...adapter.hasListing() ? {} : { pending: true },
|
|
189
|
+
};
|
|
182
190
|
},
|
|
183
191
|
};
|
|
184
192
|
}
|
package/lib/providers/ark.d.ts
CHANGED
|
@@ -163,6 +163,14 @@ export declare class ArkAdapter extends LlmAdapter {
|
|
|
163
163
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
164
164
|
/** The image-generation model id, when discovery reported one. */
|
|
165
165
|
imageModelId(): string | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* Whether this route has read a model listing yet.
|
|
168
|
+
*
|
|
169
|
+
* The image model is a row of the listing, so until one has been read "there is no image
|
|
170
|
+
* model" and "the listing has not arrived" look identical from outside; a caller that asks
|
|
171
|
+
* once needs to tell them apart.
|
|
172
|
+
*/
|
|
173
|
+
hasListing(): boolean;
|
|
166
174
|
resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
167
175
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
168
176
|
/** Translate an auth failure into the message a user can act on. */
|
package/lib/providers/ark.js
CHANGED
|
@@ -228,6 +228,16 @@ export class ArkAdapter extends LlmAdapter {
|
|
|
228
228
|
imageModelId() {
|
|
229
229
|
return this.imageModel;
|
|
230
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Whether this route has read a model listing yet.
|
|
233
|
+
*
|
|
234
|
+
* The image model is a row of the listing, so until one has been read "there is no image
|
|
235
|
+
* model" and "the listing has not arrived" look identical from outside; a caller that asks
|
|
236
|
+
* once needs to tell them apart.
|
|
237
|
+
*/
|
|
238
|
+
hasListing() {
|
|
239
|
+
return this.live !== undefined;
|
|
240
|
+
}
|
|
231
241
|
async resolveModel(_provider, model) {
|
|
232
242
|
await this.hydrate();
|
|
233
243
|
const entry = findEntry(this.catalog(), model);
|
|
@@ -88,7 +88,8 @@ export declare function liveModelFacts(row: unknown): LiveModelFacts | undefined
|
|
|
88
88
|
* What the endpoint stated wins; the curated table fills only what the listing was
|
|
89
89
|
* silent about, so a live figure — the window a model actually serves today — is
|
|
90
90
|
* never overridden by a shipped guess, and a retired figure never survives a live
|
|
91
|
-
* read that contradicts it.
|
|
91
|
+
* read that contradicts it. A route whose ids are stable aliases the service re-points
|
|
92
|
+
* per release keeps its own labels instead (see the kimi route's alias labels).
|
|
92
93
|
*
|
|
93
94
|
* @param facts - what the listing stated for this id.
|
|
94
95
|
* @param curated - the curated metadata table.
|
package/lib/providers/catalog.js
CHANGED
|
@@ -163,7 +163,8 @@ export function liveModelFacts(row) {
|
|
|
163
163
|
* What the endpoint stated wins; the curated table fills only what the listing was
|
|
164
164
|
* silent about, so a live figure — the window a model actually serves today — is
|
|
165
165
|
* never overridden by a shipped guess, and a retired figure never survives a live
|
|
166
|
-
* read that contradicts it.
|
|
166
|
+
* read that contradicts it. A route whose ids are stable aliases the service re-points
|
|
167
|
+
* per release keeps its own labels instead (see the kimi route's alias labels).
|
|
167
168
|
*
|
|
168
169
|
* @param facts - what the listing stated for this id.
|
|
169
170
|
* @param curated - the curated metadata table.
|
|
@@ -172,9 +172,17 @@ export function createGlmProvider(options) {
|
|
|
172
172
|
removeModel: (id) => controller.removeModel(id),
|
|
173
173
|
usage: (force) => controller.usage(force),
|
|
174
174
|
reload: () => controller.reload(),
|
|
175
|
-
modelVariants: (id) => {
|
|
175
|
+
modelVariants: async (id) => {
|
|
176
|
+
// The image model is a row of the listing, so the read is awaited before answering:
|
|
177
|
+
// the composer asks once, and "no image model" must mean there is none.
|
|
178
|
+
void id;
|
|
179
|
+
await adapter.hydrate();
|
|
176
180
|
const image = adapter.imageModelId();
|
|
177
|
-
return
|
|
181
|
+
return {
|
|
182
|
+
...image === undefined ? {} : { image },
|
|
183
|
+
// Nothing read yet: the answer above is provisional, and the caller is told so.
|
|
184
|
+
...adapter.hasListing() ? {} : { pending: true },
|
|
185
|
+
};
|
|
178
186
|
},
|
|
179
187
|
};
|
|
180
188
|
}
|
package/lib/providers/glm.d.ts
CHANGED
|
@@ -200,6 +200,14 @@ export declare class GlmAdapter extends LlmAdapter {
|
|
|
200
200
|
private addedEntries;
|
|
201
201
|
/** The image-generation model id, when discovery reported one. */
|
|
202
202
|
imageModelId(): string | undefined;
|
|
203
|
+
/**
|
|
204
|
+
* Whether this route has read a model listing yet.
|
|
205
|
+
*
|
|
206
|
+
* The image model is a row of the listing, so until one has been read "there is no image
|
|
207
|
+
* model" and "the listing has not arrived" look identical from outside; a caller that asks
|
|
208
|
+
* once needs to tell them apart.
|
|
209
|
+
*/
|
|
210
|
+
hasListing(): boolean;
|
|
203
211
|
/** The ids the picker must currently omit. */
|
|
204
212
|
private hidden;
|
|
205
213
|
/**
|
package/lib/providers/glm.js
CHANGED
|
@@ -380,6 +380,16 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
380
380
|
imageModelId() {
|
|
381
381
|
return this.imageModel;
|
|
382
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Whether this route has read a model listing yet.
|
|
385
|
+
*
|
|
386
|
+
* The image model is a row of the listing, so until one has been read "there is no image
|
|
387
|
+
* model" and "the listing has not arrived" look identical from outside; a caller that asks
|
|
388
|
+
* once needs to tell them apart.
|
|
389
|
+
*/
|
|
390
|
+
hasListing() {
|
|
391
|
+
return this.live !== undefined;
|
|
392
|
+
}
|
|
383
393
|
/** The ids the picker must currently omit. */
|
|
384
394
|
async hidden() {
|
|
385
395
|
if (this.options.hiddenModels === undefined)
|
package/lib/providers/grok.d.ts
CHANGED
|
@@ -337,6 +337,14 @@ export declare class GrokAdapter extends LlmAdapter {
|
|
|
337
337
|
} | undefined;
|
|
338
338
|
/** The image-generation model id, when discovery reported one. */
|
|
339
339
|
imageModelId(): string | undefined;
|
|
340
|
+
/**
|
|
341
|
+
* Whether this route has read a model listing yet.
|
|
342
|
+
*
|
|
343
|
+
* A variant is derived from the listing, so until one has been read "this model has no
|
|
344
|
+
* variant" and "the listing has not arrived" look identical from outside; a caller that
|
|
345
|
+
* asks once (the composer's quick-mode button) needs to tell them apart.
|
|
346
|
+
*/
|
|
347
|
+
hasListing(): boolean;
|
|
340
348
|
/** The curated catalog, for the Settings page's model list. */
|
|
341
349
|
catalogEntries(): readonly CatalogEntry[];
|
|
342
350
|
/** What this route itself serves, without the ids added by hand. */
|
package/lib/providers/grok.js
CHANGED
|
@@ -868,6 +868,16 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
868
868
|
imageModelId() {
|
|
869
869
|
return this.imageModel;
|
|
870
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* Whether this route has read a model listing yet.
|
|
873
|
+
*
|
|
874
|
+
* A variant is derived from the listing, so until one has been read "this model has no
|
|
875
|
+
* variant" and "the listing has not arrived" look identical from outside; a caller that
|
|
876
|
+
* asks once (the composer's quick-mode button) needs to tell them apart.
|
|
877
|
+
*/
|
|
878
|
+
hasListing() {
|
|
879
|
+
return this.live !== undefined;
|
|
880
|
+
}
|
|
871
881
|
/** The curated catalog, for the Settings page's model list. */
|
|
872
882
|
catalogEntries() {
|
|
873
883
|
return this.catalog();
|
|
@@ -1193,12 +1203,19 @@ export function createGrokProvider(options) {
|
|
|
1193
1203
|
removeModel: (id) => controller.removeModel(id),
|
|
1194
1204
|
usage: (force) => controller.usage(force),
|
|
1195
1205
|
reload: () => controller.reload(),
|
|
1196
|
-
modelVariants: (id) => {
|
|
1206
|
+
modelVariants: async (id) => {
|
|
1207
|
+
// A variant is known only once the route has read its listing, whose rows name it.
|
|
1208
|
+
// The composer asks exactly once, so the read is awaited here: "no fast variant"
|
|
1209
|
+
// must mean the route really has none, not that its table was still loading.
|
|
1210
|
+
await adapter.hydrate();
|
|
1197
1211
|
const pair = adapter.fastPairOf(id);
|
|
1198
1212
|
const image = adapter.imageModelId();
|
|
1199
1213
|
return {
|
|
1200
1214
|
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId, fastRung: fastWordOf(pair.wireId) },
|
|
1201
1215
|
...image === undefined ? {} : { image },
|
|
1216
|
+
// Nothing read yet: the answer above is provisional, and the caller is told so
|
|
1217
|
+
// rather than being left to read silence as "this model has no variant".
|
|
1218
|
+
...adapter.hasListing() ? {} : { pending: true },
|
|
1202
1219
|
};
|
|
1203
1220
|
},
|
|
1204
1221
|
};
|
|
@@ -156,12 +156,19 @@ export function createKimiProvider(options) {
|
|
|
156
156
|
removeModel: (id) => controller.removeModel(id),
|
|
157
157
|
usage: (force) => controller.usage(force),
|
|
158
158
|
reload: () => controller.reload(),
|
|
159
|
-
modelVariants: (id) => {
|
|
159
|
+
modelVariants: async (id) => {
|
|
160
|
+
// A variant is known only once the route has read its listing, whose row names it.
|
|
161
|
+
// The composer asks exactly once, so the read is awaited here: "no fast variant"
|
|
162
|
+
// must mean the route really has none, not that its table was still loading.
|
|
163
|
+
await adapter.hydrate();
|
|
160
164
|
const pair = adapter.fastPairOf(id);
|
|
161
165
|
const image = adapter.imageModelId();
|
|
162
166
|
return {
|
|
163
167
|
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId, fastRung: fastWordOf(pair.wireId) },
|
|
164
168
|
...image === undefined ? {} : { image },
|
|
169
|
+
// Nothing read yet: the answer above is provisional, and the caller is told so
|
|
170
|
+
// rather than being left to read silence as "this model has no variant".
|
|
171
|
+
...adapter.hasListing() ? {} : { pending: true },
|
|
165
172
|
};
|
|
166
173
|
},
|
|
167
174
|
};
|
package/lib/providers/kimi.d.ts
CHANGED
|
@@ -161,6 +161,16 @@ export declare class KimiAdapter extends LlmAdapter {
|
|
|
161
161
|
* @returns the entries to publish, in picker order.
|
|
162
162
|
*/
|
|
163
163
|
private adoptListing;
|
|
164
|
+
/**
|
|
165
|
+
* Put this route's stable label on one entry, when the id has one.
|
|
166
|
+
*
|
|
167
|
+
* The listing states what the model *is*; the label states what the *id* is, which only
|
|
168
|
+
* this route knows (see {@link STABLE_MODEL_LABELS}).
|
|
169
|
+
*
|
|
170
|
+
* @param entry - the entry built from the listing.
|
|
171
|
+
* @returns the entry, relabelled when its id has a stable label.
|
|
172
|
+
*/
|
|
173
|
+
private labelled;
|
|
164
174
|
/**
|
|
165
175
|
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
166
176
|
*
|
|
@@ -173,6 +183,14 @@ export declare class KimiAdapter extends LlmAdapter {
|
|
|
173
183
|
baseId: string;
|
|
174
184
|
wireId: string;
|
|
175
185
|
} | undefined;
|
|
186
|
+
/**
|
|
187
|
+
* Whether this route has read a model listing yet.
|
|
188
|
+
*
|
|
189
|
+
* A variant is derived from the listing, so until one has been read "this model has no
|
|
190
|
+
* variant" and "the listing has not arrived" look identical from outside; a caller that
|
|
191
|
+
* asks once (the composer's quick-mode button) needs to tell them apart.
|
|
192
|
+
*/
|
|
193
|
+
hasListing(): boolean;
|
|
176
194
|
/**
|
|
177
195
|
* Tool schemas in the shape Moonshot accepts: no `$ref`, and under its size
|
|
178
196
|
* ceiling. A declaration too large to send keeps its arguments, and that is
|
package/lib/providers/kimi.js
CHANGED
|
@@ -71,6 +71,35 @@ const EFFORT_LABELS = {
|
|
|
71
71
|
// is "highspeed", which is what the rung is named here.
|
|
72
72
|
[FAST_RUNG]: { name: 'Highspeed', description: 'Kimi highspeed variant of this model' },
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Stable labels for the ids this route is known for.
|
|
76
|
+
*
|
|
77
|
+
* Labels, not a roster: nothing here decides which models exist or what they serve —
|
|
78
|
+
* membership, windows, ladders and modalities are the endpoint's listing. What it fixes is
|
|
79
|
+
* the one thing the listing cannot answer, because the id does not name it: `kimi-for-coding`
|
|
80
|
+
* is the subscription alias the service re-points at each coding release, so its
|
|
81
|
+
* `display_name` names the release of the moment (`K2.8 Preview` the week K2.8 ships) while
|
|
82
|
+
* the id the user selected stays the same. Labelling that row by release would rename the
|
|
83
|
+
* user's saved selection overnight, so the alias keeps its own name — and the endpoint's
|
|
84
|
+
* release name is deliberately not shown, because the row is the alias, not the version.
|
|
85
|
+
*
|
|
86
|
+
* A model the endpoint adds that this map does not name is published exactly as the
|
|
87
|
+
* endpoint describes it.
|
|
88
|
+
*/
|
|
89
|
+
const STABLE_MODEL_LABELS = {
|
|
90
|
+
'kimi-for-coding': {
|
|
91
|
+
name: 'Kimi for Coding',
|
|
92
|
+
description: 'Subscription alias — the endpoint re-points it at the current coding release',
|
|
93
|
+
},
|
|
94
|
+
'k3': {
|
|
95
|
+
name: 'Kimi K3',
|
|
96
|
+
description: 'Flagship reasoning model',
|
|
97
|
+
},
|
|
98
|
+
'k3-256k': {
|
|
99
|
+
name: 'Kimi K3 (256K)',
|
|
100
|
+
description: 'Flagship reasoning model pinned to the 256K-token window',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
74
103
|
/**
|
|
75
104
|
* Spellings the endpoint has retired, and the model each one became.
|
|
76
105
|
*
|
|
@@ -185,7 +214,20 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
185
214
|
if (id === fact.id || !byId.has(id))
|
|
186
215
|
byId.set(id, { ...fact, id });
|
|
187
216
|
}
|
|
188
|
-
return [...byId.values()].map((fact) => liveEntry(fact, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list'));
|
|
217
|
+
return [...byId.values()].map((fact) => this.labelled(liveEntry(fact, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list')));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Put this route's stable label on one entry, when the id has one.
|
|
221
|
+
*
|
|
222
|
+
* The listing states what the model *is*; the label states what the *id* is, which only
|
|
223
|
+
* this route knows (see {@link STABLE_MODEL_LABELS}).
|
|
224
|
+
*
|
|
225
|
+
* @param entry - the entry built from the listing.
|
|
226
|
+
* @returns the entry, relabelled when its id has a stable label.
|
|
227
|
+
*/
|
|
228
|
+
labelled(entry) {
|
|
229
|
+
const stable = STABLE_MODEL_LABELS[entry.id];
|
|
230
|
+
return stable === undefined ? entry : { ...entry, name: stable.name, description: stable.description };
|
|
189
231
|
}
|
|
190
232
|
/**
|
|
191
233
|
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
@@ -212,6 +254,16 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
212
254
|
fastPairOf(id) {
|
|
213
255
|
return this.fastVariants.pairOf(id);
|
|
214
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Whether this route has read a model listing yet.
|
|
259
|
+
*
|
|
260
|
+
* A variant is derived from the listing, so until one has been read "this model has no
|
|
261
|
+
* variant" and "the listing has not arrived" look identical from outside; a caller that
|
|
262
|
+
* asks once (the composer's quick-mode button) needs to tell them apart.
|
|
263
|
+
*/
|
|
264
|
+
hasListing() {
|
|
265
|
+
return this.live !== undefined;
|
|
266
|
+
}
|
|
215
267
|
/**
|
|
216
268
|
* Tool schemas in the shape Moonshot accepts: no `$ref`, and under its size
|
|
217
269
|
* ceiling. A declaration too large to send keeps its arguments, and that is
|
|
@@ -465,7 +517,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
465
517
|
discover(ids) {
|
|
466
518
|
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
467
519
|
this.live = ids.map((id) => known.get(id)
|
|
468
|
-
?? liveEntry({ id }, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list'));
|
|
520
|
+
?? this.labelled(liveEntry({ id }, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list')));
|
|
469
521
|
}
|
|
470
522
|
/** The catalog, for the Settings page's model list. */
|
|
471
523
|
catalogEntries() {
|
|
@@ -181,18 +181,20 @@ export declare class WorkBuddyAdapter extends LlmAdapter {
|
|
|
181
181
|
/**
|
|
182
182
|
* The stored session one access token belongs to.
|
|
183
183
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* is still read with the token the caller passed.
|
|
189
|
-
*
|
|
190
|
-
* @param accessToken - the token discovery was called with.
|
|
191
|
-
* @returns the session's fields as a signable token, or undefined when none was readable.
|
|
184
|
+
* The chat signs with the resolved token, but the request still needs the session's
|
|
185
|
+
* attribution fields (`uid`, `enterpriseId`, `domain`). They are read back by matching
|
|
186
|
+
* the resolved token against each stored account; a token that matches none (already
|
|
187
|
+
* rotated away) leaves the caller to sign with the bare token.
|
|
192
188
|
*/
|
|
193
189
|
private sessionFor;
|
|
194
190
|
providerRetryPolicy(): undefined;
|
|
195
191
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
192
|
+
/**
|
|
193
|
+
* One chat-completions POST, named the way WorkBuddy's own client names a chat: the
|
|
194
|
+
* message, the request, the turn, and the conversation. Without these the gateway
|
|
195
|
+
* counts the request under no client and can cut the stream.
|
|
196
|
+
*/
|
|
197
|
+
private post;
|
|
196
198
|
/** The stored session's fields as the token the request signs with. */
|
|
197
199
|
private tokenOf;
|
|
198
200
|
private requestBody;
|
|
@@ -614,14 +614,10 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
614
614
|
/**
|
|
615
615
|
* The stored session one access token belongs to.
|
|
616
616
|
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* is still read with the token the caller passed.
|
|
622
|
-
*
|
|
623
|
-
* @param accessToken - the token discovery was called with.
|
|
624
|
-
* @returns the session's fields as a signable token, or undefined when none was readable.
|
|
617
|
+
* The chat signs with the resolved token, but the request still needs the session's
|
|
618
|
+
* attribution fields (`uid`, `enterpriseId`, `domain`). They are read back by matching
|
|
619
|
+
* the resolved token against each stored account; a token that matches none (already
|
|
620
|
+
* rotated away) leaves the caller to sign with the bare token.
|
|
625
621
|
*/
|
|
626
622
|
async sessionFor(accessToken) {
|
|
627
623
|
try {
|
|
@@ -630,8 +626,7 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
630
626
|
if (held?.session.accessToken === accessToken)
|
|
631
627
|
return this.tokenOf(held.session);
|
|
632
628
|
}
|
|
633
|
-
|
|
634
|
-
return current === undefined ? undefined : this.tokenOf(current.session);
|
|
629
|
+
return undefined;
|
|
635
630
|
}
|
|
636
631
|
catch {
|
|
637
632
|
return undefined;
|
|
@@ -642,10 +637,11 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
642
637
|
}
|
|
643
638
|
async *stream(options) {
|
|
644
639
|
diag(`stream entered: model=${options.model} provider=${options.provider}`);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
640
|
+
// Resolve (not just read) the session so a token close to its end is refreshed
|
|
641
|
+
// eagerly, the way WorkBuddy's own app does — a bare session can carry an expired
|
|
642
|
+
// access token into the request, which the gateway refuses with a 403.
|
|
643
|
+
const held = await this.options.tokens.resolve();
|
|
644
|
+
const session = await this.sessionFor(held.accessToken);
|
|
649
645
|
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
650
646
|
// Capture the tool-call history so unsafe tool names in past turns can be renamed to
|
|
651
647
|
// the safe ones the model now sees, keeping the conversation consistent.
|
|
@@ -653,28 +649,24 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
653
649
|
// The last usage frame carries the turn's real billed credit; record it so the spend
|
|
654
650
|
// total is a sum of real figures, not an estimate.
|
|
655
651
|
const turn = { credit: 0 };
|
|
656
|
-
diag(`stream start: model=${options.model} account=${held.account} expiresAt=${held.
|
|
652
|
+
diag(`stream start: model=${options.model} account=${held.account} expiresAt=${held.expiresAt} now=${Date.now()}`);
|
|
657
653
|
try {
|
|
658
654
|
const { body, names } = this.requestBody(options, history);
|
|
659
655
|
diag(`body: keys=${Object.keys(body).join(',')} messages=${Array.isArray(body.messages) ? body.messages.length : 0} model=${String(body.model)}`);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
body: JSON.stringify(body),
|
|
675
|
-
signal: watchdog.signal,
|
|
676
|
-
});
|
|
677
|
-
diag(`response: status=${response.status} ok=${response.ok} ct=${response.headers.get('content-type') ?? ''}`);
|
|
656
|
+
// Post once with the resolved token. A 401/403 can mean the access token lapsed
|
|
657
|
+
// between resolve() and now: force one refresh and post again, as WorkBuddy's own
|
|
658
|
+
// client does — a 403 with a genuinely dead credential keeps its meaning below.
|
|
659
|
+
let response = await this.post(body, session ?? held, watchdog.signal);
|
|
660
|
+
if (response.status === 401 || response.status === 403) {
|
|
661
|
+
diag(`response: ${response.status} on resolved token; forcing one refresh and retrying`);
|
|
662
|
+
const refreshed = await this.options.tokens.resolve(held.account, true);
|
|
663
|
+
const fresh = await this.sessionFor(refreshed.accessToken);
|
|
664
|
+
response = await this.post(body, fresh ?? refreshed, watchdog.signal);
|
|
665
|
+
diag(`response after refresh: status=${response.status} ok=${response.ok} ct=${response.headers.get('content-type') ?? ''}`);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
diag(`response: status=${response.status} ok=${response.ok} ct=${response.headers.get('content-type') ?? ''}`);
|
|
669
|
+
}
|
|
678
670
|
// WorkBuddy refuses a chat mid-stream when the request carries words it filters; a
|
|
679
671
|
// 403/400 here names why, so surface it instead of dying on an empty stream.
|
|
680
672
|
if (!response.ok || response.body === null) {
|
|
@@ -692,6 +684,27 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
692
684
|
}
|
|
693
685
|
}
|
|
694
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* One chat-completions POST, named the way WorkBuddy's own client names a chat: the
|
|
689
|
+
* message, the request, the turn, and the conversation. Without these the gateway
|
|
690
|
+
* counts the request under no client and can cut the stream.
|
|
691
|
+
*/
|
|
692
|
+
post(body, token, signal) {
|
|
693
|
+
return this.fetchFn(WORKBUDDY_API, {
|
|
694
|
+
method: 'POST',
|
|
695
|
+
headers: {
|
|
696
|
+
'Content-Type': 'application/json',
|
|
697
|
+
'Accept': 'text/event-stream',
|
|
698
|
+
...workbuddyHeaders(token),
|
|
699
|
+
'X-Conversation-Message-ID': randomHex(),
|
|
700
|
+
'X-Request-ID': randomHex(),
|
|
701
|
+
'X-Conversation-Request-ID': randomHex(),
|
|
702
|
+
'X-Conversation-ID': randomHex(),
|
|
703
|
+
},
|
|
704
|
+
body: JSON.stringify(body),
|
|
705
|
+
signal,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
695
708
|
/** The stored session's fields as the token the request signs with. */
|
|
696
709
|
tokenOf(session) {
|
|
697
710
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscription-oauth",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EricFL1998",
|