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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last model listing each route read from its own endpoint, kept on disk.
|
|
3
|
+
*
|
|
4
|
+
* A live listing is the authority on which models a subscription serves and what
|
|
5
|
+
* each one is: its display name, context window, input modalities and reasoning
|
|
6
|
+
* ladder. Asking for it on every picker query would spend a request per refresh and
|
|
7
|
+
* still show nothing while the endpoint is unreachable, so the answer is read once,
|
|
8
|
+
* cached here, and reused as the route's catalog until the next discovery.
|
|
9
|
+
*
|
|
10
|
+
* That cache replaces the model table this build used to ship by hand: the same
|
|
11
|
+
* curated list, except it is now the endpoint's own answer rather than a roster that
|
|
12
|
+
* goes stale between releases. A hardcoded table cannot learn that a model was
|
|
13
|
+
* released, renamed or retired; a cached live read can, and it is read from disk
|
|
14
|
+
* rather than the network on the path that matters.
|
|
15
|
+
*
|
|
16
|
+
* The endpoint's own payload is stored verbatim rather than a pre-digested catalog, so
|
|
17
|
+
* the route re-derives everything it publishes — picker rows, fast variants, context
|
|
18
|
+
* variants — from the same parse it uses on a fresh read. A later build that reads more
|
|
19
|
+
* of a payload therefore benefits from a cache written by an earlier one.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-plugin-subscription-oauth/providers/model-cache
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Keep each route's last live listing on disk.
|
|
25
|
+
*
|
|
26
|
+
* Reads are tolerant: a missing, unreadable, unparsable or stale-version file yields
|
|
27
|
+
* no cache rather than an error, because a cache miss must only mean "ask the
|
|
28
|
+
* endpoint", never a broken picker. Writes are atomic, so a crash mid-write cannot
|
|
29
|
+
* leave a half-written document behind.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ModelCatalogCache {
|
|
32
|
+
private readonly file;
|
|
33
|
+
/**
|
|
34
|
+
* @param file - absolute path of the cache document.
|
|
35
|
+
*/
|
|
36
|
+
constructor(file: string);
|
|
37
|
+
/**
|
|
38
|
+
* Whatever one route last cached.
|
|
39
|
+
*
|
|
40
|
+
* The payload is opaque here: a route writes what it needs to rebuild its catalog
|
|
41
|
+
* (Kimi stores the decoded rows of `/models`; a route that derives fast variants from
|
|
42
|
+
* a second listing stores both). It re-validates on read.
|
|
43
|
+
*
|
|
44
|
+
* @param provider - provider route key.
|
|
45
|
+
* @returns the cached payload, or undefined when there is no usable cache.
|
|
46
|
+
*/
|
|
47
|
+
read(provider: string): Promise<unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Record what the endpoint just reported for one route.
|
|
50
|
+
*
|
|
51
|
+
* @param provider - provider route key.
|
|
52
|
+
* @param payload - what that route needs to rebuild its catalog.
|
|
53
|
+
*/
|
|
54
|
+
write(provider: string, payload: unknown): Promise<void>;
|
|
55
|
+
/** Load the document, or undefined when it is absent or unusable. */
|
|
56
|
+
private load;
|
|
57
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last model listing each route read from its own endpoint, kept on disk.
|
|
3
|
+
*
|
|
4
|
+
* A live listing is the authority on which models a subscription serves and what
|
|
5
|
+
* each one is: its display name, context window, input modalities and reasoning
|
|
6
|
+
* ladder. Asking for it on every picker query would spend a request per refresh and
|
|
7
|
+
* still show nothing while the endpoint is unreachable, so the answer is read once,
|
|
8
|
+
* cached here, and reused as the route's catalog until the next discovery.
|
|
9
|
+
*
|
|
10
|
+
* That cache replaces the model table this build used to ship by hand: the same
|
|
11
|
+
* curated list, except it is now the endpoint's own answer rather than a roster that
|
|
12
|
+
* goes stale between releases. A hardcoded table cannot learn that a model was
|
|
13
|
+
* released, renamed or retired; a cached live read can, and it is read from disk
|
|
14
|
+
* rather than the network on the path that matters.
|
|
15
|
+
*
|
|
16
|
+
* The endpoint's own payload is stored verbatim rather than a pre-digested catalog, so
|
|
17
|
+
* the route re-derives everything it publishes — picker rows, fast variants, context
|
|
18
|
+
* variants — from the same parse it uses on a fresh read. A later build that reads more
|
|
19
|
+
* of a payload therefore benefits from a cache written by an earlier one.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-plugin-subscription-oauth/providers/model-cache
|
|
22
|
+
*/
|
|
23
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
/**
|
|
26
|
+
* On-disk shape version.
|
|
27
|
+
*
|
|
28
|
+
* Bumped when a route's parse of a cached payload changes meaning, so a document
|
|
29
|
+
* written by an older understanding is discarded rather than misread.
|
|
30
|
+
*/
|
|
31
|
+
const CACHE_VERSION = 1;
|
|
32
|
+
/**
|
|
33
|
+
* Keep each route's last live listing on disk.
|
|
34
|
+
*
|
|
35
|
+
* Reads are tolerant: a missing, unreadable, unparsable or stale-version file yields
|
|
36
|
+
* no cache rather than an error, because a cache miss must only mean "ask the
|
|
37
|
+
* endpoint", never a broken picker. Writes are atomic, so a crash mid-write cannot
|
|
38
|
+
* leave a half-written document behind.
|
|
39
|
+
*/
|
|
40
|
+
export class ModelCatalogCache {
|
|
41
|
+
file;
|
|
42
|
+
/**
|
|
43
|
+
* @param file - absolute path of the cache document.
|
|
44
|
+
*/
|
|
45
|
+
constructor(file) {
|
|
46
|
+
this.file = file;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whatever one route last cached.
|
|
50
|
+
*
|
|
51
|
+
* The payload is opaque here: a route writes what it needs to rebuild its catalog
|
|
52
|
+
* (Kimi stores the decoded rows of `/models`; a route that derives fast variants from
|
|
53
|
+
* a second listing stores both). It re-validates on read.
|
|
54
|
+
*
|
|
55
|
+
* @param provider - provider route key.
|
|
56
|
+
* @returns the cached payload, or undefined when there is no usable cache.
|
|
57
|
+
*/
|
|
58
|
+
async read(provider) {
|
|
59
|
+
const document = await this.load();
|
|
60
|
+
const cached = document?.providers[provider];
|
|
61
|
+
if (cached === undefined || cached.payload === undefined || cached.payload === null)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (Array.isArray(cached.payload) && cached.payload.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
return cached.payload;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Record what the endpoint just reported for one route.
|
|
69
|
+
*
|
|
70
|
+
* @param provider - provider route key.
|
|
71
|
+
* @param payload - what that route needs to rebuild its catalog.
|
|
72
|
+
*/
|
|
73
|
+
async write(provider, payload) {
|
|
74
|
+
if (payload === undefined || payload === null)
|
|
75
|
+
return;
|
|
76
|
+
if (Array.isArray(payload) && payload.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
const document = await this.load() ?? { version: CACHE_VERSION, providers: {} };
|
|
79
|
+
document.providers[provider] = { fetchedAt: Date.now(), payload };
|
|
80
|
+
await mkdir(path.dirname(this.file), { recursive: true });
|
|
81
|
+
// Write beside the target and rename, so a reader never sees a partial document.
|
|
82
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
83
|
+
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
|
|
84
|
+
await rename(temporary, this.file);
|
|
85
|
+
}
|
|
86
|
+
/** Load the document, or undefined when it is absent or unusable. */
|
|
87
|
+
async load() {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(await readFile(this.file, 'utf8'));
|
|
90
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
91
|
+
return undefined;
|
|
92
|
+
const record = parsed;
|
|
93
|
+
if (record['version'] !== CACHE_VERSION)
|
|
94
|
+
return undefined;
|
|
95
|
+
const providers = record['providers'];
|
|
96
|
+
if (typeof providers !== 'object' || providers === null || Array.isArray(providers))
|
|
97
|
+
return undefined;
|
|
98
|
+
return { version: CACHE_VERSION, providers: providers };
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Absent, unreadable or corrupt: a miss, not a failure.
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -57,6 +57,14 @@ export interface ResponsesStreamInput<S extends SessionBase> {
|
|
|
57
57
|
models?: () => Promise<readonly string[]>;
|
|
58
58
|
/** Called when a call proved the stored credential unusable. */
|
|
59
59
|
onCredentialRejected?: (account: string) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Called when the endpoint answered a request by naming the model id to use instead.
|
|
62
|
+
*
|
|
63
|
+
* The request is retried with that id, so the turn still succeeds; the caller records
|
|
64
|
+
* the rename so the next turn goes straight to the current id instead of learning it
|
|
65
|
+
* again from a refused request.
|
|
66
|
+
*/
|
|
67
|
+
onModelRenamed?: (from: string, to: string) => void;
|
|
60
68
|
/** Rewrite a thrown failure before it is mapped, e.g. to name the fix. */
|
|
61
69
|
describeFailure?(error: unknown): unknown;
|
|
62
70
|
/** Called when a model in the chain was skipped, with the reason. */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
15
15
|
import { allowanceScheduler } from '../auth/allowance-scheduler.js';
|
|
16
16
|
import { streamResponses } from '../translate/responses.js';
|
|
17
|
-
import { httpLlmError, idleWatchdog, mapFetchFailure } from './common.js';
|
|
17
|
+
import { httpLlmError, idleWatchdog, mapFetchFailure, unknownModelReplacement } from './common.js';
|
|
18
18
|
/**
|
|
19
19
|
* Minimum beat between dispatch attempts on one route, so a refused account is not
|
|
20
20
|
* re-hit at once and a sweep across the pool does not become a burst. opencodex keeps
|
|
@@ -161,10 +161,26 @@ export async function* streamProviderResponses(options, input) {
|
|
|
161
161
|
continue;
|
|
162
162
|
let response = await request(options, model, token.accessToken, watchdog.signal);
|
|
163
163
|
if (response.status === 401 || response.status === 403) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// Two different failures share this status: a credential the provider no
|
|
165
|
+
// longer accepts, and a model id it does not know. Only the first is answered
|
|
166
|
+
// by a token refresh — treating a model rename that way would refresh a
|
|
167
|
+
// healthy token and, worse, mark the account as needing a new sign-in. The
|
|
168
|
+
// body says which it is, so it is read before deciding, and an unknown model
|
|
169
|
+
// is retried once under the id the provider named.
|
|
170
|
+
const refusal = await httpLlmError(response, label);
|
|
171
|
+
const replacement = unknownModelReplacement(refusal.message);
|
|
172
|
+
if (replacement !== undefined) {
|
|
173
|
+
lastError = refusal;
|
|
174
|
+
input.onModelRenamed?.(model, replacement);
|
|
175
|
+
input.onFallback?.(model, replacement, reasonOf(refusal));
|
|
176
|
+
response = await request(options, replacement, token.accessToken, watchdog.signal);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
token = await tokens.resolve(token.account, true);
|
|
180
|
+
response = await request(options, model, token.accessToken, watchdog.signal);
|
|
181
|
+
if (response.status === 401 || response.status === 403) {
|
|
182
|
+
input.onCredentialRejected?.(token.account);
|
|
183
|
+
}
|
|
168
184
|
}
|
|
169
185
|
}
|
|
170
186
|
// A spent allowance moves to the next account (when the provider supports
|
|
@@ -13,8 +13,25 @@ export interface WorkBuddyProviderOptions {
|
|
|
13
13
|
credentialFile: string;
|
|
14
14
|
/** Abandon a stream after this much silence. */
|
|
15
15
|
streamIdleTimeoutMs: number;
|
|
16
|
+
/**
|
|
17
|
+
* Whether WorkBuddy's own console listing is consulted for the catalog.
|
|
18
|
+
*
|
|
19
|
+
* The listing is the authority on which models an account serves; switching this off
|
|
20
|
+
* leaves the seed table in place and reads nothing.
|
|
21
|
+
*/
|
|
22
|
+
discovery?: boolean;
|
|
16
23
|
/** Model the picker preselects. */
|
|
17
24
|
defaultModel?: string;
|
|
25
|
+
/**
|
|
26
|
+
* WorkBuddy's last listing, from this plugin's cache.
|
|
27
|
+
*
|
|
28
|
+
* The cached table is what the picker shows before any request: the console's own answer
|
|
29
|
+
* from the last read, so a release does not need a plugin update and a refresh does not
|
|
30
|
+
* 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>;
|
|
18
35
|
/** Shared model-visibility preferences. */
|
|
19
36
|
preferences: ModelPreferences;
|
|
20
37
|
/** Invoked when the catalog or its visibility changed. */
|
|
@@ -11,7 +11,7 @@ import { LOGIN_CANCELLED_MESSAGE } from '../auth/device-flow.js';
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { readSpendLedger } from './workbuddy-credits.js';
|
|
13
13
|
import { finishWorkBuddySignIn, parseWorkBuddySession, readWorkBuddyUsage, refreshWorkBuddyToken, startWorkBuddySignIn, workbuddyRefreshable, workbuddyStale, claimWorkBuddyCheckin, listWorkBuddyTasks, acceptWorkBuddyTasks, claimWorkBuddyTask, WORKBUDDY_EARLY_MS, } from '../auth/workbuddy-flow.js';
|
|
14
|
-
import { WorkBuddyAdapter, WORKBUDDY_CODE_URL,
|
|
14
|
+
import { WorkBuddyAdapter, WORKBUDDY_CODE_URL, WORKBUDDY_DEFAULT_MODEL, WORKBUDDY_FALLBACK_CONTEXT_WINDOW, WORKBUDDY_PROVIDER_ID, WORKBUDDY_PROVIDER_NAME, } from './workbuddy.js';
|
|
15
15
|
import { ProviderController } from '../auth/provider-controller.js';
|
|
16
16
|
import { proxiedFetch } from '../http.js';
|
|
17
17
|
/** Wait, honouring cancellation. */
|
|
@@ -133,12 +133,17 @@ export function createWorkBuddyProvider(options) {
|
|
|
133
133
|
});
|
|
134
134
|
const spendFile = path.join(path.dirname(options.credentialFile), 'workbuddy-spend.json');
|
|
135
135
|
const adapter = new WorkBuddyAdapter({
|
|
136
|
+
// Consulting the console's listing is what keeps the catalog from going stale: a model
|
|
137
|
+
// released, renamed or retired server-side appears without a plugin update.
|
|
138
|
+
discovery: options.discovery ?? true,
|
|
136
139
|
streamIdleTimeoutMs: options.streamIdleTimeoutMs,
|
|
137
140
|
tokens,
|
|
138
141
|
spendFile,
|
|
139
142
|
...options.onWarn === undefined ? {} : { onWarn: options.onWarn },
|
|
140
143
|
hiddenModels: () => options.preferences.hiddenIds(WORKBUDDY_PROVIDER_ID),
|
|
141
144
|
userModels: () => options.preferences.extraIds(WORKBUDDY_PROVIDER_ID),
|
|
145
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
146
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
142
147
|
fetchFn,
|
|
143
148
|
});
|
|
144
149
|
/** The stored session's fields as the token a request or meter signs with. */
|
|
@@ -157,13 +162,17 @@ export function createWorkBuddyProvider(options) {
|
|
|
157
162
|
displayName: WORKBUDDY_PROVIDER_NAME,
|
|
158
163
|
codeUrl: WORKBUDDY_CODE_URL,
|
|
159
164
|
credentialFile: options.credentialFile,
|
|
160
|
-
// WorkBuddy
|
|
161
|
-
|
|
165
|
+
// WorkBuddy's own console route lists the models an account may use; it is read once
|
|
166
|
+
// and cached, and a picker query never asks the endpoint.
|
|
167
|
+
discovery: options.discovery ?? true,
|
|
162
168
|
defaultModel: options.defaultModel ?? WORKBUDDY_DEFAULT_MODEL,
|
|
163
169
|
catalog: () => adapter.catalogEntries(),
|
|
164
170
|
catalogOwn: () => adapter.ownCatalogEntries(),
|
|
165
|
-
fallbackContextWindow:
|
|
166
|
-
|
|
171
|
+
fallbackContextWindow: WORKBUDDY_FALLBACK_CONTEXT_WINDOW,
|
|
172
|
+
// The token is handed over rather than the session it came from; the adapter reads the
|
|
173
|
+
// session back, so each account is attributed to its own enterprise.
|
|
174
|
+
discover: (accessToken) => adapter.fetchCatalog(accessToken),
|
|
175
|
+
applyDiscovery: (ids) => { adapter.discover(ids); },
|
|
167
176
|
usage: async (_accessToken, account) => {
|
|
168
177
|
const held = await store.get(account);
|
|
169
178
|
if (held === undefined)
|
|
@@ -178,7 +187,6 @@ export function createWorkBuddyProvider(options) {
|
|
|
178
187
|
{ key: 'endpoint', value: 'https://copilot.tencent.com' },
|
|
179
188
|
{ key: 'credentialFile', value: options.credentialFile },
|
|
180
189
|
],
|
|
181
|
-
applyDiscovery: () => { },
|
|
182
190
|
};
|
|
183
191
|
const controller = new ProviderController({
|
|
184
192
|
spec,
|
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* WorkBuddy chats are chat completions at `https://copilot.tencent.com/v2/chat/completions`,
|
|
5
5
|
* streamed only, and the API refuses a request whose User-Agent is not
|
|
6
|
-
* `WorkBuddy/<version>`. The route
|
|
7
|
-
*
|
|
6
|
+
* `WorkBuddy/<version>`. The model table this route publishes is the console's own
|
|
7
|
+
* listing — `GET /console/enterprises/<enterpriseId || personal>/models`, the route
|
|
8
|
+
* WorkBuddy's web console reads — fetched once and kept in this plugin's cache (see
|
|
9
|
+
* {@link WorkBuddyAdapter.hydrate} and {@link WorkBuddyAdapter.fetchCatalog}).
|
|
10
|
+
* {@link WORKBUDDY_CATALOG} is the cold-start seed used until a listing has been read,
|
|
11
|
+
* the fallback when a read fails, and the metadata overlay for a row the listing
|
|
12
|
+
* describes only partly, so a model released, renamed or retired server-side reaches the
|
|
13
|
+
* picker without a plugin update and without a request per refresh.
|
|
14
|
+
* Adapted from magpie's `workbuddy` for this plugin's contracts.
|
|
8
15
|
*
|
|
9
16
|
* @module dsh-plugin-subscription-oauth/providers/workbuddy
|
|
10
17
|
*/
|
|
@@ -19,8 +26,28 @@ export declare const WORKBUDDY_PROVIDER_ID = "workbuddy";
|
|
|
19
26
|
export declare const WORKBUDDY_PROVIDER_NAME = "WorkBuddy";
|
|
20
27
|
/** Where the Settings page sends someone to get an account. */
|
|
21
28
|
export declare const WORKBUDDY_CODE_URL = "https://copilot.tencent.com";
|
|
22
|
-
/**
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The model the picker preselects.
|
|
31
|
+
*
|
|
32
|
+
* The console's own default, and a row its listing actually serves, so the page's
|
|
33
|
+
* "recommended" marker never names a model the picker cannot offer. The previous value
|
|
34
|
+
* (`hy4-preview-f`) was a seed-table spelling the live listing does not serve — it offers
|
|
35
|
+
* `hy4-preview` — which is the staleness this route no longer keeps in its catalog.
|
|
36
|
+
*/
|
|
37
|
+
export declare const WORKBUDDY_DEFAULT_MODEL = "auto";
|
|
38
|
+
/** Context advertised for a listed id neither the listing nor the seed table describes. */
|
|
39
|
+
export declare const WORKBUDDY_FALLBACK_CONTEXT_WINDOW = 168000;
|
|
40
|
+
/**
|
|
41
|
+
* The console route that lists the models one account may use.
|
|
42
|
+
*
|
|
43
|
+
* The scope segment is the enterprise the session is attributed to, or `personal` for an
|
|
44
|
+
* account that states none. This is the route WorkBuddy's own console reads; it is not
|
|
45
|
+
* one of the plugin's `/v2` API routes, which is why probing those found nothing.
|
|
46
|
+
*
|
|
47
|
+
* @param enterpriseId - the session's enterprise, when it has one.
|
|
48
|
+
* @returns the absolute listing URL.
|
|
49
|
+
*/
|
|
50
|
+
export declare function workbuddyModelsUrl(enterpriseId?: string): string;
|
|
24
51
|
/** One signed-in WorkBuddy session, as stored. */
|
|
25
52
|
export interface WorkBuddyStored {
|
|
26
53
|
accessToken: string;
|
|
@@ -34,15 +61,35 @@ export interface WorkBuddyStored {
|
|
|
34
61
|
tokenType?: string;
|
|
35
62
|
refreshExpiresAt?: number;
|
|
36
63
|
}
|
|
37
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The seed table the route falls back to when no listing has been read (see
|
|
66
|
+
* {@link CN_MODELS}).
|
|
67
|
+
*
|
|
68
|
+
* A table this build ships, never the roster: WorkBuddy's own listing decides which
|
|
69
|
+
* models exist, and each row's own figures decide what they are. This is only the
|
|
70
|
+
* cold-start membership and the overlay for what a listing leaves unsaid.
|
|
71
|
+
*/
|
|
38
72
|
export declare const WORKBUDDY_CATALOG: readonly CatalogEntry[];
|
|
39
73
|
/** Constructor dependencies for {@link WorkBuddyAdapter}. */
|
|
40
74
|
export interface WorkBuddyAdapterOptions {
|
|
75
|
+
/** Whether the console's live model listing is consulted. */
|
|
76
|
+
discovery: boolean;
|
|
41
77
|
streamIdleTimeoutMs: number;
|
|
42
78
|
tokens: TokenManager<WorkBuddySession>;
|
|
43
79
|
onWarn?: (message: string) => void;
|
|
44
80
|
hiddenModels?: () => Promise<ReadonlySet<string>>;
|
|
45
81
|
userModels?: () => Promise<readonly string[]>;
|
|
82
|
+
/**
|
|
83
|
+
* The listing WorkBuddy's console answered last, as this plugin's cache kept it.
|
|
84
|
+
*
|
|
85
|
+
* Read once per session, before the first catalog query, so the picker offers the real
|
|
86
|
+
* models without asking the console anything. A route that fetched once and cached the
|
|
87
|
+
* answer has no reason to fetch again on every refresh; the Settings "refresh models"
|
|
88
|
+
* action is what re-reads it.
|
|
89
|
+
*/
|
|
90
|
+
cachedListing?: () => Promise<unknown>;
|
|
91
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
92
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
46
93
|
/** Where per-account credit spends accumulate; beside the credential file. */
|
|
47
94
|
spendFile?: string;
|
|
48
95
|
fetchFn?: FetchFn;
|
|
@@ -51,19 +98,101 @@ export interface WorkBuddyAdapterOptions {
|
|
|
51
98
|
export declare class WorkBuddyAdapter extends LlmAdapter {
|
|
52
99
|
private readonly options;
|
|
53
100
|
private readonly fetchFn;
|
|
101
|
+
/**
|
|
102
|
+
* The catalog this route serves: entries derived from WorkBuddy's own listing, either
|
|
103
|
+
* the one just read or the one the cache kept from the last read.
|
|
104
|
+
*
|
|
105
|
+
* Undefined means neither source has produced a listing yet, so the seed table
|
|
106
|
+
* ({@link WORKBUDDY_CATALOG}) is what the picker shows.
|
|
107
|
+
*/
|
|
108
|
+
private live;
|
|
109
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
110
|
+
private hydrated;
|
|
54
111
|
constructor(options: WorkBuddyAdapterOptions);
|
|
55
112
|
providerInfo(provider: string): LlmProviderInfo;
|
|
113
|
+
/**
|
|
114
|
+
* Adopt one listing as this route's catalog.
|
|
115
|
+
*
|
|
116
|
+
* Everything the picker shows is derived here from WorkBuddy's own answer: which models
|
|
117
|
+
* exist (the account's `cli` agent when the listing states one), in the order that
|
|
118
|
+
* listing states them, each with the name, window, modalities and reasoning rung its row
|
|
119
|
+
* states. The seed table is consulted only for what a row leaves unsaid, so a live
|
|
120
|
+
* figure is never overridden by a shipped guess.
|
|
121
|
+
*
|
|
122
|
+
* @param payload - the decoded listing, or the `{models, agents}` object a cache kept.
|
|
123
|
+
* @returns the entries to publish, in picker order.
|
|
124
|
+
*/
|
|
125
|
+
private adoptListing;
|
|
126
|
+
/**
|
|
127
|
+
* Read the cached listing, once, so the picker works without asking the console.
|
|
128
|
+
*
|
|
129
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
130
|
+
* failure: the route falls back to its seed table until a listing is fetched, so a
|
|
131
|
+
* broken cache can never empty the picker.
|
|
132
|
+
*/
|
|
133
|
+
hydrate(): Promise<void>;
|
|
56
134
|
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
57
135
|
private hidden;
|
|
136
|
+
/**
|
|
137
|
+
* The catalog to present: the models WorkBuddy's listing named, in the order its own
|
|
138
|
+
* console states them, each with the metadata that listing stated.
|
|
139
|
+
*
|
|
140
|
+
* The seed table is the membership only while no listing has been read, and the metadata
|
|
141
|
+
* overlay for a row the listing describes partly. It is never allowed to add a row back:
|
|
142
|
+
* a model the console stopped serving must leave the picker without a plugin update,
|
|
143
|
+
* which is the whole point of reading the listing instead of shipping a roster.
|
|
144
|
+
*/
|
|
145
|
+
private shippedCatalog;
|
|
58
146
|
/** The catalog the Settings list reads. */
|
|
59
147
|
catalogEntries(): readonly CatalogEntry[];
|
|
60
148
|
/** The entries the route itself serves, without hand-added extras. */
|
|
61
149
|
ownCatalogEntries(): readonly CatalogEntry[];
|
|
150
|
+
/**
|
|
151
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
152
|
+
*
|
|
153
|
+
* {@link fetchCatalog} already derived the entries from the listing it read, and the
|
|
154
|
+
* controller reports those same ids back here; the entries it recorded are kept, so a
|
|
155
|
+
* live name, window or rung is not replaced by the seed table's. A caller that only has
|
|
156
|
+
* ids (a test, another discovery path) gets the seed table's metadata where it knows the
|
|
157
|
+
* id and conservative entries otherwise.
|
|
158
|
+
*
|
|
159
|
+
* @param ids - model ids the caller reports.
|
|
160
|
+
*/
|
|
161
|
+
discover(ids: readonly string[]): void;
|
|
62
162
|
resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
163
|
+
/**
|
|
164
|
+
* Read WorkBuddy's own console listing and adopt it as this route's catalog.
|
|
165
|
+
*
|
|
166
|
+
* This is the one place a model table is produced, and it is WorkBuddy's own answer: the
|
|
167
|
+
* account's `cli` agent decides which models are available, and each row states its own
|
|
168
|
+
* name, window, modalities and reasoning rung (see {@link adoptListing}). The payload is
|
|
169
|
+
* also handed to {@link WorkBuddyAdapterOptions.saveListing}, so the next session starts
|
|
170
|
+
* from this table instead of asking again — the Settings "refresh models" action is what
|
|
171
|
+
* re-reads it, and a picker query never does.
|
|
172
|
+
*
|
|
173
|
+
* A failed or empty read keeps the catalog the route already had, and says so once.
|
|
174
|
+
*
|
|
175
|
+
* @param accessToken - bearer for the account.
|
|
176
|
+
* @param enterpriseId - the enterprise to read; taken from the stored session when omitted.
|
|
177
|
+
* @param signal - optional cancellation.
|
|
178
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
179
|
+
*/
|
|
180
|
+
fetchCatalog(accessToken: string, enterpriseId?: string, signal?: AbortSignal): Promise<readonly string[] | undefined>;
|
|
181
|
+
/**
|
|
182
|
+
* The stored session one access token belongs to.
|
|
183
|
+
*
|
|
184
|
+
* Discovery is handed the account's token rather than the session it came from, and the
|
|
185
|
+
* console route is attributed by that session's own fields, so the session is read back
|
|
186
|
+
* here. The first account whose stored token matches wins; a token that matches none
|
|
187
|
+
* falls back to the route's current session. A read failure is never fatal — the listing
|
|
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.
|
|
192
|
+
*/
|
|
193
|
+
private sessionFor;
|
|
63
194
|
providerRetryPolicy(): undefined;
|
|
64
195
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
65
|
-
/** The current turn's billed credit, read off the last usage frame. */
|
|
66
|
-
private turnCredit;
|
|
67
196
|
/** The stored session's fields as the token the request signs with. */
|
|
68
197
|
private tokenOf;
|
|
69
198
|
private requestBody;
|
|
@@ -72,21 +201,19 @@ export declare class WorkBuddyAdapter extends LlmAdapter {
|
|
|
72
201
|
*
|
|
73
202
|
* A tool name may hold only letters, digits, `_`, `-`; another character is refused, and
|
|
74
203
|
* a schema with a union at the parameter root or a `\0` escape is turned away by the
|
|
75
|
-
* model. Each unsafe name is made safe and the safe→original map
|
|
76
|
-
* the model's calls can be renamed back in the answer. Adapted from magpie's fitTools.
|
|
204
|
+
* model. Each unsafe name is made safe and the safe→original map returned to the caller
|
|
205
|
+
* so the model's calls can be renamed back in the answer. Adapted from magpie's fitTools.
|
|
77
206
|
*
|
|
78
207
|
* @param tools - the declared tool schemas, when any.
|
|
208
|
+
* @param history - the request's assistant/tool turns, renamed in place to the safe names.
|
|
79
209
|
* @returns the mended wire tools, or undefined when there are none.
|
|
80
210
|
*/
|
|
81
211
|
private fittedTools;
|
|
82
|
-
/** The safe→original tool names to restore in the answer. */
|
|
83
|
-
private safeToolNames;
|
|
84
|
-
/** The assistant/tool history of the request being built, for renaming past calls. */
|
|
85
|
-
private historyMessages;
|
|
86
212
|
/**
|
|
87
213
|
* Restore the original tool names the model was given, in one parsed chunk.
|
|
88
214
|
*
|
|
89
215
|
* @param value - a decoded SSE payload.
|
|
216
|
+
* @param names - the safe→original map the request that produced this stream mended.
|
|
90
217
|
* @returns whether any name changed.
|
|
91
218
|
*/
|
|
92
219
|
private namedBack;
|