@edge-sky/dsh-oauth-adapter 0.1.2-dev.2 → 0.1.2-rc.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.
@@ -1,371 +0,0 @@
1
- import z from '@deepseek-ai/schemastery';
2
- import { createModels } from '@earendil-works/pi-ai';
3
- import { SettingsConflictError } from '@deepseek-ai/dsh-settings';
4
- import { resolveImageAttachmentAccess } from '@deepseek-ai/dsh-llm';
5
- import { PiAiAdapter, recordKeyFor } from '@deepseek-ai/dsh-llm-pi-ai';
6
- import { PROVIDERS } from './protocol.js';
7
- import { MODEL_PAGE_SIZE, MODEL_PROTOCOLS, validManual } from './model-types.js';
8
- import { accountIdentity, credentialBridge, fromRecord } from './model-auth.js';
9
- import { materialize, providerFactories, validateModelStore } from './model-catalog.js';
10
- import { discoverAccountModels, ModelOperationError } from './model-discovery.js';
11
- const NS = 'oauth-models';
12
- const LEGACY_NS = 'llm-pi-ai';
13
- const Schema = z.object({ version: z.const(1).default(1), providers: z.dict(z.any()).default({}) });
14
- function object(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; }
15
- function failure(code, message) { throw new ModelOperationError(code, message); }
16
- export class OAuthModelService {
17
- ctx;
18
- options;
19
- changed;
20
- profiles = new Map();
21
- handle;
22
- queues = new Map();
23
- syncs = new Map();
24
- controllers = new Map();
25
- generations = new Map();
26
- loggingIn = new Set();
27
- errors = new Map();
28
- disposers = [];
29
- closed = false;
30
- stop = new AbortController();
31
- signedOut = new Set();
32
- adapter;
33
- ready;
34
- constructor(ctx, options, changed) {
35
- this.ctx = ctx;
36
- this.options = options;
37
- this.changed = changed;
38
- const scope = ctx.settings.register(NS, Schema, { base: { version: 1, providers: {} }, validate: value => { validateModelStore(value); } });
39
- this.adapter = new PiAiAdapter({
40
- profiles: () => this.profiles,
41
- resolveApiKey: async () => undefined,
42
- auth: { credentials: credentialBridge(ctx.credentials, provider => { if (this.closed)
43
- failure('disposed', 'OAuth models have stopped.'); if (this.signedOut.has(provider))
44
- failure('not-connected', 'This account was disconnected.'); if (this.loggingIn.has(provider))
45
- failure('busy', 'Account reconnection is in progress.'); }), authContext: { env: async () => undefined, fileExists: async () => false } },
46
- resolveAttachments: () => ctx.get('attachments'),
47
- resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, hostPath => ctx.get('fs')?.processPathFromHostPath(hostPath), ref),
48
- });
49
- this.ready = this.initialize();
50
- this.disposers.push(scope.watch(() => {
51
- if (this.closed)
52
- return;
53
- for (const p of PROVIDERS)
54
- void this.enqueue(p.id, async () => { await this.reconcile(p.id); }).catch(error => this.report(p.id, error));
55
- }));
56
- this.disposers.push(ctx.on('credentials/record-updated', key => {
57
- const provider = PROVIDERS.find(p => p.key === key)?.id;
58
- if (!provider || this.closed || this.loggingIn.has(provider))
59
- return;
60
- void this.enqueue(provider, async () => { await this.reconcile(provider); }).catch(error => this.report(provider, error));
61
- }));
62
- }
63
- state() { return validateModelStore(this.ctx.settings.get(NS)); }
64
- revision() { return this.ctx.settings.describe().find(s => s.ns === NS)?.revision ?? 0; }
65
- isManaged(provider) { return this.profiles.has(provider); }
66
- checkRevision(revision) { if (revision !== this.revision())
67
- failure('settings-conflict', 'Models changed in another operation. Refresh and retry.'); }
68
- checkOpen() { if (this.closed)
69
- failure('disposed', 'OAuth models have stopped.'); }
70
- async save(provider, state, revision = this.revision()) {
71
- this.checkOpen();
72
- materialize(provider, state);
73
- try {
74
- await this.ctx.settings.mutate(NS, [{ op: 'set', path: ['providers', provider], value: state }], revision);
75
- }
76
- catch (error) {
77
- if (error instanceof SettingsConflictError)
78
- failure('settings-conflict', 'Models changed in another operation. Refresh and retry.');
79
- throw error;
80
- }
81
- }
82
- report(provider, error) {
83
- this.errors.set(provider, error instanceof ModelOperationError ? { code: error.code, message: error.message } : { code: 'operation-failed', message: 'Model operation failed. The previous configuration was retained.' });
84
- this.changed(provider);
85
- }
86
- enqueue(provider, action) {
87
- if (this.closed)
88
- return Promise.reject(new ModelOperationError('disposed', 'OAuth models have stopped.'));
89
- const previous = this.queues.get(provider) ?? Promise.resolve();
90
- const task = previous.catch(() => { }).then(() => { this.checkOpen(); return action(); });
91
- this.queues.set(provider, task);
92
- void task.finally(() => { if (this.queues.get(provider) === task)
93
- this.queues.delete(provider); }).catch(() => { });
94
- return task;
95
- }
96
- async initialize() {
97
- for (const p of PROVIDERS) {
98
- try {
99
- await this.enqueue(p.id, async () => { await this.ensureManaged(p.id, false); });
100
- }
101
- catch (error) {
102
- this.report(p.id, error);
103
- }
104
- }
105
- }
106
- legacy(provider) {
107
- const descriptor = this.ctx.settings.describe().find(s => s.ns === LEGACY_NS);
108
- if (!descriptor)
109
- failure('model-host', 'The DSH pi-ai settings namespace is unavailable.');
110
- const profile = object(object(descriptor.user)?.providers)?.[provider];
111
- const base = object(object(descriptor.base)?.providers)?.[provider];
112
- const value = object(profile);
113
- const label = PROVIDERS.find(p => p.id === provider).modelGroupLabel;
114
- return { profile: value, revision: descriptor.revision,
115
- custom: value !== undefined && !(Object.keys(value).length === 1 && value.displayName === label),
116
- blocked: base !== undefined || (value !== undefined && value.apiKeyEnv !== undefined) };
117
- }
118
- async connected(provider) { return !this.signedOut.has(provider) && fromRecord(await this.ctx.credentials.readRecord(recordKeyFor(provider)))?.type === 'oauth'; }
119
- async publish(provider, state) {
120
- this.checkOpen();
121
- const next = new Map(this.profiles);
122
- if (state && await this.connected(provider))
123
- next.set(provider, materialize(provider, state).profile);
124
- else
125
- next.delete(provider);
126
- this.checkOpen();
127
- const old = this.profiles;
128
- this.profiles = next;
129
- try {
130
- if (this.handle)
131
- this.handle.replace([...next.keys()]);
132
- else if (next.size)
133
- this.handle = this.ctx.llm.registerAdapter([...next.keys()], this.adapter);
134
- }
135
- catch (error) {
136
- this.profiles = old;
137
- throw error;
138
- }
139
- this.changed(provider);
140
- }
141
- async reconcile(provider) {
142
- if (this.loggingIn.has(provider))
143
- return;
144
- const state = this.state().providers[provider];
145
- await this.publish(provider, state?.phase === 'managed' ? state : undefined);
146
- }
147
- /** Wait on the actual route registry; settings persistence alone does not imply adapter release. */
148
- async releaseLegacy(provider, revision) {
149
- const signal = AbortSignal.any([this.stop.signal, AbortSignal.timeout(this.options.timeoutMs)]);
150
- let wake;
151
- const dispose = this.ctx.on('llm/adapters-updated', () => { wake?.(); });
152
- try {
153
- await this.ctx.settings.mutate(LEGACY_NS, [{ op: 'unset', path: ['providers', provider] }], revision);
154
- while (this.ctx.llm.listProviders().some(p => p.id === provider)) {
155
- signal.throwIfAborted();
156
- await new Promise((resolve, reject) => {
157
- const abort = () => { cleanup(); reject(new ModelOperationError('route-conflict', 'The previous model adapter did not release this provider.')); };
158
- const cleanup = () => { signal.removeEventListener('abort', abort); wake = undefined; };
159
- wake = () => { cleanup(); resolve(); };
160
- signal.addEventListener('abort', abort, { once: true });
161
- if (signal.aborted)
162
- abort();
163
- else if (!this.ctx.llm.listProviders().some(p => p.id === provider))
164
- wake();
165
- });
166
- }
167
- }
168
- finally {
169
- dispose();
170
- }
171
- }
172
- async ensureManaged(provider, approve) {
173
- if (!await this.connected(provider))
174
- return;
175
- let state = this.state().providers[provider];
176
- if (state?.phase === 'managed') {
177
- await this.publish(provider, state);
178
- return;
179
- }
180
- const legacy = this.legacy(provider);
181
- if (legacy.blocked)
182
- failure('route-conflict', 'This provider has an API-key reference or a composition-owned profile; it was not changed.');
183
- if (!state && legacy.custom && !approve)
184
- failure('migration-required', 'Review and migrate the existing custom OAuth configuration before managing models.');
185
- if (!state) {
186
- state = { phase: 'pending', legacy: legacy.profile ?? { displayName: PROVIDERS.find(p => p.id === provider).modelGroupLabel }, manual: [], restoredFromLegacy: legacy.profile !== undefined };
187
- const choices = MODEL_PROTOCOLS[provider];
188
- if (state.legacy.api && !choices.includes(state.legacy.api))
189
- failure('route-conflict', 'The existing protocol is not supported by this OAuth provider.');
190
- state.manual = (state.legacy.models ?? []).map(model => {
191
- const api = state.legacy.api ?? providerFactories[provider]().getModels().find(m => m.id === model.id)?.api ?? (choices.length === 1 ? choices[0] : undefined);
192
- if (!api)
193
- failure('model-protocol', `Existing model ${model.id} needs an explicit protocol.`);
194
- return { id: model.id, ...(model.name ? { name: model.name } : {}), ...(choices.length > 1 ? { api: api } : {}) };
195
- });
196
- await this.save(provider, state);
197
- }
198
- try {
199
- if (legacy.profile)
200
- await this.releaseLegacy(provider, legacy.revision);
201
- else if (this.ctx.llm.listProviders().some(p => p.id === provider) && !this.profiles.has(provider))
202
- failure('route-conflict', 'Another adapter owns this provider; it was not changed.');
203
- const managed = { ...state, phase: 'managed' };
204
- await this.publish(provider, managed);
205
- await this.save(provider, managed);
206
- this.errors.delete(provider);
207
- }
208
- catch (error) {
209
- await this.publish(provider, undefined);
210
- // Restore only if the source is still absent. Never overwrite a concurrent profile edit.
211
- const source = this.legacy(provider);
212
- if (!source.profile && state.restoredFromLegacy)
213
- await this.ctx.settings.mutate(LEGACY_NS, [{ op: 'set', path: ['providers', provider], value: state.legacy }], source.revision);
214
- throw error;
215
- }
216
- }
217
- async page(provider, offset) {
218
- await this.ready;
219
- const state = this.state().providers[provider];
220
- const fallback = { phase: 'pending', legacy: this.legacy(provider).profile ?? {}, manual: [] };
221
- const rows = materialize(provider, state ?? fallback).rows;
222
- const legacy = this.legacy(provider);
223
- offset = Math.min(offset, Math.max(0, Math.floor((rows.length - 1) / MODEL_PAGE_SIZE) * MODEL_PAGE_SIZE));
224
- return {
225
- provider, revision: this.revision(), offset, total: rows.length, rows: rows.slice(offset, offset + MODEL_PAGE_SIZE),
226
- source: state?.discovered ? 'account' : 'catalog',
227
- counts: { matched: rows.filter(r => r.source === 'matched' || r.source === 'catalog').length, pending: rows.filter(r => r.source === 'pending').length, manual: rows.filter(r => r.source === 'manual').length },
228
- connected: await this.connected(provider), writable: this.ctx.settings.writable,
229
- managed: this.isManaged(provider), busy: this.syncs.has(provider),
230
- ...(state?.syncedAt === undefined ? {} : { syncedAt: state.syncedAt }),
231
- ...(this.errors.has(provider) ? { error: this.errors.get(provider).message, errorCode: this.errors.get(provider).code } : {}),
232
- ...((state?.phase === 'pending' || !state && legacy.custom) && !legacy.blocked ? { migration: { fields: Object.keys(state?.legacy ?? legacy.profile ?? {}), models: (state?.legacy ?? legacy.profile)?.models?.length ?? 0 } } : {}),
233
- };
234
- }
235
- async migrate(provider, revision) {
236
- await this.ready;
237
- return this.enqueue(provider, async () => { this.checkRevision(revision); await this.ensureManaged(provider, true); });
238
- }
239
- async saveManual(provider, model, revision) {
240
- if (!validManual(provider, model))
241
- failure('invalid-model', 'Model fields or protocol are invalid.');
242
- await this.ready;
243
- return this.enqueue(provider, async () => {
244
- this.checkRevision(revision);
245
- await this.ensureManaged(provider, false);
246
- const state = this.state().providers[provider];
247
- if (!state || !await this.connected(provider))
248
- failure('not-connected', 'Connect this provider before editing models.');
249
- const next = { ...state, manual: [...state.manual.filter(m => m.id !== model.id), model] };
250
- await this.save(provider, next);
251
- await this.publish(provider, next);
252
- });
253
- }
254
- async deleteManual(provider, id, revision) {
255
- await this.ready;
256
- return this.enqueue(provider, async () => {
257
- this.checkRevision(revision);
258
- const state = this.state().providers[provider];
259
- if (!state)
260
- failure('not-connected', 'This provider has no managed models.');
261
- const next = { ...state, manual: state.manual.filter(m => m.id !== id) };
262
- await this.save(provider, next);
263
- await this.publish(provider, next);
264
- });
265
- }
266
- async sync(provider, revision) {
267
- await this.ready;
268
- const running = this.syncs.get(provider);
269
- if (running)
270
- return running;
271
- const generation = this.generations.get(provider) ?? 0;
272
- const controller = new AbortController();
273
- this.controllers.set(provider, controller);
274
- const task = this.enqueue(provider, async () => {
275
- this.checkRevision(revision);
276
- await this.ensureManaged(provider, false);
277
- const state = this.state().providers[provider];
278
- if (!state || !await this.connected(provider))
279
- failure('not-connected', 'Connect this provider before synchronizing models.');
280
- const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(this.options.timeoutMs)]);
281
- const check = () => { this.checkOpen(); signal.throwIfAborted(); if ((this.generations.get(provider) ?? 0) !== generation)
282
- failure('cancelled', 'The account changed during synchronization.'); };
283
- const collection = createModels({ credentials: credentialBridge(this.ctx.credentials, check), authContext: { env: async () => undefined, fileExists: async () => false } });
284
- collection.setProvider(providerFactories[provider]());
285
- const resolved = await collection.getAuth(provider, { signal });
286
- check();
287
- if (!resolved)
288
- failure('not-connected', 'The provider has no usable OAuth credential.');
289
- const credential = fromRecord(await this.ctx.credentials.readRecord(recordKeyFor(provider)));
290
- const accountId = credential?.type === 'oauth' && typeof credential.accountId === 'string' ? credential.accountId : undefined;
291
- const discovered = await discoverAccountModels(provider, { auth: resolved.auth, ...(accountId ? { accountId } : {}) }, {
292
- signal, maxBytes: this.options.maxResponseBytes, maxPages: this.options.maxPages, codexClientVersion: this.options.codexClientVersion,
293
- });
294
- check();
295
- const next = { ...state, discovered, syncedAt: new Date().toISOString() };
296
- const identity = accountIdentity(provider, credential);
297
- if (identity)
298
- next.account = identity;
299
- const expected = this.revision();
300
- // External edits while a network request was pending must not be overwritten.
301
- if (JSON.stringify(this.state().providers[provider]) !== JSON.stringify(state))
302
- failure('settings-conflict', 'Models changed during synchronization. Refresh and retry.');
303
- await this.save(provider, next, expected);
304
- check();
305
- await this.publish(provider, next);
306
- this.errors.delete(provider);
307
- }).catch(error => {
308
- const failure = error instanceof ModelOperationError ? error : error instanceof Error && error.name === 'TimeoutError' ? new ModelOperationError('discovery-timeout', 'Model discovery timed out; the previous list was retained.') : controller.signal.aborted ? new ModelOperationError('cancelled', 'Model synchronization was cancelled.') : new ModelOperationError('discovery-failed', 'Model discovery failed; the previous list was retained.');
309
- this.report(provider, failure);
310
- throw failure;
311
- }).finally(() => {
312
- if (this.controllers.get(provider) === controller)
313
- this.controllers.delete(provider);
314
- this.syncs.delete(provider);
315
- if (!this.closed)
316
- this.changed(provider);
317
- });
318
- this.syncs.set(provider, task);
319
- this.changed(provider);
320
- return task;
321
- }
322
- loginStarted(provider) {
323
- this.loggingIn.add(provider);
324
- this.generations.set(provider, (this.generations.get(provider) ?? 0) + 1);
325
- this.controllers.get(provider)?.abort();
326
- }
327
- async loginFinished(provider, authorized) {
328
- if (authorized)
329
- this.signedOut.delete(provider);
330
- this.loggingIn.delete(provider);
331
- await this.ready;
332
- await this.enqueue(provider, async () => {
333
- if (authorized) {
334
- const state = this.state().providers[provider];
335
- const identity = accountIdentity(provider, fromRecord(await this.ctx.credentials.readRecord(recordKeyFor(provider))));
336
- if (state && (!identity || identity !== state.account)) {
337
- const { discovered: _discovered, syncedAt: _time, account: _account, ...rest } = state;
338
- await this.save(provider, { ...rest, ...(identity ? { account: identity } : {}) });
339
- }
340
- await this.ensureManaged(provider, false);
341
- }
342
- else
343
- await this.reconcile(provider);
344
- });
345
- if (authorized)
346
- await this.sync(provider, this.revision());
347
- }
348
- async forget(provider) {
349
- this.signedOut.add(provider);
350
- this.generations.set(provider, (this.generations.get(provider) ?? 0) + 1);
351
- this.controllers.get(provider)?.abort();
352
- await this.ready;
353
- await this.enqueue(provider, async () => {
354
- await this.ctx.credentials.deleteRecord(recordKeyFor(provider));
355
- await this.publish(provider, undefined);
356
- this.errors.delete(provider);
357
- });
358
- }
359
- async dispose() {
360
- this.closed = true;
361
- this.stop.abort();
362
- for (const dispose of this.disposers)
363
- dispose();
364
- for (const controller of this.controllers.values())
365
- controller.abort();
366
- await Promise.allSettled([...this.queues.values()]);
367
- this.handle?.();
368
- this.profiles = new Map();
369
- }
370
- }
371
- //# sourceMappingURL=model-service.js.map
@@ -1,92 +0,0 @@
1
- /** Model-management messages shared by the browser and Host. No credentials travel here. */
2
- import type { ProviderId } from './protocol.js';
3
- export declare const MODEL_PAGE_SIZE = 10;
4
- export declare const MODEL_PROTOCOLS: {
5
- readonly 'github-copilot': readonly ["openai-completions", "openai-responses", "anthropic-messages"];
6
- readonly openrouter: readonly ["openai-completions", "anthropic-messages"];
7
- readonly 'openai-codex': readonly ["openai-codex-responses"];
8
- readonly xai: readonly ["openai-responses"];
9
- readonly anthropic: readonly ["anthropic-messages"];
10
- readonly 'kimi-coding': readonly ["anthropic-messages"];
11
- };
12
- export type ModelProtocol = (typeof MODEL_PROTOCOLS)[ProviderId][number];
13
- export interface ManualModel {
14
- id: string;
15
- name?: string;
16
- api?: ModelProtocol;
17
- }
18
- export interface DiscoveredModel {
19
- id: string;
20
- name?: string;
21
- }
22
- export interface ModelRow {
23
- id: string;
24
- name: string;
25
- source: 'catalog' | 'matched' | 'pending' | 'manual';
26
- api?: string;
27
- }
28
- export interface ModelPage {
29
- provider: ProviderId;
30
- revision: number;
31
- offset: number;
32
- total: number;
33
- rows: ModelRow[];
34
- source: 'catalog' | 'account';
35
- counts: {
36
- matched: number;
37
- pending: number;
38
- manual: number;
39
- };
40
- connected: boolean;
41
- writable: boolean;
42
- managed: boolean;
43
- busy: boolean;
44
- syncedAt?: string;
45
- error?: string;
46
- errorCode?: string;
47
- migration?: {
48
- fields: string[];
49
- models: number;
50
- };
51
- }
52
- export type ModelCommand = {
53
- type: 'models-list';
54
- requestId: string;
55
- provider: ProviderId;
56
- offset: number;
57
- } | {
58
- type: 'models-sync' | 'models-migrate';
59
- requestId: string;
60
- provider: ProviderId;
61
- revision: number;
62
- } | {
63
- type: 'models-save';
64
- requestId: string;
65
- provider: ProviderId;
66
- revision: number;
67
- model: ManualModel;
68
- } | {
69
- type: 'models-delete';
70
- requestId: string;
71
- provider: ProviderId;
72
- revision: number;
73
- id: string;
74
- };
75
- export type ModelMessage = {
76
- type: 'models-page';
77
- requestId: string;
78
- page: ModelPage;
79
- } | {
80
- type: 'models-changed';
81
- provider: ProviderId;
82
- } | {
83
- type: 'models-error';
84
- requestId: string;
85
- provider: ProviderId;
86
- code: string;
87
- message: string;
88
- };
89
- export declare function modelText(value: unknown): value is string;
90
- /** Validate at the wire and durable-data boundaries; fixed-protocol routes accept no override. */
91
- export declare function validManual(provider: ProviderId, value: unknown): value is ManualModel;
92
- //# sourceMappingURL=model-types.d.ts.map
@@ -1,25 +0,0 @@
1
- export const MODEL_PAGE_SIZE = 10;
2
- export const MODEL_PROTOCOLS = {
3
- 'github-copilot': ['openai-completions', 'openai-responses', 'anthropic-messages'],
4
- openrouter: ['openai-completions', 'anthropic-messages'],
5
- 'openai-codex': ['openai-codex-responses'],
6
- xai: ['openai-responses'],
7
- anthropic: ['anthropic-messages'],
8
- 'kimi-coding': ['anthropic-messages'],
9
- };
10
- export function modelText(value) {
11
- return typeof value === 'string' && value.length > 0 && value.length <= 256 && value.trim() === value && !/[\u0000-\u001f\u007f]/u.test(value);
12
- }
13
- /** Validate at the wire and durable-data boundaries; fixed-protocol routes accept no override. */
14
- export function validManual(provider, value) {
15
- if (typeof value !== 'object' || value === null || Array.isArray(value))
16
- return false;
17
- const m = value;
18
- if (Object.keys(m).some(k => !['id', 'name', 'api'].includes(k)) || !modelText(m.id))
19
- return false;
20
- if (m.name !== undefined && !modelText(m.name))
21
- return false;
22
- const choices = MODEL_PROTOCOLS[provider];
23
- return choices.length === 1 ? m.api === undefined : typeof m.api === 'string' && choices.includes(m.api);
24
- }
25
- //# sourceMappingURL=model-types.js.map
@@ -1,11 +0,0 @@
1
- import type { OAuthAccountsController } from './store.js';
2
- import type { OAuthLocaleKey } from './locales.js';
3
- import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
4
- export interface OAuthModelsInjected {
5
- controller: OAuthAccountsController;
6
- t: (key: OAuthLocaleKey) => string;
7
- }
8
- type Props = InjectFace<OAuthModelsInjected>;
9
- export declare function OAuthModelsSection(props: Props): JSX.Element | null;
10
- export {};
11
- //# sourceMappingURL=OAuthModelsSection.d.ts.map
@@ -1,92 +0,0 @@
1
- /** Model-management messages shared by the browser and Host. No credentials travel here. */
2
- import type { ProviderId } from './protocol.js';
3
- export declare const MODEL_PAGE_SIZE = 10;
4
- export declare const MODEL_PROTOCOLS: {
5
- readonly 'github-copilot': readonly ["openai-completions", "openai-responses", "anthropic-messages"];
6
- readonly openrouter: readonly ["openai-completions", "anthropic-messages"];
7
- readonly 'openai-codex': readonly ["openai-codex-responses"];
8
- readonly xai: readonly ["openai-responses"];
9
- readonly anthropic: readonly ["anthropic-messages"];
10
- readonly 'kimi-coding': readonly ["anthropic-messages"];
11
- };
12
- export type ModelProtocol = (typeof MODEL_PROTOCOLS)[ProviderId][number];
13
- export interface ManualModel {
14
- id: string;
15
- name?: string;
16
- api?: ModelProtocol;
17
- }
18
- export interface DiscoveredModel {
19
- id: string;
20
- name?: string;
21
- }
22
- export interface ModelRow {
23
- id: string;
24
- name: string;
25
- source: 'catalog' | 'matched' | 'pending' | 'manual';
26
- api?: string;
27
- }
28
- export interface ModelPage {
29
- provider: ProviderId;
30
- revision: number;
31
- offset: number;
32
- total: number;
33
- rows: ModelRow[];
34
- source: 'catalog' | 'account';
35
- counts: {
36
- matched: number;
37
- pending: number;
38
- manual: number;
39
- };
40
- connected: boolean;
41
- writable: boolean;
42
- managed: boolean;
43
- busy: boolean;
44
- syncedAt?: string;
45
- error?: string;
46
- errorCode?: string;
47
- migration?: {
48
- fields: string[];
49
- models: number;
50
- };
51
- }
52
- export type ModelCommand = {
53
- type: 'models-list';
54
- requestId: string;
55
- provider: ProviderId;
56
- offset: number;
57
- } | {
58
- type: 'models-sync' | 'models-migrate';
59
- requestId: string;
60
- provider: ProviderId;
61
- revision: number;
62
- } | {
63
- type: 'models-save';
64
- requestId: string;
65
- provider: ProviderId;
66
- revision: number;
67
- model: ManualModel;
68
- } | {
69
- type: 'models-delete';
70
- requestId: string;
71
- provider: ProviderId;
72
- revision: number;
73
- id: string;
74
- };
75
- export type ModelMessage = {
76
- type: 'models-page';
77
- requestId: string;
78
- page: ModelPage;
79
- } | {
80
- type: 'models-changed';
81
- provider: ProviderId;
82
- } | {
83
- type: 'models-error';
84
- requestId: string;
85
- provider: ProviderId;
86
- code: string;
87
- message: string;
88
- };
89
- export declare function modelText(value: unknown): value is string;
90
- /** Validate at the wire and durable-data boundaries; fixed-protocol routes accept no override. */
91
- export declare function validManual(provider: ProviderId, value: unknown): value is ManualModel;
92
- //# sourceMappingURL=model-types.d.ts.map