dsh-plugin-subscription-oauth 0.16.3 → 0.17.1
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/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/index.js +21 -0
- package/lib/protocol.d.ts +9 -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 +16 -2
- package/lib/providers/ark.d.ts +104 -10
- package/lib/providers/ark.js +186 -48
- package/lib/providers/catalog.d.ts +92 -0
- package/lib/providers/catalog.js +191 -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 +16 -2
- package/lib/providers/glm.d.ts +100 -16
- package/lib/providers/glm.js +182 -85
- package/lib/providers/grok.d.ts +114 -27
- package/lib/providers/grok.js +330 -129
- package/lib/providers/kimi-provider.d.ts +10 -0
- package/lib/providers/kimi-provider.js +16 -4
- package/lib/providers/kimi.d.ts +122 -32
- package/lib/providers/kimi.js +243 -122
- 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 +501 -59
- package/package.json +1 -1
package/lib/providers/common.js
CHANGED
|
@@ -141,6 +141,29 @@ export function isMissingOrInvalidCredential(error) {
|
|
|
141
141
|
}
|
|
142
142
|
return false;
|
|
143
143
|
}
|
|
144
|
+
/** Body text that says the request named a model the account does not serve. */
|
|
145
|
+
const UNKNOWN_MODEL = /model id does not exist|unknown model|model (?:id )?not found|does not exist, recognized as/i;
|
|
146
|
+
/** Body text that names the model id to use instead. */
|
|
147
|
+
const MODEL_REPLACEMENT = /set model id as\s*`?([A-Za-z0-9][A-Za-z0-9._:@[\]/-]*)`?/i;
|
|
148
|
+
/**
|
|
149
|
+
* The model id a provider's failure names as the one to use instead.
|
|
150
|
+
*
|
|
151
|
+
* A 401/403 is not always about the credential: a provider also answers it when the
|
|
152
|
+
* request names a model id the account does not serve — Kimi says `Your model id does
|
|
153
|
+
* not exist, recognized as other:k3[1m]. Please set model id as \`k3\``. That message
|
|
154
|
+
* is the endpoint's own answer to "which id should I have used", so it is worth acting
|
|
155
|
+
* on rather than reporting: refreshing a healthy token, or marking the account as
|
|
156
|
+
* needing a new login, would both be wrong answers to a model rename.
|
|
157
|
+
*
|
|
158
|
+
* @param message - the failure text.
|
|
159
|
+
* @returns the replacement id, or undefined when the failure is not about the model id.
|
|
160
|
+
*/
|
|
161
|
+
export function unknownModelReplacement(message) {
|
|
162
|
+
if (!UNKNOWN_MODEL.test(message))
|
|
163
|
+
return undefined;
|
|
164
|
+
const replacement = MODEL_REPLACEMENT.exec(message)?.[1];
|
|
165
|
+
return replacement === undefined || replacement === '' ? undefined : replacement;
|
|
166
|
+
}
|
|
144
167
|
/**
|
|
145
168
|
* Map a thrown transport failure onto a harness error, distinguishing caller
|
|
146
169
|
* cancellation from an idle timeout from a genuine network fault.
|
|
@@ -22,6 +22,16 @@ export interface GlmProviderOptions {
|
|
|
22
22
|
defaultModel?: string;
|
|
23
23
|
/** Extra model ids published without shipped metadata. */
|
|
24
24
|
extraModels?: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* The endpoint's last live listing, from this plugin's cache.
|
|
27
|
+
*
|
|
28
|
+
* The cached table is what the picker shows before any request: the endpoint's own
|
|
29
|
+
* answer from the last read, so a release does not need a plugin update and a refresh
|
|
30
|
+
* does not need a request.
|
|
31
|
+
*/
|
|
32
|
+
cachedListing?: () => Promise<unknown>;
|
|
33
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
34
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
25
35
|
/** Shared model-visibility preferences. */
|
|
26
36
|
preferences: ModelPreferences;
|
|
27
37
|
/** Resolve the attachment service per request. */
|
|
@@ -116,8 +116,14 @@ export function createGlmProvider(options) {
|
|
|
116
116
|
return read.windows;
|
|
117
117
|
},
|
|
118
118
|
...options.extraModels === undefined ? {} : { extraModels: options.extraModels },
|
|
119
|
+
...options.defaultModel === undefined ? {} : { defaultModel: options.defaultModel },
|
|
120
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
121
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
119
122
|
fetchFn,
|
|
120
123
|
});
|
|
124
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
125
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
126
|
+
void adapter.hydrate().catch(() => undefined);
|
|
121
127
|
const spec = {
|
|
122
128
|
id: GLM_PROVIDER_ID,
|
|
123
129
|
displayName: GLM_PROVIDER_NAME,
|
|
@@ -166,9 +172,17 @@ export function createGlmProvider(options) {
|
|
|
166
172
|
removeModel: (id) => controller.removeModel(id),
|
|
167
173
|
usage: (force) => controller.usage(force),
|
|
168
174
|
reload: () => controller.reload(),
|
|
169
|
-
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();
|
|
170
180
|
const image = adapter.imageModelId();
|
|
171
|
-
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
|
+
};
|
|
172
186
|
},
|
|
173
187
|
};
|
|
174
188
|
}
|
package/lib/providers/glm.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* requests and the model list then go to the coding-plan host, whose
|
|
7
7
|
* `/responses` route answers the same wire the `kimi` and `grok` routes use.
|
|
8
8
|
*
|
|
9
|
+
* The model table this route publishes is that host's own `/models` answer, read once
|
|
10
|
+
* and kept in this plugin's cache (see `hydrate` and `fetchCatalog`). Nothing in this
|
|
11
|
+
* module decides which models exist, so one released, renamed or retired server-side
|
|
12
|
+
* reaches the picker without a plugin update and without a request per refresh.
|
|
13
|
+
*
|
|
9
14
|
* @module dsh-plugin-subscription-oauth/providers/glm
|
|
10
15
|
*/
|
|
11
16
|
import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
@@ -44,14 +49,6 @@ export declare const GLM_DEFAULT_MODEL = "glm-5.3";
|
|
|
44
49
|
export declare const GLM_CONTEXT_WINDOW = 1048576;
|
|
45
50
|
/** Output budget used when the caller sets no cap. */
|
|
46
51
|
export declare const GLM_DEFAULT_MAX_TOKENS = 131072;
|
|
47
|
-
/**
|
|
48
|
-
* The catalog this build ships, so a signed-in route is usable with no discovery.
|
|
49
|
-
*
|
|
50
|
-
* Ids are the wire ids, lowercase: the coding plan answers `glm-5.3`, not the
|
|
51
|
-
* `GLM-5.3` display name its own client shows. `glm-5.3-flash` is the multimodal
|
|
52
|
-
* member of the family; the rest read text only.
|
|
53
|
-
*/
|
|
54
|
-
export declare const GLM_MODELS: readonly CatalogEntry[];
|
|
55
52
|
/** One signed-in BigModel account. */
|
|
56
53
|
export interface GlmSession extends SessionBase {
|
|
57
54
|
/** ZCode JWT the coding plan is bound to; presented as the API key. */
|
|
@@ -82,6 +79,25 @@ export interface GlmAdapterOptions {
|
|
|
82
79
|
userModels?: () => Promise<readonly string[]> | readonly string[];
|
|
83
80
|
/** Extra ids published without shipped metadata. */
|
|
84
81
|
extraModels?: readonly string[];
|
|
82
|
+
/**
|
|
83
|
+
* Model this route preselects, and therefore always offers.
|
|
84
|
+
*
|
|
85
|
+
* Configuration rather than a shipped fact: a subscription that re-points its
|
|
86
|
+
* alias at a new release follows the config instead of this build's release
|
|
87
|
+
* cadence. Defaults to {@link GLM_DEFAULT_MODEL}.
|
|
88
|
+
*/
|
|
89
|
+
defaultModel?: string;
|
|
90
|
+
/**
|
|
91
|
+
* The endpoint's last live listing, from this plugin's cache.
|
|
92
|
+
*
|
|
93
|
+
* Read once per session, before the first catalog query, so the picker offers real
|
|
94
|
+
* models without asking the endpoint anything. A route that fetched once and cached
|
|
95
|
+
* the answer has no reason to fetch again on every refresh; the Settings "refresh
|
|
96
|
+
* models" action is what re-reads it.
|
|
97
|
+
*/
|
|
98
|
+
cachedListing?: () => Promise<unknown>;
|
|
99
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
100
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
85
101
|
/** Resolve the attachment service per request. */
|
|
86
102
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
87
103
|
onWarn?: (message: string) => void;
|
|
@@ -118,11 +134,46 @@ export declare function readGlmUsage(input: {
|
|
|
118
134
|
/** The adapter behind the `glm` route. */
|
|
119
135
|
export declare class GlmAdapter extends LlmAdapter {
|
|
120
136
|
private readonly options;
|
|
121
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* The catalog this route serves: entries derived from the endpoint's own listing,
|
|
139
|
+
* either the one just read or the one the cache kept from the last read.
|
|
140
|
+
*
|
|
141
|
+
* Undefined means neither source has produced a listing yet, so only the configured
|
|
142
|
+
* default model is offered until one does.
|
|
143
|
+
*/
|
|
122
144
|
private live;
|
|
123
|
-
/**
|
|
145
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
146
|
+
private hydrated;
|
|
147
|
+
/** Image-generation model id, when the listing reported one. */
|
|
124
148
|
private imageModel;
|
|
149
|
+
/**
|
|
150
|
+
* Fast-variant wire id per picker row.
|
|
151
|
+
*
|
|
152
|
+
* Derived from the listing rather than a curated alias map: a row whose name is a
|
|
153
|
+
* listed model plus the provider's own fast word rides that row instead of becoming
|
|
154
|
+
* one of its own. GLM's own `-flash` id is a distinct model, not a variant, and is
|
|
155
|
+
* left as a row.
|
|
156
|
+
*/
|
|
157
|
+
private readonly fastVariants;
|
|
125
158
|
constructor(options: GlmAdapterOptions);
|
|
159
|
+
/**
|
|
160
|
+
* Adopt the rows of one live listing as this route's catalog.
|
|
161
|
+
*
|
|
162
|
+
* Everything the picker shows is derived here from the endpoint's own answer: the
|
|
163
|
+
* rows that become picker entries and the fast variant that pairs with each row.
|
|
164
|
+
* Nothing about which models exist, or what each one is, is decided by this build.
|
|
165
|
+
*
|
|
166
|
+
* @param rows - decoded rows of the endpoint's listing, in the endpoint's order.
|
|
167
|
+
* @returns the entries to publish, in picker order.
|
|
168
|
+
*/
|
|
169
|
+
private adoptListing;
|
|
170
|
+
/**
|
|
171
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
172
|
+
*
|
|
173
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
174
|
+
* failure: the route simply has no listing until one is fetched.
|
|
175
|
+
*/
|
|
176
|
+
hydrate(): Promise<void>;
|
|
126
177
|
providerInfo(provider: string): LlmProviderInfo;
|
|
127
178
|
providerRetryPolicy(): undefined;
|
|
128
179
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
@@ -130,30 +181,63 @@ export declare class GlmAdapter extends LlmAdapter {
|
|
|
130
181
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
131
182
|
/** Translate an auth failure into the message a user can act on. */
|
|
132
183
|
private refreshHint;
|
|
133
|
-
/** The catalog served: the
|
|
184
|
+
/** The catalog served: the route's own entries, then hand-added ids. */
|
|
134
185
|
private catalog;
|
|
135
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* The catalog to present: the models the endpoint's listing named, in its own order,
|
|
188
|
+
* each with the metadata that listing stated, then the configured extras.
|
|
189
|
+
*
|
|
190
|
+
* There is no shipped roster. A listing is read once and cached (see {@link hydrate}
|
|
191
|
+
* and {@link fetchCatalog}), and that cached table is what the picker shows until the
|
|
192
|
+
* next read, so a model released, renamed or retired server-side is reflected without
|
|
193
|
+
* a plugin update. Only the configured default is always offered, because the picker
|
|
194
|
+
* preselects it.
|
|
195
|
+
*/
|
|
136
196
|
private shippedCatalog;
|
|
197
|
+
/** The model the picker preselects, from configuration. */
|
|
198
|
+
private defaultModel;
|
|
137
199
|
/** Ids the user published by hand that this route does not already serve. */
|
|
138
200
|
private addedEntries;
|
|
139
201
|
/** The image-generation model id, when discovery reported one. */
|
|
140
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;
|
|
141
211
|
/** The ids the picker must currently omit. */
|
|
142
212
|
private hidden;
|
|
143
213
|
/**
|
|
144
|
-
* Adopt the
|
|
214
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
215
|
+
*
|
|
216
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
217
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
218
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
219
|
+
* for them.
|
|
145
220
|
*
|
|
146
|
-
* @param ids - model ids the
|
|
221
|
+
* @param ids - model ids the caller reports.
|
|
147
222
|
*/
|
|
148
223
|
discover(ids: readonly string[]): void;
|
|
149
224
|
/**
|
|
150
|
-
* Read the
|
|
225
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
226
|
+
*
|
|
227
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
228
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
229
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
230
|
+
* also handed to {@link GlmAdapterOptions.saveListing}, so the next session starts
|
|
231
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
232
|
+
* what re-reads it.
|
|
151
233
|
*
|
|
152
234
|
* @param accessToken - bearer for the account.
|
|
153
235
|
* @param signal - optional cancellation.
|
|
154
|
-
* @returns the ids
|
|
236
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
155
237
|
*/
|
|
156
238
|
fetchCatalog(accessToken: string, signal?: AbortSignal): Promise<readonly string[] | undefined>;
|
|
239
|
+
/** Persist one live listing, reporting a cache failure without failing the read. */
|
|
240
|
+
private saveListing;
|
|
157
241
|
/** Send one streaming request over the Responses wire. */
|
|
158
242
|
private request;
|
|
159
243
|
/** Resolve image blocks through the attachment service, when it is present. */
|
package/lib/providers/glm.js
CHANGED
|
@@ -6,16 +6,21 @@
|
|
|
6
6
|
* requests and the model list then go to the coding-plan host, whose
|
|
7
7
|
* `/responses` route answers the same wire the `kimi` and `grok` routes use.
|
|
8
8
|
*
|
|
9
|
+
* The model table this route publishes is that host's own `/models` answer, read once
|
|
10
|
+
* and kept in this plugin's cache (see `hydrate` and `fetchCatalog`). Nothing in this
|
|
11
|
+
* module decides which models exist, so one released, renamed or retired server-side
|
|
12
|
+
* reaches the picker without a plugin update and without a request per refresh.
|
|
13
|
+
*
|
|
9
14
|
* @module dsh-plugin-subscription-oauth/providers/glm
|
|
10
15
|
*/
|
|
11
16
|
import { LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
12
17
|
import { REAUTH_REQUIRED, ProviderError } from '../errors.js';
|
|
13
18
|
import { finiteInstant, nonEmpty } from '../auth/store.js';
|
|
14
19
|
import { proxiedFetch } from '../http.js';
|
|
15
|
-
import { findEntry, resolveCatalog } from './catalog.js';
|
|
20
|
+
import { composeCatalog, fallbackEntry, findEntry, liveEntry, liveModelFacts, resolveCatalog } from './catalog.js';
|
|
16
21
|
import { streamProviderResponses, accountPoolHooks } from './responses-stream.js';
|
|
17
22
|
import { toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
18
|
-
import { isImageModel } from './grok.js';
|
|
23
|
+
import { baseModelOf, FastVariantIndex, isFastVariant, isImageModel } from './grok.js';
|
|
19
24
|
/** Model ids that are clearly not chat models. */
|
|
20
25
|
const NON_CHAT_ID = /(?:^|[-_/])(?:image|video|imagine|embed|embedding|whisper|tts|vision-only)/i;
|
|
21
26
|
/** Provider route this module serves. */
|
|
@@ -45,54 +50,6 @@ export const GLM_DEFAULT_MODEL = 'glm-5.3';
|
|
|
45
50
|
export const GLM_CONTEXT_WINDOW = 1_048_576;
|
|
46
51
|
/** Output budget used when the caller sets no cap. */
|
|
47
52
|
export const GLM_DEFAULT_MAX_TOKENS = 131_072;
|
|
48
|
-
/** Reasoning ladder the GLM 5.3 family accepts. */
|
|
49
|
-
const GLM_53_EFFORTS = ['low', 'high', 'max'];
|
|
50
|
-
/**
|
|
51
|
-
* The catalog this build ships, so a signed-in route is usable with no discovery.
|
|
52
|
-
*
|
|
53
|
-
* Ids are the wire ids, lowercase: the coding plan answers `glm-5.3`, not the
|
|
54
|
-
* `GLM-5.3` display name its own client shows. `glm-5.3-flash` is the multimodal
|
|
55
|
-
* member of the family; the rest read text only.
|
|
56
|
-
*/
|
|
57
|
-
export const GLM_MODELS = [
|
|
58
|
-
{
|
|
59
|
-
id: 'glm-5.3',
|
|
60
|
-
name: 'GLM-5.3',
|
|
61
|
-
description: 'Coding plan flagship, text only',
|
|
62
|
-
contextWindow: GLM_CONTEXT_WINDOW,
|
|
63
|
-
inputModalities: ['text'],
|
|
64
|
-
reasoningEfforts: [...GLM_53_EFFORTS],
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'glm-5.3-flash',
|
|
68
|
-
name: 'GLM-5.3 Flash',
|
|
69
|
-
description: 'Fast, reads images',
|
|
70
|
-
contextWindow: GLM_CONTEXT_WINDOW,
|
|
71
|
-
inputModalities: ['text', 'image'],
|
|
72
|
-
reasoningEfforts: [...GLM_53_EFFORTS],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'glm-5.2',
|
|
76
|
-
name: 'GLM-5.2',
|
|
77
|
-
description: 'Previous flagship',
|
|
78
|
-
contextWindow: GLM_CONTEXT_WINDOW,
|
|
79
|
-
inputModalities: ['text'],
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
id: 'glm-5.1',
|
|
83
|
-
name: 'GLM-5.1',
|
|
84
|
-
description: 'Earlier 5.x release',
|
|
85
|
-
contextWindow: GLM_CONTEXT_WINDOW,
|
|
86
|
-
inputModalities: ['text'],
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: 'glm-5',
|
|
90
|
-
name: 'GLM-5',
|
|
91
|
-
description: 'First 5.x release',
|
|
92
|
-
contextWindow: GLM_CONTEXT_WINDOW,
|
|
93
|
-
inputModalities: ['text'],
|
|
94
|
-
},
|
|
95
|
-
];
|
|
96
53
|
/**
|
|
97
54
|
* Coerce one decoded credential entry into a GLM session.
|
|
98
55
|
*
|
|
@@ -221,14 +178,88 @@ async function httpFailure(response, label) {
|
|
|
221
178
|
/** The adapter behind the `glm` route. */
|
|
222
179
|
export class GlmAdapter extends LlmAdapter {
|
|
223
180
|
options;
|
|
224
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* The catalog this route serves: entries derived from the endpoint's own listing,
|
|
183
|
+
* either the one just read or the one the cache kept from the last read.
|
|
184
|
+
*
|
|
185
|
+
* Undefined means neither source has produced a listing yet, so only the configured
|
|
186
|
+
* default model is offered until one does.
|
|
187
|
+
*/
|
|
225
188
|
live;
|
|
226
|
-
/**
|
|
189
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
190
|
+
hydrated = false;
|
|
191
|
+
/** Image-generation model id, when the listing reported one. */
|
|
227
192
|
imageModel;
|
|
193
|
+
/**
|
|
194
|
+
* Fast-variant wire id per picker row.
|
|
195
|
+
*
|
|
196
|
+
* Derived from the listing rather than a curated alias map: a row whose name is a
|
|
197
|
+
* listed model plus the provider's own fast word rides that row instead of becoming
|
|
198
|
+
* one of its own. GLM's own `-flash` id is a distinct model, not a variant, and is
|
|
199
|
+
* left as a row.
|
|
200
|
+
*/
|
|
201
|
+
fastVariants = new FastVariantIndex();
|
|
228
202
|
constructor(options) {
|
|
229
203
|
super();
|
|
230
204
|
this.options = options;
|
|
231
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Adopt the rows of one live listing as this route's catalog.
|
|
208
|
+
*
|
|
209
|
+
* Everything the picker shows is derived here from the endpoint's own answer: the
|
|
210
|
+
* rows that become picker entries and the fast variant that pairs with each row.
|
|
211
|
+
* Nothing about which models exist, or what each one is, is decided by this build.
|
|
212
|
+
*
|
|
213
|
+
* @param rows - decoded rows of the endpoint's listing, in the endpoint's order.
|
|
214
|
+
* @returns the entries to publish, in picker order.
|
|
215
|
+
*/
|
|
216
|
+
adoptListing(rows) {
|
|
217
|
+
const facts = rows
|
|
218
|
+
.map((row) => liveModelFacts(row))
|
|
219
|
+
.filter((fact) => fact !== undefined);
|
|
220
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
221
|
+
this.imageModel = facts.find((fact) => isImageModel(fact.id))?.id;
|
|
222
|
+
this.fastVariants.clear();
|
|
223
|
+
for (const fact of facts)
|
|
224
|
+
this.fastVariants.noteListed(fact.id);
|
|
225
|
+
for (const fact of facts) {
|
|
226
|
+
const base = this.fastVariants.pair(fact.id);
|
|
227
|
+
this.fastVariants.note(fact.id, () => base ?? baseModelOf(fact.id));
|
|
228
|
+
}
|
|
229
|
+
// Fold a variant onto its row and drop the ids that are not chat models, keeping the
|
|
230
|
+
// endpoint's order. A row's own facts describe the row; a variant's describe the
|
|
231
|
+
// variant, so a row that is also listed is never overwritten by its variant.
|
|
232
|
+
const byId = new Map();
|
|
233
|
+
for (const fact of facts) {
|
|
234
|
+
if (NON_CHAT_ID.test(fact.id))
|
|
235
|
+
continue;
|
|
236
|
+
const id = isFastVariant(fact.id) ? this.fastVariants.pair(fact.id) ?? baseModelOf(fact.id) : fact.id;
|
|
237
|
+
if (id === fact.id || !byId.has(id))
|
|
238
|
+
byId.set(id, { ...fact, id });
|
|
239
|
+
}
|
|
240
|
+
return [...byId.values()].map((fact) => liveEntry(fact, [], GLM_CONTEXT_WINDOW, 'Reported by the GLM model list'));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
244
|
+
*
|
|
245
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
246
|
+
* failure: the route simply has no listing until one is fetched.
|
|
247
|
+
*/
|
|
248
|
+
async hydrate() {
|
|
249
|
+
if (this.hydrated)
|
|
250
|
+
return;
|
|
251
|
+
this.hydrated = true;
|
|
252
|
+
if (this.live !== undefined || this.options.cachedListing === undefined)
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
const rows = listingRows(await this.options.cachedListing());
|
|
256
|
+
if (rows.length > 0)
|
|
257
|
+
this.live = this.adoptListing(rows);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
this.options.onWarn?.(`cached model catalog unavailable (${error instanceof Error ? error.message : String(error)})`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
232
263
|
providerInfo(provider) {
|
|
233
264
|
return { id: provider, name: GLM_PROVIDER_NAME };
|
|
234
265
|
}
|
|
@@ -242,6 +273,7 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
242
273
|
// must not appear in the picker, and Auto must not try it.
|
|
243
274
|
if (await this.options.tokens.session() === undefined)
|
|
244
275
|
return [];
|
|
276
|
+
await this.hydrate();
|
|
245
277
|
const hidden = await this.hidden();
|
|
246
278
|
// Image models are never picker rows: they are agent-called, always on.
|
|
247
279
|
const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
|
|
@@ -254,11 +286,12 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
254
286
|
}));
|
|
255
287
|
}
|
|
256
288
|
async resolveModel(_provider, model) {
|
|
289
|
+
await this.hydrate();
|
|
257
290
|
const entry = findEntry(this.catalog(), model);
|
|
258
291
|
if (entry === undefined) {
|
|
259
292
|
// An id this build has never heard of is still dispatchable: the endpoint is
|
|
260
293
|
// the authority on what it serves, and a newer model should not be refused.
|
|
261
|
-
const template = findEntry(this.catalog(),
|
|
294
|
+
const template = findEntry(this.catalog(), this.defaultModel()) ?? this.catalog()[0];
|
|
262
295
|
return {
|
|
263
296
|
provider: GLM_PROVIDER_ID,
|
|
264
297
|
id: model,
|
|
@@ -304,24 +337,34 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
304
337
|
}
|
|
305
338
|
return error;
|
|
306
339
|
}
|
|
307
|
-
/** The catalog served: the
|
|
340
|
+
/** The catalog served: the route's own entries, then hand-added ids. */
|
|
308
341
|
catalog() {
|
|
309
342
|
return [...this.shippedCatalog(), ...this.addedEntries()];
|
|
310
343
|
}
|
|
311
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* The catalog to present: the models the endpoint's listing named, in its own order,
|
|
346
|
+
* each with the metadata that listing stated, then the configured extras.
|
|
347
|
+
*
|
|
348
|
+
* There is no shipped roster. A listing is read once and cached (see {@link hydrate}
|
|
349
|
+
* and {@link fetchCatalog}), and that cached table is what the picker shows until the
|
|
350
|
+
* next read, so a model released, renamed or retired server-side is reflected without
|
|
351
|
+
* a plugin update. Only the configured default is always offered, because the picker
|
|
352
|
+
* preselects it.
|
|
353
|
+
*/
|
|
312
354
|
shippedCatalog() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
355
|
+
return composeCatalog({
|
|
356
|
+
curated: [],
|
|
357
|
+
...this.live === undefined ? {} : { live: this.live },
|
|
358
|
+
...this.options.extraModels === undefined ? {} : { extra: this.options.extraModels },
|
|
359
|
+
defaultModel: this.defaultModel(),
|
|
360
|
+
fallbackContextWindow: GLM_CONTEXT_WINDOW,
|
|
361
|
+
liveDescription: 'Reported by the GLM model list',
|
|
362
|
+
extraDescription: 'Extra model from the plugin configuration',
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/** The model the picker preselects, from configuration. */
|
|
366
|
+
defaultModel() {
|
|
367
|
+
return this.options.defaultModel ?? GLM_DEFAULT_MODEL;
|
|
325
368
|
}
|
|
326
369
|
/** Ids the user published by hand that this route does not already serve. */
|
|
327
370
|
addedEntries() {
|
|
@@ -331,12 +374,22 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
331
374
|
return [];
|
|
332
375
|
return (Array.isArray(added) ? added : [])
|
|
333
376
|
.filter((id) => findEntry(own, id) === undefined)
|
|
334
|
-
.map((id) =>
|
|
377
|
+
.map((id) => fallbackEntry(id, 'Added by hand in Settings', GLM_CONTEXT_WINDOW));
|
|
335
378
|
}
|
|
336
379
|
/** The image-generation model id, when discovery reported one. */
|
|
337
380
|
imageModelId() {
|
|
338
381
|
return this.imageModel;
|
|
339
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
|
+
}
|
|
340
393
|
/** The ids the picker must currently omit. */
|
|
341
394
|
async hidden() {
|
|
342
395
|
if (this.options.hiddenModels === undefined)
|
|
@@ -350,19 +403,33 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
350
403
|
}
|
|
351
404
|
}
|
|
352
405
|
/**
|
|
353
|
-
* Adopt the
|
|
406
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
407
|
+
*
|
|
408
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
409
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
410
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
411
|
+
* for them.
|
|
354
412
|
*
|
|
355
|
-
* @param ids - model ids the
|
|
413
|
+
* @param ids - model ids the caller reports.
|
|
356
414
|
*/
|
|
357
415
|
discover(ids) {
|
|
358
|
-
this.live
|
|
416
|
+
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
417
|
+
this.live = ids.map((id) => known.get(id)
|
|
418
|
+
?? liveEntry({ id }, [], GLM_CONTEXT_WINDOW, 'Reported by the GLM model list'));
|
|
359
419
|
}
|
|
360
420
|
/**
|
|
361
|
-
* Read the
|
|
421
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
422
|
+
*
|
|
423
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
424
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
425
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
426
|
+
* also handed to {@link GlmAdapterOptions.saveListing}, so the next session starts
|
|
427
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
428
|
+
* what re-reads it.
|
|
362
429
|
*
|
|
363
430
|
* @param accessToken - bearer for the account.
|
|
364
431
|
* @param signal - optional cancellation.
|
|
365
|
-
* @returns the ids
|
|
432
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
366
433
|
*/
|
|
367
434
|
async fetchCatalog(accessToken, signal) {
|
|
368
435
|
if (!this.options.discovery)
|
|
@@ -374,23 +441,40 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
374
441
|
...signal === undefined ? {} : { signal },
|
|
375
442
|
});
|
|
376
443
|
if (!response.ok) {
|
|
377
|
-
this.options.onWarn?.(`GLM model list unavailable (HTTP ${response.status})`);
|
|
444
|
+
this.options.onWarn?.(`GLM model list unavailable (HTTP ${response.status}); keeping the last catalog`);
|
|
378
445
|
return undefined;
|
|
379
446
|
}
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
447
|
+
const payload = await response.json();
|
|
448
|
+
const rows = Array.isArray(payload.data) ? payload.data : [];
|
|
449
|
+
const entries = this.adoptListing(rows);
|
|
450
|
+
if (entries.length === 0) {
|
|
451
|
+
this.options.onWarn?.('GLM model discovery returned an empty catalog; keeping the last catalog');
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
this.live = entries;
|
|
455
|
+
// The cached table is the endpoint's answer, kept so the next session does not ask
|
|
456
|
+
// again. A cache failure never fails the read that produced it.
|
|
457
|
+
await this.saveListing(rows);
|
|
458
|
+
return entries.map((entry) => entry.id);
|
|
388
459
|
}
|
|
389
460
|
catch (error) {
|
|
461
|
+
if (signal?.aborted === true)
|
|
462
|
+
return undefined;
|
|
390
463
|
this.options.onWarn?.(`GLM model list unavailable (${error instanceof Error ? error.message : String(error)})`);
|
|
391
464
|
return undefined;
|
|
392
465
|
}
|
|
393
466
|
}
|
|
467
|
+
/** Persist one live listing, reporting a cache failure without failing the read. */
|
|
468
|
+
async saveListing(rows) {
|
|
469
|
+
if (this.options.saveListing === undefined)
|
|
470
|
+
return;
|
|
471
|
+
try {
|
|
472
|
+
await this.options.saveListing(rows);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
this.options.onWarn?.(`model catalog cache write failed (${error instanceof Error ? error.message : String(error)})`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
394
478
|
/** Send one streaming request over the Responses wire. */
|
|
395
479
|
async request(options, model, accessToken, signal) {
|
|
396
480
|
const resolveImage = this.imageResolver(signal);
|
|
@@ -446,9 +530,22 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
446
530
|
return this.catalog();
|
|
447
531
|
}
|
|
448
532
|
}
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
533
|
+
/**
|
|
534
|
+
* The rows of one cached listing.
|
|
535
|
+
*
|
|
536
|
+
* The cache holds the rows this plugin stored; a payload written as the endpoint's own
|
|
537
|
+
* envelope is read too, so a differently shaped cache cannot silently empty the picker.
|
|
538
|
+
*
|
|
539
|
+
* @param payload - whatever `cachedListing` returned.
|
|
540
|
+
* @returns the rows to adopt, or an empty list when the payload names none.
|
|
541
|
+
*/
|
|
542
|
+
function listingRows(payload) {
|
|
543
|
+
if (Array.isArray(payload))
|
|
544
|
+
return payload;
|
|
545
|
+
const data = typeof payload === 'object' && payload !== null
|
|
546
|
+
? payload.data
|
|
547
|
+
: undefined;
|
|
548
|
+
return Array.isArray(data) ? data : [];
|
|
452
549
|
}
|
|
453
550
|
/** The reasoning block for a catalog entry, or undefined when it supports none. */
|
|
454
551
|
function reasoningInfo(entry) {
|