@askdkc/dsh-cli 0.12.3 → 0.13.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/cordis.patch.yml +12 -131
- package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
- package/lib/types/adapter/channel/session-projection.js +0 -7
- package/lib/types/adapter/ports/channel-view.d.ts +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/dsh-adapter/bundled-presets.d.ts +3 -8
- package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/bundled-presets.js +5 -20
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +2 -9
- package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/external-commands.js +9 -33
- package/lib/types/dsh-adapter/channel/local-actions.d.ts +2 -1
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/local-actions.js +6 -2
- package/lib/types/dsh-adapter/channel/model-actions.js +3 -3
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +0 -1
- package/lib/types/dsh-adapter/channel/permissions.d.ts +1 -6
- package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/permissions.js +2 -54
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +15 -70
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-fork.js +2 -5
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +1 -4
- package/lib/types/dsh-adapter/channel/session-resume.js +2 -2
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +0 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +1 -2
- package/lib/types/dsh-adapter/channel/session-tree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree.js +9 -62
- package/lib/types/dsh-adapter/channel/skill-catalog.js +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +2 -8
- package/lib/types/dsh-adapter/channel/transcript.d.ts +0 -16
- package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/transcript.js +0 -43
- package/lib/types/dsh-adapter/channel.js +2 -2
- package/lib/types/dsh-adapter/compat/index.d.ts +5 -25
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +5 -25
- package/lib/types/dsh-adapter/compat/liveSession.d.ts +12 -23
- package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/liveSession.js +15 -68
- package/lib/types/dsh-adapter/compat/messages.d.ts +1 -3
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/messages.js +2 -10
- package/lib/types/dsh-adapter/compat/persistence.d.ts +2 -3
- package/lib/types/dsh-adapter/compat/persistence.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/persistence.js +9 -14
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +10 -144
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +45 -275
- package/lib/types/dsh-adapter/compat/settings.d.ts +4 -10
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/settings.js +6 -23
- package/lib/types/dsh-adapter/compat/shell.d.ts +2 -3
- package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/shell.js +2 -7
- package/lib/types/dsh-adapter/contract.d.ts +4 -34
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +8 -83
- package/lib/types/dsh-adapter/packaged-presets.d.ts +0 -19
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/packaged-presets.js +1 -83
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +26 -183
- package/lib/types/dsh-adapter/preset-resolution.d.ts +1 -22
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/preset-resolution.js +0 -34
- package/lib/types/dsh-adapter/presets.d.ts +1 -4
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +3 -6
- package/lib/types/dsh-adapter/questions-answerer.d.ts +5 -43
- package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions-answerer.js +7 -63
- package/lib/types/dsh-adapter/sessionTree.d.ts +0 -19
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessionTree.js +2 -112
- package/lib/types/dsh-adapter/sessions/header.d.ts +2 -2
- package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/header.js +2 -8
- package/lib/types/dsh-adapter/sessions/list.d.ts +7 -28
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/list.js +48 -103
- package/lib/types/dsh-adapter/sessions/store.d.ts +1 -2
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/store.js +7 -10
- package/lib/types/dsh-adapter/sharp.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sharp.js +0 -1
- package/lib/types/dsh-adapter/subagents.d.ts +1 -2
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +1 -12
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +1 -1
- package/lib/types/dsh-adapter/trajectory/aggregate.js +1 -1
- package/lib/types/dsh-adapter/trajectory/guards.d.ts +5 -22
- package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -1
- package/lib/types/dsh-adapter/trajectory/guards.js +5 -23
- package/lib/types/dsh-adapter/trajectory/projection.d.ts +5 -21
- package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/trajectory/projection.js +4 -38
- package/lib/types/dsh-adapter/trajectory/types.d.ts +1 -1
- package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -1
- package/lib/types/i18n.d.ts +2 -22
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +2 -10
- package/lib/types/presetPrefs.d.ts +1 -3
- package/lib/types/presetPrefs.d.ts.map +1 -1
- package/lib/types/presetPrefs.js +2 -4
- package/lib/types/sessionHistory.d.ts +2 -0
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessionHistory.js +8 -0
- package/node_modules/@askdkc/dsh-auth/README.ja.md +28 -9
- package/node_modules/@askdkc/dsh-auth/README.md +19 -8
- package/node_modules/@askdkc/dsh-auth/dsh-plugin.json +3 -3
- package/node_modules/@askdkc/dsh-auth/lib/THIRD_PARTY_NOTICES.txt +168 -0
- package/node_modules/@askdkc/dsh-auth/lib/abortable.d.ts +2 -0
- package/node_modules/@askdkc/dsh-auth/lib/abortable.js +9 -0
- package/node_modules/@askdkc/dsh-auth/lib/auth-contract.d.ts +66 -0
- package/node_modules/@askdkc/dsh-auth/lib/auth-contract.js +1 -0
- package/node_modules/@askdkc/dsh-auth/lib/command.js +15 -1
- package/node_modules/@askdkc/dsh-auth/lib/credentials.d.ts +8 -14
- package/node_modules/@askdkc/dsh-auth/lib/credentials.js +1 -1
- package/node_modules/@askdkc/dsh-auth/lib/index.d.ts +8 -28
- package/node_modules/@askdkc/dsh-auth/lib/index.js +67 -65
- package/node_modules/@askdkc/dsh-auth/lib/interaction.d.ts +5 -5
- package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.d.ts +37 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.js +206 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.d.ts +72 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.js +252 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.d.ts +6 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.js +93 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.d.ts +113 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.js +2866 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.d.ts +15 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.js +141 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.d.ts +5 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js +43465 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js.map +7 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.d.ts +5 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.js +69 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-routes.d.ts +32 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-routes.js +45 -0
- package/node_modules/@askdkc/dsh-auth/lib/profiles.d.ts +2 -20
- package/node_modules/@askdkc/dsh-auth/lib/profiles.js +6 -41
- package/node_modules/@askdkc/dsh-auth/lib/routes.d.ts +20 -0
- package/node_modules/@askdkc/dsh-auth/lib/routes.js +13 -0
- package/node_modules/@askdkc/dsh-auth/lib/service.d.ts +11 -2
- package/node_modules/@askdkc/dsh-auth/lib/service.js +26 -16
- package/node_modules/@askdkc/dsh-auth/package.json +26 -15
- package/package.json +100 -96
- package/lib/types/dsh-adapter/providerGuard.d.ts +0 -75
- package/lib/types/dsh-adapter/providerGuard.d.ts.map +0 -1
- package/lib/types/dsh-adapter/providerGuard.js +0 -104
- package/lib/types/migrationPrefs.d.ts +0 -60
- package/lib/types/migrationPrefs.d.ts.map +0 -1
- package/lib/types/migrationPrefs.js +0 -97
- package/node_modules/@askdkc/dsh-auth/lib/fresh-models.d.ts +0 -59
- package/node_modules/@askdkc/dsh-auth/lib/fresh-models.js +0 -246
- package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.d.ts +0 -321
- package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.js +0 -528
- package/presets/liangshen/.dsh-tui-managed.json +0 -5
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-auth — provider sign-in as LLM provider routes.
|
|
3
3
|
*
|
|
4
|
-
* One cordis plugin mounts
|
|
4
|
+
* One cordis plugin mounts owned OpenCode routes and legacy pi profiles as
|
|
5
5
|
* `llm` registry routes, so
|
|
6
|
-
* catalog models appear in every model picker after sign-in.
|
|
6
|
+
* catalog models appear in every model picker after sign-in. OpenCode owns
|
|
7
|
+
* its public catalog and bundled SDK transports. Other routes use `PiAiAdapter` with this
|
|
7
8
|
* plugin's file-backed pi-ai `CredentialStore` injected
|
|
8
9
|
* (`PiAiAuthInjection`): requests resolve stored credentials through the
|
|
9
10
|
* provider's own auth or an explicit API key. Refreshes hold the store lock.
|
|
@@ -31,14 +32,12 @@
|
|
|
31
32
|
*
|
|
32
33
|
* @module dsh-auth
|
|
33
34
|
*/
|
|
34
|
-
import { existsSync } from 'node:fs';
|
|
35
|
-
import { homedir } from 'node:os';
|
|
36
|
-
import { join } from 'node:path';
|
|
37
35
|
import z from '@deepseek-ai/schemastery';
|
|
38
|
-
import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
39
36
|
import { CredentialFile, defaultCredentialsFile } from './credentials.js';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
37
|
+
import { AUTH_PROVIDER_IDS, canonicalProvider } from './routes.js';
|
|
38
|
+
import { OpenCodeAdapter } from './opencode-adapter.js';
|
|
39
|
+
import { OpenCodeCatalog, OPEN_CODE_ROUTES } from './opencode-catalog.js';
|
|
40
|
+
import { OPEN_CODE_SNAPSHOTS } from './opencode-owned.generated.js';
|
|
42
41
|
import { refreshNous } from './nous-oauth.js';
|
|
43
42
|
import { createDshAuthApi, DshAuthService } from './service.js';
|
|
44
43
|
import { createAuthCommandHandler } from './command.js';
|
|
@@ -67,49 +66,11 @@ export { createDshAuthApi } from './service.js';
|
|
|
67
66
|
export { QuestionBridge, describeEvent } from './interaction.js';
|
|
68
67
|
export { copyToClipboard, openInBrowser, openerFor } from './opener.js';
|
|
69
68
|
export { CredentialFile, defaultCredentialsFile } from './credentials.js';
|
|
70
|
-
export { OAUTH_PROVIDER_IDS, CATALOG_PROVIDER_IDS, AUTH_PROVIDER_IDS, canonicalProvider
|
|
71
|
-
export {
|
|
72
|
-
|
|
73
|
-
* The ambient auth context providers may consult while resolving their own
|
|
74
|
-
* auth. `env()` answers from the process environment; `fileExists()` answers
|
|
75
|
-
* about the host process's filesystem (the paths a provider asks about —
|
|
76
|
-
* `~/.aws/credentials` and friends — are facts about where this process
|
|
77
|
-
* runs, not about the project under edit).
|
|
78
|
-
*/
|
|
79
|
-
function hostAuthContext() {
|
|
80
|
-
return {
|
|
81
|
-
env: async (name) => process.env[name],
|
|
82
|
-
fileExists: path => Promise.resolve(path.startsWith('~/') ? existsSync(join(homedir(), path.slice(2))) : existsSync(path)),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* The adapter the plugin registers: a {@link PiAiAdapter} whose *advisory
|
|
87
|
-
* catalog* is credential-gated. A provider with no stored credential
|
|
88
|
-
* lists no models — its rows never reach any model picker, which is the
|
|
89
|
-
* whole point: picking a model that would only fail with "not signed in"
|
|
90
|
-
* is noise. The gate only shapes `listModels`; `resolveModel` and requests
|
|
91
|
-
* are untouched, so a model id already saved in a session (or named
|
|
92
|
-
* explicitly) keeps resolving exactly as the registry contract promises
|
|
93
|
-
* ("advisory and never changes routing").
|
|
94
|
-
*
|
|
95
|
-
* An expired-but-stored credential still lists: token refresh runs on the
|
|
96
|
-
* next request, and a picker that hid a refreshable provider would look
|
|
97
|
-
* signed-out when it is not.
|
|
98
|
-
*/
|
|
99
|
-
export class CredentialGatedAdapter extends PiAiAdapter {
|
|
100
|
-
hasCredential;
|
|
101
|
-
constructor(options, hasCredential) {
|
|
102
|
-
super(options);
|
|
103
|
-
this.hasCredential = hasCredential;
|
|
104
|
-
}
|
|
105
|
-
async listModels(provider) {
|
|
106
|
-
if (!(await this.hasCredential(provider)))
|
|
107
|
-
return [];
|
|
108
|
-
return super.listModels(provider);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
69
|
+
export { OAUTH_PROVIDER_IDS, CATALOG_PROVIDER_IDS, AUTH_PROVIDER_IDS, canonicalProvider } from './routes.js';
|
|
70
|
+
export { OpenCodeAdapter } from './opencode-adapter.js';
|
|
71
|
+
export { OpenCodeCatalog } from './opencode-catalog.js';
|
|
111
72
|
/** Mount the routes, the service, and the command. */
|
|
112
|
-
export function apply(ctx, config) {
|
|
73
|
+
export async function apply(ctx, config) {
|
|
113
74
|
const configured = (config.providers ?? [...AUTH_PROVIDER_IDS]).map(canonicalProvider);
|
|
114
75
|
const unknown = configured.filter(id => !AUTH_PROVIDER_IDS.includes(id));
|
|
115
76
|
if (unknown.length > 0 || configured.length === 0) {
|
|
@@ -123,14 +84,35 @@ export function apply(ctx, config) {
|
|
|
123
84
|
if (unknownOverridden.length > 0) {
|
|
124
85
|
throw new Error(`dsh-auth: modelOverrides names provider "${unknownOverridden[0]}", which is not among the mounted providers [${configured.join(', ')}]`);
|
|
125
86
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
87
|
+
const piIds = configured.filter(id => !OPEN_CODE_ROUTES.includes(id));
|
|
88
|
+
const custom = new Map();
|
|
89
|
+
const piProfiles = new Map();
|
|
90
|
+
const profiles = new Map();
|
|
91
|
+
let pi;
|
|
92
|
+
if (piIds.length) {
|
|
93
|
+
try {
|
|
94
|
+
pi = await import('./pi-routes.js');
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
ctx.logger.warn('dsh-auth: pi routes unavailable; OpenCode routes remain available');
|
|
98
|
+
}
|
|
99
|
+
if (pi)
|
|
100
|
+
for (const id of piIds) {
|
|
101
|
+
try {
|
|
102
|
+
const entry = pi.CUSTOM_PROVIDER_IDS.includes(id) ? pi.createCustomProfile(id, overrides[id]) : undefined;
|
|
103
|
+
if (entry)
|
|
104
|
+
custom.set(id, entry);
|
|
105
|
+
const profile = entry?.profile ?? pi.buildOAuthProfile(id, overrides[id]);
|
|
106
|
+
piProfiles.set(id, profile);
|
|
107
|
+
profiles.set(id, { provider: id, displayName: profile.displayName, oauth: profile.piProvider?.auth.oauth });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
ctx.logger.warn(`dsh-auth: route ${id} unavailable; remaining routes stay mounted`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const id of configured.filter(id => OPEN_CODE_ROUTES.includes(id)))
|
|
115
|
+
profiles.set(id, { provider: id, displayName: id === 'opencode' ? 'OpenCode Zen' : 'OpenCode Go' });
|
|
134
116
|
const store = new CredentialFile(config.credentialsFile ?? defaultCredentialsFile());
|
|
135
117
|
const credentialKey = async (provider) => {
|
|
136
118
|
const current = await store.read(provider);
|
|
@@ -161,28 +143,34 @@ export function apply(ctx, config) {
|
|
|
161
143
|
return false;
|
|
162
144
|
}
|
|
163
145
|
};
|
|
164
|
-
const adapter = new CredentialGatedAdapter({
|
|
146
|
+
const adapter = pi ? new pi.CredentialGatedAdapter({
|
|
165
147
|
// One immutable map for the plugin's lifetime: the snapshot memoizes on
|
|
166
148
|
// identity, and route changes here always mean a plugin remount anyway.
|
|
167
|
-
profiles: () =>
|
|
149
|
+
profiles: () => piProfiles,
|
|
168
150
|
// Explicit API keys cover catalog routes too. OAuth catalog routes still
|
|
169
151
|
// resolve their grants through pi-ai's injected credential store.
|
|
170
152
|
resolveApiKey: credentialKey,
|
|
171
153
|
auth: {
|
|
172
154
|
credentials: store,
|
|
173
|
-
authContext: hostAuthContext(),
|
|
155
|
+
authContext: pi.hostAuthContext(),
|
|
174
156
|
},
|
|
175
157
|
resolveAttachments: () => ctx.get('attachments'),
|
|
176
158
|
onReplayDegrade: ({ provider, model, reason }) => {
|
|
177
159
|
ctx.logger.warn(`dsh-auth: replay state on assistant history for ${provider}/${model} degraded: ${reason}`);
|
|
178
160
|
},
|
|
179
|
-
}, hasCredential);
|
|
161
|
+
}, hasCredential) : undefined;
|
|
162
|
+
const catalogs = new Map();
|
|
163
|
+
for (const id of OPEN_CODE_ROUTES)
|
|
164
|
+
if (configured.includes(id))
|
|
165
|
+
catalogs.set(id, new OpenCodeCatalog(id, OPEN_CODE_SNAPSHOTS[id], { changed: () => service.api?.notifyModelsChanged?.(id) }));
|
|
166
|
+
const native = new OpenCodeAdapter({ catalogs, store, overrides, attachments: () => ctx.get('attachments'), warn: reason => ctx.logger.warn(reason) });
|
|
180
167
|
// The service is a thin holder so UIs can find the api without importing
|
|
181
168
|
// the plugin module; the defensive get-then-create matches how the TUI
|
|
182
169
|
// mounts userQuestions.
|
|
183
170
|
const service = ctx.get('dshAuth') ?? new DshAuthService(ctx);
|
|
184
171
|
const api = createDshAuthApi({
|
|
185
172
|
profiles,
|
|
173
|
+
catalogs,
|
|
186
174
|
store,
|
|
187
175
|
resolveAsk: () => {
|
|
188
176
|
const questions = ctx.get('userQuestions');
|
|
@@ -191,6 +179,14 @@ export function apply(ctx, config) {
|
|
|
191
179
|
logger: ctx.logger,
|
|
192
180
|
nousClientId: config.nous?.clientId,
|
|
193
181
|
credentialChanged: async (provider, credential) => {
|
|
182
|
+
if (catalogs.has(provider)) {
|
|
183
|
+
if (credential) {
|
|
184
|
+
const status = await catalogs.get(provider).refresh();
|
|
185
|
+
if (status.warning)
|
|
186
|
+
throw new Error(status.warning);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
194
190
|
const entry = custom.get(provider);
|
|
195
191
|
if (entry === undefined)
|
|
196
192
|
return;
|
|
@@ -207,6 +203,8 @@ export function apply(ctx, config) {
|
|
|
207
203
|
},
|
|
208
204
|
});
|
|
209
205
|
service.api = api;
|
|
206
|
+
for (const catalog of catalogs.values())
|
|
207
|
+
void catalog.start();
|
|
210
208
|
for (const [provider, entry] of custom) {
|
|
211
209
|
void credentialKey(provider).then(key => key === undefined ? undefined : entry.refresh(key))
|
|
212
210
|
.then(count => { if (count !== undefined)
|
|
@@ -226,9 +224,11 @@ export function apply(ctx, config) {
|
|
|
226
224
|
else {
|
|
227
225
|
// Individual registrations: one conflicting route must not strand the
|
|
228
226
|
// rest (the registry keeps the previous owner serving).
|
|
229
|
-
for (const id of
|
|
227
|
+
for (const id of profiles.keys()) {
|
|
230
228
|
try {
|
|
231
|
-
|
|
229
|
+
const owned = catalogs.has(id) ? native : adapter;
|
|
230
|
+
if (owned)
|
|
231
|
+
releases.push(llm.registerAdapter([id], owned));
|
|
232
232
|
}
|
|
233
233
|
catch (error) {
|
|
234
234
|
ctx.logger.error(`dsh-auth: route "${id}" was not registered: ${error instanceof Error ? error.message : String(error)} `
|
|
@@ -244,7 +244,7 @@ export function apply(ctx, config) {
|
|
|
244
244
|
const handler = createAuthCommandHandler(api);
|
|
245
245
|
releases.push(commands.register({
|
|
246
246
|
name: 'auth',
|
|
247
|
-
description: 'Provider authentication: status, login, logout',
|
|
247
|
+
description: 'Provider authentication and catalogs: status, login, logout, models, refresh',
|
|
248
248
|
handler: invocation => {
|
|
249
249
|
const operation = handler(invocation);
|
|
250
250
|
active.add(operation);
|
|
@@ -257,6 +257,8 @@ export function apply(ctx, config) {
|
|
|
257
257
|
// enter while already-started logins finish their final write.
|
|
258
258
|
yield async () => { await Promise.allSettled([...active]); };
|
|
259
259
|
yield () => {
|
|
260
|
+
for (const catalog of catalogs.values())
|
|
261
|
+
catalog.dispose();
|
|
260
262
|
for (const release of releases)
|
|
261
263
|
release();
|
|
262
264
|
};
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @module dsh-auth/interaction
|
|
30
30
|
*/
|
|
31
31
|
import type { AskUserQuestionItem, AskUserQuestionAnswer } from '@deepseek-ai/dsh-user-questions';
|
|
32
|
-
import type {
|
|
32
|
+
import type { AuthEvent, AuthInteraction, AuthPrompt } from './auth-contract.js';
|
|
33
33
|
/** The ask surface this bridge drives (usually `ctx.userQuestions.ask`). */
|
|
34
34
|
export type AskFn = (request: {
|
|
35
35
|
questions: AskUserQuestionItem[];
|
|
@@ -41,13 +41,13 @@ export interface QuestionBridgeHelpers {
|
|
|
41
41
|
copyText?: (text: string) => Promise<boolean>;
|
|
42
42
|
}
|
|
43
43
|
/** Human-readable copy for one notify event. */
|
|
44
|
-
export declare function describeEvent(event:
|
|
44
|
+
export declare function describeEvent(event: AuthEvent): string;
|
|
45
45
|
/**
|
|
46
46
|
* The bridge for one login run. At most one waiting panel exists at a time;
|
|
47
47
|
* `settle()` retires it when the flow ends (any outcome), so a completed or
|
|
48
48
|
* failed login never leaves an interactive dead end on screen.
|
|
49
49
|
*/
|
|
50
|
-
export declare class QuestionBridge implements
|
|
50
|
+
export declare class QuestionBridge implements AuthInteraction {
|
|
51
51
|
private readonly ask;
|
|
52
52
|
private readonly runAbort;
|
|
53
53
|
readonly signal: AbortSignal;
|
|
@@ -56,8 +56,8 @@ export declare class QuestionBridge implements PiAiAuthInteraction {
|
|
|
56
56
|
private waiting;
|
|
57
57
|
private waitingClosed;
|
|
58
58
|
constructor(ask: AskFn, runAbort: AbortController, helpers?: QuestionBridgeHelpers);
|
|
59
|
-
prompt(prompt:
|
|
60
|
-
notify(event:
|
|
59
|
+
prompt(prompt: AuthPrompt): Promise<string>;
|
|
60
|
+
notify(event: AuthEvent): void;
|
|
61
61
|
/** Close the waiting panel (if any) and stop answering for this run. */
|
|
62
62
|
settle(): Promise<void>;
|
|
63
63
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Native DSH adapter for Zen/Go; catalog and wire dispatch never consult pi. */
|
|
2
|
+
import { LlmAdapter, type GenerateOptions, type StreamChunk, type PreparedAdapterCall, type LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
4
|
+
import { type OpenCodeCatalog, type OpenCodeRoute } from './opencode-catalog.js';
|
|
5
|
+
import type { CredentialFile } from './credentials.js';
|
|
6
|
+
interface Options {
|
|
7
|
+
catalogs: ReadonlyMap<OpenCodeRoute, OpenCodeCatalog>;
|
|
8
|
+
store: CredentialFile;
|
|
9
|
+
attachments?: () => AttachmentStore | undefined;
|
|
10
|
+
fetcher?: typeof fetch;
|
|
11
|
+
overrides?: Record<string, Record<string, {
|
|
12
|
+
contextWindow?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
}>>;
|
|
15
|
+
warn?: (reason: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare class OpenCodeAdapter extends LlmAdapter {
|
|
18
|
+
private readonly options;
|
|
19
|
+
constructor(options: Options);
|
|
20
|
+
providerInfo(provider: string): {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
private descriptor;
|
|
25
|
+
listModels(provider: string): Promise<{
|
|
26
|
+
provider: string;
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
inputModalities: ("text" | "image")[];
|
|
30
|
+
}[]>;
|
|
31
|
+
private resolved;
|
|
32
|
+
resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
33
|
+
prepareCall(provider: string, model: string, signal?: AbortSignal): Promise<PreparedAdapterCall>;
|
|
34
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
35
|
+
private dispatch;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/** Native DSH adapter for Zen/Go; catalog and wire dispatch never consult pi. */
|
|
2
|
+
import { LlmAdapter, LlmError, attributionHeaders } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import { encodePrompt } from './opencode-codec.js';
|
|
4
|
+
import { createTransport } from './opencode-transport.js';
|
|
5
|
+
import { abortable } from './abortable.js';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { record, mergeMetadata } from './opencode-replay.js';
|
|
8
|
+
export class OpenCodeAdapter extends LlmAdapter {
|
|
9
|
+
options;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super();
|
|
12
|
+
this.options = options;
|
|
13
|
+
for (const [provider, models] of Object.entries(options.overrides ?? {}))
|
|
14
|
+
for (const [id, override] of Object.entries(models)) {
|
|
15
|
+
if (!options.catalogs.has(provider))
|
|
16
|
+
continue;
|
|
17
|
+
this.descriptor(provider, id);
|
|
18
|
+
for (const value of Object.values(override))
|
|
19
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0))
|
|
20
|
+
throw new Error(`Invalid model override for ${provider}/${id}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
providerInfo(provider) { return { id: provider, name: provider === 'opencode' ? 'OpenCode Zen' : 'OpenCode Go' }; }
|
|
24
|
+
descriptor(provider, id) {
|
|
25
|
+
const catalog = this.options.catalogs.get(provider);
|
|
26
|
+
const found = catalog?.models().find(model => model.id === id);
|
|
27
|
+
if (!found)
|
|
28
|
+
throw new LlmError(`OpenCode model ${provider}/${id} unavailable: ${catalog?.status().excluded.find(model => model.id === id)?.reason ?? 'not on the verified roster'}`, 'MODEL_NOT_FOUND');
|
|
29
|
+
const override = this.options.overrides?.[provider]?.[id];
|
|
30
|
+
return { ...found, ...override };
|
|
31
|
+
}
|
|
32
|
+
async listModels(provider) {
|
|
33
|
+
if (!(await this.options.store.read(provider)))
|
|
34
|
+
return [];
|
|
35
|
+
return (this.options.catalogs.get(provider)?.models() ?? []).map(model => ({ provider, id: model.id, name: model.name, inputModalities: model.inputModalities }));
|
|
36
|
+
}
|
|
37
|
+
resolved(provider, descriptor) {
|
|
38
|
+
return { provider, id: descriptor.id, name: descriptor.name, inputModalities: descriptor.inputModalities, context: { contextWindow: descriptor.contextWindow }, defaultMaxTokens: descriptor.maxTokens,
|
|
39
|
+
...(descriptor.efforts.length ? { reasoning: { efforts: descriptor.efforts.map(id => ({ id: id, name: id })) } } : {}) };
|
|
40
|
+
}
|
|
41
|
+
async resolveModel(provider, model, signal) { signal?.throwIfAborted(); return this.resolved(provider, this.descriptor(provider, model)); }
|
|
42
|
+
async prepareCall(provider, model, signal) {
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
const descriptor = this.descriptor(provider, model);
|
|
45
|
+
return { model: this.resolved(provider, descriptor), stream: options => {
|
|
46
|
+
if (options.provider !== provider || options.model !== model)
|
|
47
|
+
throw new LlmError('Prepared OpenCode call identity mismatch', 'INVALID_PREPARED_CALL');
|
|
48
|
+
return this.dispatch(options, descriptor);
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
51
|
+
stream(options) { return this.dispatch(options, this.descriptor(options.provider, options.model)); }
|
|
52
|
+
async *dispatch(options, descriptor) {
|
|
53
|
+
options.signal?.throwIfAborted();
|
|
54
|
+
if (options.tools?.length && !descriptor.toolCall)
|
|
55
|
+
throw new LlmError('This OpenCode model does not support tool calls', 'UNSUPPORTED_CAPABILITY');
|
|
56
|
+
if (options.reasoningEffort && !descriptor.efforts.includes(options.reasoningEffort))
|
|
57
|
+
throw new LlmError('Unsupported reasoning effort for this model', 'UNSUPPORTED_CAPABILITY');
|
|
58
|
+
const prompt = await encodePrompt(options, descriptor, this.options.attachments, this.options.warn);
|
|
59
|
+
const credential = await this.options.store.read(options.provider);
|
|
60
|
+
if (credential?.type !== 'api_key' || !credential.key?.trim())
|
|
61
|
+
throw new LlmError(`Run /auth login ${options.provider}`, 'AUTH');
|
|
62
|
+
const headers = { ...attributionHeaders(), 'x-opencode-session': options.sessionId ?? randomUUID() };
|
|
63
|
+
const model = createTransport(descriptor, options, credential.key, headers, prompt, this.options.fetcher);
|
|
64
|
+
const abort = new AbortController();
|
|
65
|
+
const signal = AbortSignal.any([abort.signal, ...(options.signal ? [options.signal] : [])]);
|
|
66
|
+
let timer = setTimeout(() => abort.abort(new Error('OpenCode stream idle timeout')), 300_000);
|
|
67
|
+
timer.unref();
|
|
68
|
+
let reader;
|
|
69
|
+
try {
|
|
70
|
+
const result = await abortable(model.doStream({ prompt, abortSignal: signal, maxOutputTokens: options.maxTokens ?? descriptor.maxTokens,
|
|
71
|
+
...(descriptor.temperature ? { temperature: options.temperature } : {}), stopSequences: options.stop,
|
|
72
|
+
tools: options.tools?.map(tool => ({ type: 'function', name: tool.name, description: tool.description, inputSchema: tool.parameters })),
|
|
73
|
+
providerOptions: descriptor.protocol === 'responses' ? { openai: { store: false } } : undefined }), signal);
|
|
74
|
+
reader = result.stream.getReader();
|
|
75
|
+
const blocks = new Map();
|
|
76
|
+
let responseId;
|
|
77
|
+
while (true) {
|
|
78
|
+
const item = await abortable(reader.read(), signal);
|
|
79
|
+
if (item.done)
|
|
80
|
+
throw new LlmError('OpenCode stream ended without a finish event', 'PROTOCOL_ERROR');
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
timer = setTimeout(() => abort.abort(new Error('OpenCode stream idle timeout')), 300_000);
|
|
83
|
+
timer.unref();
|
|
84
|
+
const part = item.value;
|
|
85
|
+
if (part.type === 'error')
|
|
86
|
+
throw part.error;
|
|
87
|
+
if (part.type === 'response-metadata') {
|
|
88
|
+
responseId = part.id;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (part.type === 'text-start' || part.type === 'reasoning-start' || part.type === 'tool-input-start') {
|
|
92
|
+
const block = part.type === 'tool-input-start' ? { type: 'tool-call', id: part.id, name: part.toolName, arguments: '' } : { type: part.type === 'text-start' ? 'text' : 'reasoning', text: '' };
|
|
93
|
+
const entry = { index: blocks.size, block, ended: false, providerOptions: part.providerMetadata };
|
|
94
|
+
blocks.set(part.id, entry);
|
|
95
|
+
yield { type: 'block-start', index: entry.index, blockType: block.type };
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (part.type === 'text-delta' || part.type === 'reasoning-delta' || part.type === 'tool-input-delta') {
|
|
99
|
+
const entry = blocks.get(part.id);
|
|
100
|
+
if (!entry || entry.ended || entry.inputEnded)
|
|
101
|
+
throw new LlmError('Unmatched stream delta', 'PROTOCOL_ERROR');
|
|
102
|
+
if (entry.block.type === 'tool-call') {
|
|
103
|
+
entry.block.arguments += part.delta;
|
|
104
|
+
yield { type: 'tool-call-delta', index: entry.index, id: entry.block.id, argumentsDelta: part.delta };
|
|
105
|
+
}
|
|
106
|
+
else if (entry.block.type === 'text' || entry.block.type === 'reasoning') {
|
|
107
|
+
entry.block.text += part.delta;
|
|
108
|
+
yield { type: entry.block.type === 'text' ? 'text-delta' : 'reasoning-delta', index: entry.index, text: part.delta };
|
|
109
|
+
}
|
|
110
|
+
if (part.providerMetadata)
|
|
111
|
+
entry.providerOptions = mergeMetadata(entry.providerOptions, part.providerMetadata);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (part.type === 'text-end' || part.type === 'reasoning-end' || part.type === 'tool-input-end') {
|
|
115
|
+
const entry = blocks.get(part.id);
|
|
116
|
+
if (!entry || entry.ended)
|
|
117
|
+
throw new LlmError('Unmatched stream end', 'PROTOCOL_ERROR');
|
|
118
|
+
if (part.providerMetadata)
|
|
119
|
+
entry.providerOptions = mergeMetadata(entry.providerOptions, part.providerMetadata);
|
|
120
|
+
if (entry.block.type === 'tool-call') {
|
|
121
|
+
entry.inputEnded = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
entry.ended = true;
|
|
125
|
+
yield { type: 'block-end', index: entry.index, block: entry.block };
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (part.type === 'tool-call') {
|
|
129
|
+
if (part.providerExecuted)
|
|
130
|
+
throw new LlmError('Unexpected provider-executed tool', 'PROTOCOL_ERROR');
|
|
131
|
+
if (!blocks.has(part.toolCallId)) {
|
|
132
|
+
const block = { type: 'tool-call', id: part.toolCallId, name: part.toolName, arguments: part.input };
|
|
133
|
+
const index = blocks.size;
|
|
134
|
+
blocks.set(part.toolCallId, { index, block, ended: true, providerOptions: part.providerMetadata });
|
|
135
|
+
yield { type: 'block-start', index, blockType: 'tool-call' };
|
|
136
|
+
yield { type: 'block-end', index, block };
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const entry = blocks.get(part.toolCallId);
|
|
140
|
+
if (entry.block.type !== 'tool-call' || entry.ended || entry.block.name !== part.toolName)
|
|
141
|
+
throw new LlmError('Mismatched final tool call', 'PROTOCOL_ERROR');
|
|
142
|
+
// Some providers send the complete arguments only in the final
|
|
143
|
+
// tool event. Preserve streamed raw JSON when it was supplied.
|
|
144
|
+
if (!entry.block.arguments)
|
|
145
|
+
entry.block.arguments = part.input;
|
|
146
|
+
if (part.providerMetadata)
|
|
147
|
+
entry.providerOptions = mergeMetadata(entry.providerOptions, part.providerMetadata);
|
|
148
|
+
entry.ended = true;
|
|
149
|
+
yield { type: 'block-end', index: entry.index, block: entry.block };
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (part.type === 'finish') {
|
|
154
|
+
for (const entry of blocks.values())
|
|
155
|
+
if (!entry.ended) {
|
|
156
|
+
entry.ended = true;
|
|
157
|
+
yield { type: 'block-end', index: entry.index, block: entry.block };
|
|
158
|
+
}
|
|
159
|
+
const input = part.usage.inputTokens;
|
|
160
|
+
const output = part.usage.outputTokens;
|
|
161
|
+
// DSH snapshots chunks losslessly: optional SDK values must be absent,
|
|
162
|
+
// not own properties containing undefined. Preserve reported zeroes.
|
|
163
|
+
yield { type: 'usage', usage: {
|
|
164
|
+
inputTokens: input.noCache ?? Math.max(0, (input.total ?? 0) - (input.cacheRead ?? 0) - (input.cacheWrite ?? 0)),
|
|
165
|
+
outputTokens: output.total ?? 0,
|
|
166
|
+
...(input.cacheRead !== undefined ? { cacheReadTokens: input.cacheRead } : {}),
|
|
167
|
+
...(input.cacheWrite !== undefined ? { cacheWriteTokens: input.cacheWrite } : {}),
|
|
168
|
+
...(output.reasoning !== undefined ? { reasoningTokens: output.reasoning } : {}),
|
|
169
|
+
...(input.total !== undefined && output.total !== undefined ? { totalTokens: input.total + output.total } : {}),
|
|
170
|
+
} };
|
|
171
|
+
if (!blocks.size)
|
|
172
|
+
throw new LlmError('OpenCode returned no content', 'EMPTY_RESPONSE');
|
|
173
|
+
const finish = part.finishReason.unified;
|
|
174
|
+
if (finish === 'error' || finish === 'content-filter' || finish === 'other')
|
|
175
|
+
throw new LlmError(`OpenCode generation ended: ${finish}`, 'PROVIDER_ERROR');
|
|
176
|
+
yield { type: 'finish', reason: { kind: finish === 'tool-calls' ? 'tool-calls' : finish === 'length' ? 'max-tokens' : 'stop' }, replayState: { response: { kind: 'dsh-opencode', version: 1, protocol: descriptor.protocol, provider: options.provider, model: descriptor.id, ...(responseId !== undefined ? { responseId } : {}) }, blocks: [...blocks.values()].map(entry => ({ type: entry.block.type, ...(entry.providerOptions ? { providerOptions: entry.providerOptions } : {}) })) } };
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (options.signal?.aborted)
|
|
183
|
+
throw options.signal.reason;
|
|
184
|
+
if (signal.aborted)
|
|
185
|
+
throw new LlmError('OpenCode stream idle timeout', 'TIMEOUT');
|
|
186
|
+
if (error instanceof LlmError)
|
|
187
|
+
throw error;
|
|
188
|
+
const raw = record(error);
|
|
189
|
+
const status = typeof raw?.statusCode === 'number' ? raw.statusCode : undefined;
|
|
190
|
+
const responseHeaders = record(raw?.responseHeaders);
|
|
191
|
+
const retryHeader = responseHeaders?.['retry-after'];
|
|
192
|
+
const seconds = Number(retryHeader);
|
|
193
|
+
const retry = typeof retryHeader === 'string' ? Number.isFinite(seconds) ? seconds * 1000 : Date.parse(retryHeader) - Date.now() : undefined;
|
|
194
|
+
const requestId = responseHeaders?.['x-request-id'] ?? responseHeaders?.['request-id'];
|
|
195
|
+
throw new LlmError(`OpenCode request failed${status ? `: HTTP ${status}` : ''}`, status === 401 || status === 403 ? 'AUTH' : status === 429 ? 'RATE_LIMIT' : status && status >= 500 ? 'SERVER' : status ? 'PROVIDER_ERROR' : 'TRANSPORT', { status, ...(retry !== undefined && retry > 0 ? { providerRetryAfterMs: retry } : {}), ...(typeof requestId === 'string' && requestId.trim() ? { requestId: requestId } : {}) });
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
abort.abort();
|
|
200
|
+
if (reader) {
|
|
201
|
+
void reader.cancel().catch(() => undefined);
|
|
202
|
+
reader.releaseLock();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export declare const OPEN_CODE_ROUTES: readonly ["opencode", "opencode-go"];
|
|
2
|
+
export type OpenCodeRoute = typeof OPEN_CODE_ROUTES[number];
|
|
3
|
+
export type OpenCodeProtocol = 'messages' | 'responses' | 'chat' | 'google';
|
|
4
|
+
export declare const OPEN_CODE_URLS: Record<OpenCodeRoute, string>;
|
|
5
|
+
export interface OpenCodeModel {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
protocol: OpenCodeProtocol;
|
|
9
|
+
contextWindow: number;
|
|
10
|
+
maxTokens: number;
|
|
11
|
+
inputModalities: ('text' | 'image')[];
|
|
12
|
+
toolCall: boolean;
|
|
13
|
+
reasoning: boolean;
|
|
14
|
+
temperature: boolean;
|
|
15
|
+
efforts: string[];
|
|
16
|
+
cost?: Record<string, number>;
|
|
17
|
+
}
|
|
18
|
+
export interface OpenCodeSnapshot {
|
|
19
|
+
version: 1;
|
|
20
|
+
provider: OpenCodeRoute;
|
|
21
|
+
fetchedAt: number;
|
|
22
|
+
roster: string[];
|
|
23
|
+
models: OpenCodeModel[];
|
|
24
|
+
excluded: {
|
|
25
|
+
id: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
export interface OpenCodeCatalogStatus {
|
|
30
|
+
provider: OpenCodeRoute;
|
|
31
|
+
source: 'bundled' | 'cache' | 'live';
|
|
32
|
+
fetchedAt: number;
|
|
33
|
+
available: number;
|
|
34
|
+
excluded: readonly {
|
|
35
|
+
id: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}[];
|
|
38
|
+
warning?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function rosterIds(payload: unknown): string[];
|
|
41
|
+
/** Normalize only exact-route metadata; every roster id has an explicit outcome. */
|
|
42
|
+
export declare function normalizeCatalog(provider: OpenCodeRoute, roster: readonly string[], metadata: unknown, fetchedAt: number, previous?: OpenCodeSnapshot): OpenCodeSnapshot;
|
|
43
|
+
/** Validate cache/snapshot data using the same normalizer as fetched metadata. */
|
|
44
|
+
export declare function parseSnapshot(value: unknown, provider: OpenCodeRoute): OpenCodeSnapshot;
|
|
45
|
+
export declare function fetchCatalogJson(url: string, fetcher: typeof fetch, signal?: AbortSignal): Promise<unknown>;
|
|
46
|
+
export declare class OpenCodeCatalog {
|
|
47
|
+
readonly provider: OpenCodeRoute;
|
|
48
|
+
private readonly options;
|
|
49
|
+
private snapshot;
|
|
50
|
+
private source;
|
|
51
|
+
private warning?;
|
|
52
|
+
private pending?;
|
|
53
|
+
private disposed;
|
|
54
|
+
private abort;
|
|
55
|
+
private timer?;
|
|
56
|
+
private started;
|
|
57
|
+
constructor(provider: OpenCodeRoute, bundled: unknown, options?: {
|
|
58
|
+
directory?: string;
|
|
59
|
+
fetcher?: typeof fetch;
|
|
60
|
+
changed?: () => void;
|
|
61
|
+
now?: () => number;
|
|
62
|
+
});
|
|
63
|
+
private get path();
|
|
64
|
+
models(): readonly OpenCodeModel[];
|
|
65
|
+
status(): OpenCodeCatalogStatus;
|
|
66
|
+
start(): Promise<void>;
|
|
67
|
+
refresh(): Promise<OpenCodeCatalogStatus>;
|
|
68
|
+
private update;
|
|
69
|
+
/** Compare and replace under a bounded cross-process lock; failed writes retain the old cache. */
|
|
70
|
+
private save;
|
|
71
|
+
dispose(): void;
|
|
72
|
+
}
|