@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.
- package/README.i18n.yaml +2 -2
- package/README.md +27 -63
- package/README.zh.md +27 -63
- package/lib/client.js +7 -500
- package/lib/index.d.ts +3 -7
- package/lib/index.js +78 -82
- package/lib/protocol.d.ts +2 -3
- package/lib/protocol.js +0 -27
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/locales.d.ts +0 -34
- package/lib/types/client/store.d.ts +0 -17
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/protocol.d.ts +2 -3
- package/package.json +35 -62
- package/lib/model-auth.d.ts +0 -8
- package/lib/model-auth.js +0 -48
- package/lib/model-catalog.d.ts +0 -27
- package/lib/model-catalog.js +0 -145
- package/lib/model-discovery.d.ts +0 -22
- package/lib/model-discovery.js +0 -147
- package/lib/model-service.d.ts +0 -64
- package/lib/model-service.js +0 -371
- package/lib/model-types.d.ts +0 -92
- package/lib/model-types.js +0 -25
- package/lib/types/client/OAuthModelsSection.d.ts +0 -11
- package/lib/types/model-types.d.ts +0 -92
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edge-sky/dsh-oauth-adapter",
|
|
3
3
|
"description": "Web OAuth account surface for official DSH authorization flows",
|
|
4
|
-
"version": "0.1.2-
|
|
4
|
+
"version": "0.1.2-rc.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "edge-sky",
|
|
7
7
|
"url": "https://github.com/edge-sky"
|
|
@@ -50,8 +50,7 @@
|
|
|
50
50
|
"build:host": "tsc -p tsconfig.json",
|
|
51
51
|
"build:client:types": "tsc -p tsconfig.client.json",
|
|
52
52
|
"test": "pnpm run build && vitest run",
|
|
53
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
|
|
54
|
-
"prepack": "pnpm run typecheck && pnpm test"
|
|
53
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit"
|
|
55
54
|
},
|
|
56
55
|
"license": "MIT",
|
|
57
56
|
"dsh": {
|
|
@@ -60,30 +59,27 @@
|
|
|
60
59
|
},
|
|
61
60
|
"client": {
|
|
62
61
|
"inject": [
|
|
62
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
63
63
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
64
|
-
"@deepseek-ai/dsh-client-locale"
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-settings-models"
|
|
64
|
+
"@deepseek-ai/dsh-client-locale"
|
|
66
65
|
],
|
|
67
66
|
"platform": "web"
|
|
68
67
|
}
|
|
69
68
|
},
|
|
70
69
|
"peerDependencies": {
|
|
71
|
-
"@deepseek-ai/cordis": ">=4.0.
|
|
72
|
-
"@deepseek-ai/dsh-authorization": ">=0.1.
|
|
73
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.
|
|
74
|
-
"@deepseek-ai/dsh-client-
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-llm
|
|
81
|
-
"@deepseek-ai/dsh-
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"@deepseek-ai/dsh-attachment": ">=0.1.2-rc.1 <0.2.0-0",
|
|
85
|
-
"@deepseek-ai/dsh-fs": ">=0.1.2-rc.1 <0.2.0-0",
|
|
86
|
-
"@deepseek-ai/dsh-client-ui-renderer": ">=0.1.2-rc.1 <0.2.0-0"
|
|
70
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
71
|
+
"@deepseek-ai/dsh-authorization": ">=0.1.1-rc.2 <0.2.0-0",
|
|
72
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0-0",
|
|
73
|
+
"@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2 <0.2.0-0",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.2 <0.2.0-0",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0-0",
|
|
76
|
+
"@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2 <0.2.0-0",
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.1-rc.2 <0.2.0-0",
|
|
78
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2 <0.2.0-0",
|
|
79
|
+
"@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 <0.2.0-0",
|
|
80
|
+
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.1-rc.2 <0.2.0-0",
|
|
81
|
+
"@deepseek-ai/dsh-settings": ">=0.1.1-rc.2 <0.2.0-0",
|
|
82
|
+
"react": ">=18.2.0"
|
|
87
83
|
},
|
|
88
84
|
"peerDependenciesMeta": {
|
|
89
85
|
"@deepseek-ai/cordis": {
|
|
@@ -92,6 +88,9 @@
|
|
|
92
88
|
"@deepseek-ai/dsh-client-locale": {
|
|
93
89
|
"optional": true
|
|
94
90
|
},
|
|
91
|
+
"@deepseek-ai/dsh-client-runtime": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
95
94
|
"@deepseek-ai/dsh-client-ui-settings": {
|
|
96
95
|
"optional": true
|
|
97
96
|
},
|
|
@@ -118,37 +117,25 @@
|
|
|
118
117
|
},
|
|
119
118
|
"react": {
|
|
120
119
|
"optional": true
|
|
121
|
-
},
|
|
122
|
-
"@deepseek-ai/dsh-client-ui-settings-models": {
|
|
123
|
-
"optional": true
|
|
124
|
-
},
|
|
125
|
-
"@deepseek-ai/dsh-attachment": {
|
|
126
|
-
"optional": true
|
|
127
|
-
},
|
|
128
|
-
"@deepseek-ai/dsh-fs": {
|
|
129
|
-
"optional": true
|
|
130
|
-
},
|
|
131
|
-
"@deepseek-ai/dsh-client-ui-renderer": {
|
|
132
|
-
"optional": true
|
|
133
120
|
}
|
|
134
121
|
},
|
|
135
122
|
"dependencies": {
|
|
136
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
137
|
-
"ws": "^8.18.3"
|
|
138
|
-
"@earendil-works/pi-ai": "0.84.4"
|
|
123
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
124
|
+
"ws": "^8.18.3"
|
|
139
125
|
},
|
|
140
126
|
"devDependencies": {
|
|
141
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
142
|
-
"@deepseek-ai/dsh-authorization": "0.1.
|
|
143
|
-
"@deepseek-ai/dsh-client-locale": "0.1.
|
|
144
|
-
"@deepseek-ai/dsh-client-
|
|
145
|
-
"@deepseek-ai/dsh-client-ui-
|
|
146
|
-
"@deepseek-ai/dsh-
|
|
147
|
-
"@deepseek-ai/dsh-
|
|
148
|
-
"@deepseek-ai/dsh-
|
|
149
|
-
"@deepseek-ai/dsh-
|
|
150
|
-
"@deepseek-ai/dsh-llm
|
|
151
|
-
"@deepseek-ai/dsh-
|
|
127
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
128
|
+
"@deepseek-ai/dsh-authorization": "0.1.1-rc.2",
|
|
129
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
130
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
131
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
|
|
132
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
133
|
+
"@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
|
|
134
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
|
|
135
|
+
"@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
|
|
136
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
137
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
|
|
138
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
152
139
|
"@types/node": "^22.20.0",
|
|
153
140
|
"@types/react": "~18.3.1",
|
|
154
141
|
"@types/react-dom": "~18.3.1",
|
|
@@ -157,20 +144,6 @@
|
|
|
157
144
|
"react-dom": "^18.2.0",
|
|
158
145
|
"tsdown": "^0.22.2",
|
|
159
146
|
"typescript": "^6.0.3",
|
|
160
|
-
"vitest": "^4.1.8"
|
|
161
|
-
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.2-rc.1",
|
|
162
|
-
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
163
|
-
"@deepseek-ai/dsh-fs": "0.1.2-rc.1",
|
|
164
|
-
"@deepseek-ai/dsh-settings-file": "0.1.2-rc.1",
|
|
165
|
-
"@deepseek-ai/dsh-credentials-local": "0.1.2-rc.1",
|
|
166
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-rc.1",
|
|
167
|
-
"jsdom": "^26.1.0",
|
|
168
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-rc.1",
|
|
169
|
-
"@deepseek-ai/dsh-client-store": "0.1.2-rc.1",
|
|
170
|
-
"@deepseek-ai/dsh-atomic-write": "0.1.2-rc.1",
|
|
171
|
-
"@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
|
|
172
|
-
"@deepseek-ai/dsh-launch-environment": "0.1.2-rc.1",
|
|
173
|
-
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
174
|
-
"@deepseek-ai/dsh-scope": "0.1.2-rc.1"
|
|
147
|
+
"vitest": "^4.1.8"
|
|
175
148
|
}
|
|
176
149
|
}
|
package/lib/model-auth.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Credential, CredentialStore } from '@earendil-works/pi-ai';
|
|
2
|
-
import type { CredentialProvider, CredentialRecord } from '@deepseek-ai/dsh-credentials';
|
|
3
|
-
export declare function fromRecord(record: CredentialRecord | undefined): Credential | undefined;
|
|
4
|
-
/** Every refresh checks its cancellation generation while holding the credential writer lock. */
|
|
5
|
-
export declare function credentialBridge(credentials: CredentialProvider, check: (provider: string) => void): CredentialStore;
|
|
6
|
-
/** Only provider-authored stable account identifiers are persisted, as a one-way hash. */
|
|
7
|
-
export declare function accountIdentity(provider: string, credential: Credential | undefined): string | undefined;
|
|
8
|
-
//# sourceMappingURL=model-auth.d.ts.map
|
package/lib/model-auth.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/** Bridge public pi-ai authentication to DSH's existing credential records. */
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { recordKeyFor } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
4
|
-
import { ModelOperationError } from './model-discovery.js';
|
|
5
|
-
export function fromRecord(record) {
|
|
6
|
-
if (!record)
|
|
7
|
-
return undefined;
|
|
8
|
-
if (record.kind === 'api-key')
|
|
9
|
-
return { type: 'api_key', ...(record.key === undefined ? {} : { key: record.key }), ...(record.env === undefined ? {} : { env: record.env }) };
|
|
10
|
-
const value = record.payload;
|
|
11
|
-
if (!value || typeof value !== 'object' || Array.isArray(value) || !('type' in value) || value.type !== 'oauth')
|
|
12
|
-
throw new ModelOperationError('credential-format', 'The stored OAuth credential is not supported; reconnect this account.');
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
function toRecord(value) {
|
|
16
|
-
if (value.type === 'api_key')
|
|
17
|
-
return { kind: 'api-key', ...(value.key === undefined ? {} : { key: value.key }), ...(value.env === undefined ? {} : { env: value.env }) };
|
|
18
|
-
// pi-ai includes undefined optional properties; the durable record is JSON.
|
|
19
|
-
return { kind: 'grant', payload: JSON.parse(JSON.stringify(value)) };
|
|
20
|
-
}
|
|
21
|
-
/** Every refresh checks its cancellation generation while holding the credential writer lock. */
|
|
22
|
-
export function credentialBridge(credentials, check) {
|
|
23
|
-
return {
|
|
24
|
-
async read(provider) { check(provider); return fromRecord(await credentials.readRecord(recordKeyFor(provider))); },
|
|
25
|
-
async list() { return []; },
|
|
26
|
-
async modify(provider, mutate) {
|
|
27
|
-
check(provider);
|
|
28
|
-
return fromRecord(await credentials.modifyRecord(recordKeyFor(provider), async (current) => {
|
|
29
|
-
check(provider);
|
|
30
|
-
if (!current)
|
|
31
|
-
throw new ModelOperationError('not-connected', 'This account was disconnected.');
|
|
32
|
-
const next = await mutate(fromRecord(current));
|
|
33
|
-
check(provider);
|
|
34
|
-
return next === undefined ? undefined : toRecord(next);
|
|
35
|
-
}));
|
|
36
|
-
},
|
|
37
|
-
async delete(provider) { check(provider); await credentials.deleteRecord(recordKeyFor(provider)); },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
/** Only provider-authored stable account identifiers are persisted, as a one-way hash. */
|
|
41
|
-
export function accountIdentity(provider, credential) {
|
|
42
|
-
if (credential?.type !== 'oauth')
|
|
43
|
-
return undefined;
|
|
44
|
-
const value = credential;
|
|
45
|
-
const id = value.accountId ?? value.userId ?? value.email;
|
|
46
|
-
return typeof id === 'string' && id.length > 0 ? createHash('sha256').update(`${provider}:${id}`).digest('hex') : undefined;
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=model-auth.js.map
|
package/lib/model-catalog.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Provider } from '@earendil-works/pi-ai';
|
|
2
|
-
import type { PiAiProviderProfile, ResolvedPiAiProviderProfile } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
3
|
-
import { type ProviderId } from './protocol.js';
|
|
4
|
-
import { type ManualModel, type DiscoveredModel, type ModelRow } from './model-types.js';
|
|
5
|
-
export declare const providerFactories: Record<ProviderId, () => Provider>;
|
|
6
|
-
export interface ProviderModels {
|
|
7
|
-
phase: 'pending' | 'managed';
|
|
8
|
-
restoredFromLegacy?: boolean;
|
|
9
|
-
legacy: PiAiProviderProfile;
|
|
10
|
-
manual: ManualModel[];
|
|
11
|
-
discovered?: DiscoveredModel[];
|
|
12
|
-
syncedAt?: string;
|
|
13
|
-
/** A hash of a stable provider account identity; never an access/refresh token. */
|
|
14
|
-
account?: string;
|
|
15
|
-
}
|
|
16
|
-
export interface ModelStore {
|
|
17
|
-
version: 1;
|
|
18
|
-
providers: Partial<Record<ProviderId, ProviderModels>>;
|
|
19
|
-
}
|
|
20
|
-
/** Validate data loaded from the settings file, including the opaque legacy profile. */
|
|
21
|
-
export declare function validateModelStore(value: unknown): ModelStore;
|
|
22
|
-
/** Exact IDs only: unrelated providers and display-name similarities confer no compatibility. */
|
|
23
|
-
export declare function materialize(provider: ProviderId, state: ProviderModels, catalog?: Provider): {
|
|
24
|
-
profile: ResolvedPiAiProviderProfile;
|
|
25
|
-
rows: ModelRow[];
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=model-catalog.d.ts.map
|
package/lib/model-catalog.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/** Build immutable pi-ai profiles from account discovery and explicitly authored models. */
|
|
2
|
-
import { createProvider } from '@earendil-works/pi-ai';
|
|
3
|
-
import { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';
|
|
4
|
-
import { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';
|
|
5
|
-
import { githubCopilotProvider } from '@earendil-works/pi-ai/providers/github-copilot';
|
|
6
|
-
import { openaiCodexProvider } from '@earendil-works/pi-ai/providers/openai-codex';
|
|
7
|
-
import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
|
|
8
|
-
import { kimiCodingProvider } from '@earendil-works/pi-ai/providers/kimi-coding';
|
|
9
|
-
import { openrouterProvider } from '@earendil-works/pi-ai/providers/openrouter';
|
|
10
|
-
import { xaiProvider } from '@earendil-works/pi-ai/providers/xai';
|
|
11
|
-
import { Config as PiConfig } from '@deepseek-ai/dsh-llm-pi-ai';
|
|
12
|
-
import { resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
|
|
13
|
-
import { PROVIDERS } from './protocol.js';
|
|
14
|
-
import { MODEL_PROTOCOLS, modelText, validManual } from './model-types.js';
|
|
15
|
-
import { ModelOperationError } from './model-discovery.js';
|
|
16
|
-
export const providerFactories = {
|
|
17
|
-
'github-copilot': githubCopilotProvider, 'openai-codex': openaiCodexProvider,
|
|
18
|
-
anthropic: anthropicProvider, 'kimi-coding': kimiCodingProvider,
|
|
19
|
-
openrouter: openrouterProvider, xai: xaiProvider,
|
|
20
|
-
};
|
|
21
|
-
/** Validate data loaded from the settings file, including the opaque legacy profile. */
|
|
22
|
-
export function validateModelStore(value) {
|
|
23
|
-
if (!value || typeof value !== 'object')
|
|
24
|
-
throw new Error('Invalid oauth-models settings');
|
|
25
|
-
const store = value;
|
|
26
|
-
if (store.version !== 1 || !store.providers || typeof store.providers !== 'object' || Array.isArray(store.providers))
|
|
27
|
-
throw new Error('Unsupported oauth-models format');
|
|
28
|
-
for (const [id, entry] of Object.entries(store.providers)) {
|
|
29
|
-
if (!PROVIDERS.some(p => p.id === id) || !entry || !['pending', 'managed'].includes(entry.phase))
|
|
30
|
-
throw new Error('Invalid OAuth model provider');
|
|
31
|
-
if (!Array.isArray(entry.manual) || entry.manual.some(m => !validManual(id, m)))
|
|
32
|
-
throw new Error('Invalid manual OAuth model');
|
|
33
|
-
if (new Set(entry.manual.map(m => m.id)).size !== entry.manual.length)
|
|
34
|
-
throw new Error('Duplicate manual OAuth model');
|
|
35
|
-
if (entry.discovered !== undefined && (!Array.isArray(entry.discovered) || entry.discovered.some(m => !m || !modelText(m.id) || (m.name !== undefined && !modelText(m.name)))))
|
|
36
|
-
throw new Error('Invalid discovered OAuth model');
|
|
37
|
-
if (entry.syncedAt !== undefined && (typeof entry.syncedAt !== 'string' || !Number.isFinite(Date.parse(entry.syncedAt))))
|
|
38
|
-
throw new Error('Invalid synchronization time');
|
|
39
|
-
if (entry.account !== undefined && (typeof entry.account !== 'string' || !/^[a-f0-9]{64}$/.test(entry.account)))
|
|
40
|
-
throw new Error('Invalid account identity');
|
|
41
|
-
if (!entry.legacy || typeof entry.legacy !== 'object' || Array.isArray(entry.legacy))
|
|
42
|
-
throw new Error('Invalid legacy profile');
|
|
43
|
-
PiConfig({ providers: { [id]: entry.legacy } });
|
|
44
|
-
}
|
|
45
|
-
return store;
|
|
46
|
-
}
|
|
47
|
-
/** Exact IDs only: unrelated providers and display-name similarities confer no compatibility. */
|
|
48
|
-
export function materialize(provider, state, catalog = providerFactories[provider]()) {
|
|
49
|
-
const defaults = new Map(catalog.getModels().map(m => [m.id, m]));
|
|
50
|
-
const parsed = PiConfig({ providers: { [provider]: state.legacy } }).providers?.[provider];
|
|
51
|
-
if (!parsed)
|
|
52
|
-
throw new Error('Missing provider profile');
|
|
53
|
-
const manual = new Map(state.manual.map(m => [m.id, m]));
|
|
54
|
-
const configured = new Map((parsed.models ?? []).map(m => [m.id, m]));
|
|
55
|
-
const candidates = state.discovered ?? (configured.size ? [...configured.values()] : [...defaults.values()]);
|
|
56
|
-
const ids = new Set([...candidates.map(m => m.id), ...manual.keys()]);
|
|
57
|
-
const rows = [];
|
|
58
|
-
const models = [];
|
|
59
|
-
const caps = new Map();
|
|
60
|
-
for (const id of ids) {
|
|
61
|
-
const base = defaults.get(id);
|
|
62
|
-
const authored = manual.get(id);
|
|
63
|
-
const previous = configured.get(id) ?? parsed.modelOverrides?.[id];
|
|
64
|
-
const candidate = candidates.find(m => m.id === id);
|
|
65
|
-
if (!base && !authored) {
|
|
66
|
-
rows.push({ id, name: candidate?.name ?? id, source: 'pending' });
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const choices = MODEL_PROTOCOLS[provider];
|
|
70
|
-
const api = authored ? authored.api ?? (choices.length === 1 ? choices[0] : base?.api) : parsed.api ?? base?.api;
|
|
71
|
-
if (!api)
|
|
72
|
-
throw new ModelOperationError('model-protocol', `Model ${id} needs a protocol.`);
|
|
73
|
-
const peers = [...defaults.values()].filter(m => m.api === api);
|
|
74
|
-
// Only provider-wide shared transport metadata can default an unknown model.
|
|
75
|
-
const shared = (items) => {
|
|
76
|
-
const first = items[0];
|
|
77
|
-
if (!first)
|
|
78
|
-
return undefined;
|
|
79
|
-
return Object.fromEntries(Object.entries(first).filter(([k, v]) => items.every(item => item && JSON.stringify(item[k]) === JSON.stringify(v))));
|
|
80
|
-
};
|
|
81
|
-
const headers = shared(peers.map(m => m.headers));
|
|
82
|
-
const compat = shared(peers.map(m => m.compat));
|
|
83
|
-
const generic = {
|
|
84
|
-
id, name: id, provider, api: api, baseUrl: parsed.baseURL ?? (provider === 'openrouter' && api === 'anthropic-messages' ? 'https://openrouter.ai/api' : catalog.baseUrl) ?? '',
|
|
85
|
-
reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
86
|
-
contextWindow: parsed.defaultContextWindow ?? 262144, maxTokens: parsed.defaultMaxTokens ?? 32768,
|
|
87
|
-
...(headers ? { headers } : {}), ...(compat ? { compat } : {}),
|
|
88
|
-
};
|
|
89
|
-
const model = {
|
|
90
|
-
...(base?.api === api ? base : { ...generic, ...(base ? { input: base.input, reasoning: base.reasoning, contextWindow: base.contextWindow, maxTokens: base.maxTokens, cost: base.cost } : {}) }), ...previous, id, provider, api: api,
|
|
91
|
-
name: authored ? authored.name ?? id : previous?.name ?? base?.name ?? candidate?.name ?? id,
|
|
92
|
-
...(parsed.baseURL === undefined ? {} : { baseUrl: parsed.baseURL }),
|
|
93
|
-
};
|
|
94
|
-
const accepted = api === 'openai-completions' ? ['supportsStore', 'supportsDeveloperRole', 'supportsReasoningEffort', 'supportsUsageInStreaming', 'supportsFinishReason', 'maxTokensField', 'requiresToolResultName', 'requiresAssistantAfterToolResult', 'requiresThinkingAsText', 'requiresReasoningContentOnAssistantMessages', 'thinkingFormat', 'chatTemplateKwargs', 'chatTemplateArgs', 'supportsThinkingTokenBudget', 'supportsStrictMode', 'cacheControlFormat', 'supportsLongCacheRetention'] : api === 'anthropic-messages' ? ['supportsEagerToolInputStreaming', 'supportsLongCacheRetention', 'supportsCacheControlOnTools', 'supportsTemperature', 'forceAdaptiveThinking', 'allowEmptySignature', 'supportsStrictTools'] : ['supportsDeveloperRole', 'supportsStrictMode', 'supportsLongCacheRetention'];
|
|
95
|
-
const routeCompat = Object.fromEntries(Object.entries(parsed.compat ?? {}).filter(([key]) => accepted.includes(key)));
|
|
96
|
-
for (const key of Object.keys(previous?.compat ?? {}))
|
|
97
|
-
if (!accepted.includes(key))
|
|
98
|
-
throw new ModelOperationError('model-compat', `Model ${id} has an incompatible ${key} option.`);
|
|
99
|
-
if (Object.keys(routeCompat).length || previous?.compat)
|
|
100
|
-
model.compat = { ...(base?.api === api ? base.compat : generic.compat), ...routeCompat, ...previous?.compat };
|
|
101
|
-
if (previous?.reasoningEfforts !== undefined) {
|
|
102
|
-
model.reasoning = previous.reasoningEfforts !== false;
|
|
103
|
-
if (previous.reasoningEfforts !== false) {
|
|
104
|
-
const efforts = previous.reasoningEfforts;
|
|
105
|
-
if (!Object.entries(efforts).some(([level, value]) => level !== 'off' && typeof value === 'string' && value.length))
|
|
106
|
-
throw new ModelOperationError('model-reasoning', `Model ${id} needs explicit reasoning levels.`);
|
|
107
|
-
model.thinkingLevelMap = Object.fromEntries(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'].flatMap(level => level === 'off' && efforts.off === null ? [] : [[level, efforts[level] ?? null]]));
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (!model.baseUrl)
|
|
111
|
-
throw new ModelOperationError('model-endpoint', `Provider ${provider} has no endpoint.`);
|
|
112
|
-
if (previous?.maxTokens !== undefined)
|
|
113
|
-
caps.set(id, previous.maxTokens);
|
|
114
|
-
models.push(freezeModel(model));
|
|
115
|
-
rows.push({ id, name: model.name, api, source: authored ? 'manual' : state.discovered ? 'matched' : 'catalog' });
|
|
116
|
-
}
|
|
117
|
-
const { models: _models, displayName: _displayName, apiKeyEnv: _key, retryPolicy: _retry, ...rest } = parsed;
|
|
118
|
-
const piProvider = provider === 'openrouter' ? createProvider({
|
|
119
|
-
id: provider, name: catalog.name, baseUrl: catalog.baseUrl, auth: catalog.auth, models,
|
|
120
|
-
api: { 'openai-completions': openAICompletionsApi(), 'anthropic-messages': anthropicMessagesApi() },
|
|
121
|
-
}) : { ...catalog, getModels: () => models, filterModels: (current) => current };
|
|
122
|
-
return {
|
|
123
|
-
rows,
|
|
124
|
-
profile: {
|
|
125
|
-
...rest, provider, displayName: parsed.displayName ?? PROVIDERS.find(p => p.id === provider).modelGroupLabel,
|
|
126
|
-
streamIdleTimeoutMs: parsed.streamIdleTimeoutMs ?? 300000,
|
|
127
|
-
maxRequestImageBytes: parsed.maxRequestImageBytes ?? 20971520,
|
|
128
|
-
requestImagePixelBudget: parsed.requestImagePixelBudget ?? 4194304,
|
|
129
|
-
requestImageMaxBytes: parsed.requestImageMaxBytes ?? 1048576,
|
|
130
|
-
retryPolicy: resolveRetryPolicy(parsed.retryPolicy, `oauth-models/${provider}`),
|
|
131
|
-
configuredMaxTokens: caps,
|
|
132
|
-
piProvider,
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
/** Freeze JSON model metadata recursively; prepared calls retain their captured descriptions. */
|
|
137
|
-
function freezeModel(value) {
|
|
138
|
-
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
139
|
-
for (const child of Object.values(value))
|
|
140
|
-
freezeModel(child);
|
|
141
|
-
Object.freeze(value);
|
|
142
|
-
}
|
|
143
|
-
return value;
|
|
144
|
-
}
|
|
145
|
-
//# sourceMappingURL=model-catalog.js.map
|
package/lib/model-discovery.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/** Account-scoped model discovery. Each provider owns its endpoint and response parser. */
|
|
2
|
-
import type { ModelAuth } from '@earendil-works/pi-ai';
|
|
3
|
-
import type { ProviderId } from './protocol.js';
|
|
4
|
-
import { type DiscoveredModel } from './model-types.js';
|
|
5
|
-
export declare class ModelOperationError extends Error {
|
|
6
|
-
readonly code: string;
|
|
7
|
-
constructor(code: string, message: string);
|
|
8
|
-
}
|
|
9
|
-
export interface DiscoveryOptions {
|
|
10
|
-
signal: AbortSignal;
|
|
11
|
-
maxBytes: number;
|
|
12
|
-
maxPages: number;
|
|
13
|
-
codexClientVersion: string;
|
|
14
|
-
fetch?: typeof globalThis.fetch;
|
|
15
|
-
}
|
|
16
|
-
export interface DiscoveryAuth {
|
|
17
|
-
auth: ModelAuth;
|
|
18
|
-
accountId?: string;
|
|
19
|
-
}
|
|
20
|
-
/** Fetch complete account-visible results; never substitute the public OpenRouter catalog. */
|
|
21
|
-
export declare function discoverAccountModels(provider: ProviderId, resolved: DiscoveryAuth, options: DiscoveryOptions): Promise<DiscoveredModel[]>;
|
|
22
|
-
//# sourceMappingURL=model-discovery.d.ts.map
|
package/lib/model-discovery.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { modelText } from './model-types.js';
|
|
2
|
-
export class ModelOperationError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
constructor(code, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.code = code;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
function record(value) {
|
|
10
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
11
|
-
}
|
|
12
|
-
function invalid() { throw new ModelOperationError('invalid-catalog', 'The provider returned an invalid model catalog.'); }
|
|
13
|
-
/** Read a bounded page; errors never include response bodies or token-bearing URLs. */
|
|
14
|
-
async function readPage(url, headers, options) {
|
|
15
|
-
const response = await (options.fetch ?? globalThis.fetch)(url, { headers, signal: options.signal, redirect: 'error' });
|
|
16
|
-
if (!response.ok) {
|
|
17
|
-
await response.body?.cancel();
|
|
18
|
-
throw new ModelOperationError(response.status === 401 || response.status === 403 ? 'discovery-auth' : response.status === 404 || response.status === 405 ? 'discovery-unsupported' : 'discovery-failed', `Model discovery returned HTTP ${response.status}.`);
|
|
19
|
-
}
|
|
20
|
-
if (!response.body)
|
|
21
|
-
invalid();
|
|
22
|
-
const reader = response.body.getReader();
|
|
23
|
-
const chunks = [];
|
|
24
|
-
let size = 0;
|
|
25
|
-
try {
|
|
26
|
-
for (;;) {
|
|
27
|
-
const part = await reader.read();
|
|
28
|
-
if (part.done)
|
|
29
|
-
break;
|
|
30
|
-
size += part.value.byteLength;
|
|
31
|
-
if (size > options.maxBytes)
|
|
32
|
-
throw new ModelOperationError('catalog-too-large', 'The model catalog exceeds the configured response limit.');
|
|
33
|
-
chunks.push(part.value);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
finally {
|
|
37
|
-
await reader.cancel().catch(() => { });
|
|
38
|
-
}
|
|
39
|
-
const bytes = new Uint8Array(size);
|
|
40
|
-
let offset = 0;
|
|
41
|
-
for (const chunk of chunks) {
|
|
42
|
-
bytes.set(chunk, offset);
|
|
43
|
-
offset += chunk.byteLength;
|
|
44
|
-
}
|
|
45
|
-
let value;
|
|
46
|
-
try {
|
|
47
|
-
value = JSON.parse(new TextDecoder().decode(bytes));
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
invalid();
|
|
51
|
-
}
|
|
52
|
-
return record(value) ?? invalid();
|
|
53
|
-
}
|
|
54
|
-
/** Fetch complete account-visible results; never substitute the public OpenRouter catalog. */
|
|
55
|
-
export async function discoverAccountModels(provider, resolved, options) {
|
|
56
|
-
const headers = new Headers(resolved.auth.headers);
|
|
57
|
-
headers.set('accept', 'application/json');
|
|
58
|
-
if (resolved.auth.apiKey)
|
|
59
|
-
headers.set('authorization', `Bearer ${resolved.auth.apiKey}`);
|
|
60
|
-
let url;
|
|
61
|
-
switch (provider) {
|
|
62
|
-
case 'github-copilot':
|
|
63
|
-
url = new URL(`${(resolved.auth.baseUrl ?? 'https://api.individual.githubcopilot.com').replace(/\/+$/, '')}/models`);
|
|
64
|
-
headers.set('editor-version', 'vscode/1.107.0');
|
|
65
|
-
headers.set('editor-plugin-version', 'copilot-chat/0.35.0');
|
|
66
|
-
headers.set('copilot-integration-id', 'vscode-chat');
|
|
67
|
-
headers.set('user-agent', 'GitHubCopilotChat/0.35.0');
|
|
68
|
-
headers.set('x-github-api-version', '2026-06-01');
|
|
69
|
-
break;
|
|
70
|
-
case 'openai-codex':
|
|
71
|
-
if (!resolved.accountId)
|
|
72
|
-
throw new ModelOperationError('discovery-auth', 'The Codex credential has no account ID; reconnect this account.');
|
|
73
|
-
url = new URL('https://chatgpt.com/backend-api/codex/models');
|
|
74
|
-
url.searchParams.set('client_version', options.codexClientVersion);
|
|
75
|
-
headers.set('chatgpt-account-id', resolved.accountId);
|
|
76
|
-
headers.set('originator', 'pi');
|
|
77
|
-
break;
|
|
78
|
-
case 'anthropic':
|
|
79
|
-
url = new URL('https://api.anthropic.com/v1/models?limit=1000');
|
|
80
|
-
headers.set('anthropic-version', '2023-06-01');
|
|
81
|
-
headers.set('anthropic-beta', 'oauth-2025-04-20');
|
|
82
|
-
break;
|
|
83
|
-
case 'kimi-coding':
|
|
84
|
-
url = new URL('https://api.kimi.com/coding/v1/models');
|
|
85
|
-
break;
|
|
86
|
-
case 'openrouter':
|
|
87
|
-
url = new URL('https://openrouter.ai/api/v1/models/user?limit=500&offset=0');
|
|
88
|
-
break;
|
|
89
|
-
case 'xai':
|
|
90
|
-
url = new URL('https://api.x.ai/v1/models');
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
const results = new Map();
|
|
94
|
-
const cursors = new Set();
|
|
95
|
-
for (let page = 0; page < options.maxPages; page += 1) {
|
|
96
|
-
options.signal.throwIfAborted();
|
|
97
|
-
const body = await readPage(url, headers, options);
|
|
98
|
-
const data = provider === 'openai-codex' ? body.models : body.data;
|
|
99
|
-
if (!Array.isArray(data))
|
|
100
|
-
invalid();
|
|
101
|
-
for (const raw of data) {
|
|
102
|
-
const item = record(raw) ?? invalid();
|
|
103
|
-
const id = provider === 'openai-codex' ? item.slug : item.id;
|
|
104
|
-
if (!modelText(id))
|
|
105
|
-
invalid();
|
|
106
|
-
if (provider === 'github-copilot') {
|
|
107
|
-
const capabilities = record(item.capabilities);
|
|
108
|
-
if (record(capabilities?.supports)?.tool_calls === false)
|
|
109
|
-
continue;
|
|
110
|
-
if (item.model_picker_enabled !== true || record(item.policy)?.state === 'disabled')
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (provider === 'openai-codex' && item.visibility !== 'list')
|
|
114
|
-
continue;
|
|
115
|
-
const name = item.display_name ?? item.name;
|
|
116
|
-
if (name !== undefined && !modelText(name))
|
|
117
|
-
invalid();
|
|
118
|
-
results.set(id, { id, ...(name === undefined ? {} : { name }) });
|
|
119
|
-
}
|
|
120
|
-
if (provider === 'openrouter') {
|
|
121
|
-
if (body.total_count !== undefined && (!Number.isSafeInteger(body.total_count) || body.total_count < 0))
|
|
122
|
-
invalid();
|
|
123
|
-
const consumed = Number(url.searchParams.get('offset')) + data.length;
|
|
124
|
-
const more = body.total_count !== undefined ? consumed < body.total_count : data.length === 500;
|
|
125
|
-
if (more) {
|
|
126
|
-
if (data.length === 0 || cursors.has(JSON.stringify(data)))
|
|
127
|
-
invalid();
|
|
128
|
-
cursors.add(JSON.stringify(data));
|
|
129
|
-
url = new URL(url);
|
|
130
|
-
url.searchParams.set('offset', String(consumed));
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
return [...results.values()];
|
|
134
|
-
}
|
|
135
|
-
if (body.has_more === undefined || body.has_more === false)
|
|
136
|
-
return [...results.values()];
|
|
137
|
-
if (provider !== 'anthropic')
|
|
138
|
-
invalid();
|
|
139
|
-
if (body.has_more !== true || !modelText(body.last_id) || cursors.has(body.last_id))
|
|
140
|
-
invalid();
|
|
141
|
-
cursors.add(body.last_id);
|
|
142
|
-
url = new URL(url);
|
|
143
|
-
url.searchParams.set('after_id', body.last_id);
|
|
144
|
-
}
|
|
145
|
-
throw new ModelOperationError('catalog-too-large', 'The model catalog exceeds the configured page limit.');
|
|
146
|
-
}
|
|
147
|
-
//# sourceMappingURL=model-discovery.js.map
|
package/lib/model-service.d.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/** Persistent OAuth model management and reversible ownership of existing provider routes. */
|
|
2
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
-
import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
|
|
4
|
-
import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
|
|
5
|
-
import type LlmRuntime from '@deepseek-ai/dsh-llm';
|
|
6
|
-
import { type ProviderId } from './protocol.js';
|
|
7
|
-
import { type ModelPage, type ManualModel } from './model-types.js';
|
|
8
|
-
export interface ModelServiceOptions {
|
|
9
|
-
timeoutMs: number;
|
|
10
|
-
maxResponseBytes: number;
|
|
11
|
-
maxPages: number;
|
|
12
|
-
codexClientVersion: string;
|
|
13
|
-
}
|
|
14
|
-
export type ModelContext = Context & {
|
|
15
|
-
settings: SettingsProvider;
|
|
16
|
-
credentials: CredentialProvider;
|
|
17
|
-
llm: LlmRuntime;
|
|
18
|
-
};
|
|
19
|
-
export declare class OAuthModelService {
|
|
20
|
-
private readonly ctx;
|
|
21
|
-
private readonly options;
|
|
22
|
-
private readonly changed;
|
|
23
|
-
private profiles;
|
|
24
|
-
private handle;
|
|
25
|
-
private readonly queues;
|
|
26
|
-
private readonly syncs;
|
|
27
|
-
private readonly controllers;
|
|
28
|
-
private readonly generations;
|
|
29
|
-
private readonly loggingIn;
|
|
30
|
-
private readonly errors;
|
|
31
|
-
private readonly disposers;
|
|
32
|
-
private closed;
|
|
33
|
-
private readonly stop;
|
|
34
|
-
private readonly signedOut;
|
|
35
|
-
private readonly adapter;
|
|
36
|
-
readonly ready: Promise<void>;
|
|
37
|
-
constructor(ctx: ModelContext, options: ModelServiceOptions, changed: (provider: ProviderId) => void);
|
|
38
|
-
private state;
|
|
39
|
-
revision(): number;
|
|
40
|
-
isManaged(provider: ProviderId): boolean;
|
|
41
|
-
private checkRevision;
|
|
42
|
-
private checkOpen;
|
|
43
|
-
private save;
|
|
44
|
-
private report;
|
|
45
|
-
private enqueue;
|
|
46
|
-
private initialize;
|
|
47
|
-
private legacy;
|
|
48
|
-
private connected;
|
|
49
|
-
private publish;
|
|
50
|
-
private reconcile;
|
|
51
|
-
/** Wait on the actual route registry; settings persistence alone does not imply adapter release. */
|
|
52
|
-
private releaseLegacy;
|
|
53
|
-
private ensureManaged;
|
|
54
|
-
page(provider: ProviderId, offset: number): Promise<ModelPage>;
|
|
55
|
-
migrate(provider: ProviderId, revision: number): Promise<void>;
|
|
56
|
-
saveManual(provider: ProviderId, model: ManualModel, revision: number): Promise<void>;
|
|
57
|
-
deleteManual(provider: ProviderId, id: string, revision: number): Promise<void>;
|
|
58
|
-
sync(provider: ProviderId, revision: number): Promise<void>;
|
|
59
|
-
loginStarted(provider: ProviderId): void;
|
|
60
|
-
loginFinished(provider: ProviderId, authorized: boolean): Promise<void>;
|
|
61
|
-
forget(provider: ProviderId): Promise<void>;
|
|
62
|
-
dispose(): Promise<void>;
|
|
63
|
-
}
|
|
64
|
-
//# sourceMappingURL=model-service.d.ts.map
|