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
|
@@ -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,
|
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,10 +181,21 @@ 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. */
|
|
@@ -141,19 +203,33 @@ export declare class GlmAdapter extends LlmAdapter {
|
|
|
141
203
|
/** The ids the picker must currently omit. */
|
|
142
204
|
private hidden;
|
|
143
205
|
/**
|
|
144
|
-
* Adopt the
|
|
206
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
207
|
+
*
|
|
208
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
209
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
210
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
211
|
+
* for them.
|
|
145
212
|
*
|
|
146
|
-
* @param ids - model ids the
|
|
213
|
+
* @param ids - model ids the caller reports.
|
|
147
214
|
*/
|
|
148
215
|
discover(ids: readonly string[]): void;
|
|
149
216
|
/**
|
|
150
|
-
* Read the
|
|
217
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
218
|
+
*
|
|
219
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
220
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
221
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
222
|
+
* also handed to {@link GlmAdapterOptions.saveListing}, so the next session starts
|
|
223
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
224
|
+
* what re-reads it.
|
|
151
225
|
*
|
|
152
226
|
* @param accessToken - bearer for the account.
|
|
153
227
|
* @param signal - optional cancellation.
|
|
154
|
-
* @returns the ids
|
|
228
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
155
229
|
*/
|
|
156
230
|
fetchCatalog(accessToken: string, signal?: AbortSignal): Promise<readonly string[] | undefined>;
|
|
231
|
+
/** Persist one live listing, reporting a cache failure without failing the read. */
|
|
232
|
+
private saveListing;
|
|
157
233
|
/** Send one streaming request over the Responses wire. */
|
|
158
234
|
private request;
|
|
159
235
|
/** 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,7 +374,7 @@ 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() {
|
|
@@ -350,19 +393,33 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
350
393
|
}
|
|
351
394
|
}
|
|
352
395
|
/**
|
|
353
|
-
* Adopt the
|
|
396
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
354
397
|
*
|
|
355
|
-
* @
|
|
398
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
399
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
400
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
401
|
+
* for them.
|
|
402
|
+
*
|
|
403
|
+
* @param ids - model ids the caller reports.
|
|
356
404
|
*/
|
|
357
405
|
discover(ids) {
|
|
358
|
-
this.live
|
|
406
|
+
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
407
|
+
this.live = ids.map((id) => known.get(id)
|
|
408
|
+
?? liveEntry({ id }, [], GLM_CONTEXT_WINDOW, 'Reported by the GLM model list'));
|
|
359
409
|
}
|
|
360
410
|
/**
|
|
361
|
-
* Read the
|
|
411
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
412
|
+
*
|
|
413
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
414
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
415
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
416
|
+
* also handed to {@link GlmAdapterOptions.saveListing}, so the next session starts
|
|
417
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
418
|
+
* what re-reads it.
|
|
362
419
|
*
|
|
363
420
|
* @param accessToken - bearer for the account.
|
|
364
421
|
* @param signal - optional cancellation.
|
|
365
|
-
* @returns the ids
|
|
422
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
366
423
|
*/
|
|
367
424
|
async fetchCatalog(accessToken, signal) {
|
|
368
425
|
if (!this.options.discovery)
|
|
@@ -374,23 +431,40 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
374
431
|
...signal === undefined ? {} : { signal },
|
|
375
432
|
});
|
|
376
433
|
if (!response.ok) {
|
|
377
|
-
this.options.onWarn?.(`GLM model list unavailable (HTTP ${response.status})`);
|
|
434
|
+
this.options.onWarn?.(`GLM model list unavailable (HTTP ${response.status}); keeping the last catalog`);
|
|
378
435
|
return undefined;
|
|
379
436
|
}
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
437
|
+
const payload = await response.json();
|
|
438
|
+
const rows = Array.isArray(payload.data) ? payload.data : [];
|
|
439
|
+
const entries = this.adoptListing(rows);
|
|
440
|
+
if (entries.length === 0) {
|
|
441
|
+
this.options.onWarn?.('GLM model discovery returned an empty catalog; keeping the last catalog');
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
this.live = entries;
|
|
445
|
+
// The cached table is the endpoint's answer, kept so the next session does not ask
|
|
446
|
+
// again. A cache failure never fails the read that produced it.
|
|
447
|
+
await this.saveListing(rows);
|
|
448
|
+
return entries.map((entry) => entry.id);
|
|
388
449
|
}
|
|
389
450
|
catch (error) {
|
|
451
|
+
if (signal?.aborted === true)
|
|
452
|
+
return undefined;
|
|
390
453
|
this.options.onWarn?.(`GLM model list unavailable (${error instanceof Error ? error.message : String(error)})`);
|
|
391
454
|
return undefined;
|
|
392
455
|
}
|
|
393
456
|
}
|
|
457
|
+
/** Persist one live listing, reporting a cache failure without failing the read. */
|
|
458
|
+
async saveListing(rows) {
|
|
459
|
+
if (this.options.saveListing === undefined)
|
|
460
|
+
return;
|
|
461
|
+
try {
|
|
462
|
+
await this.options.saveListing(rows);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
this.options.onWarn?.(`model catalog cache write failed (${error instanceof Error ? error.message : String(error)})`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
394
468
|
/** Send one streaming request over the Responses wire. */
|
|
395
469
|
async request(options, model, accessToken, signal) {
|
|
396
470
|
const resolveImage = this.imageResolver(signal);
|
|
@@ -446,9 +520,22 @@ export class GlmAdapter extends LlmAdapter {
|
|
|
446
520
|
return this.catalog();
|
|
447
521
|
}
|
|
448
522
|
}
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
523
|
+
/**
|
|
524
|
+
* The rows of one cached listing.
|
|
525
|
+
*
|
|
526
|
+
* The cache holds the rows this plugin stored; a payload written as the endpoint's own
|
|
527
|
+
* envelope is read too, so a differently shaped cache cannot silently empty the picker.
|
|
528
|
+
*
|
|
529
|
+
* @param payload - whatever `cachedListing` returned.
|
|
530
|
+
* @returns the rows to adopt, or an empty list when the payload names none.
|
|
531
|
+
*/
|
|
532
|
+
function listingRows(payload) {
|
|
533
|
+
if (Array.isArray(payload))
|
|
534
|
+
return payload;
|
|
535
|
+
const data = typeof payload === 'object' && payload !== null
|
|
536
|
+
? payload.data
|
|
537
|
+
: undefined;
|
|
538
|
+
return Array.isArray(data) ? data : [];
|
|
452
539
|
}
|
|
453
540
|
/** The reasoning block for a catalog entry, or undefined when it supports none. */
|
|
454
541
|
function reasoningInfo(entry) {
|