pi-devin-auth 0.1.1 → 0.1.2
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/extensions/index.ts +94 -27
- package/package.json +1 -1
- package/src/cloud-direct/catalog.ts +1 -1
- package/src/cloud-direct/index.ts +1 -0
- package/src/models.ts +207 -106
package/extensions/index.ts
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* Registers the `devin` provider with pi's ExtensionAPI, wiring up:
|
|
5
5
|
* - OAuth login via Windsurf's browser sign-in flow (`loginDevin`)
|
|
6
6
|
* - A no-op token refresh (Windsurf api_keys are long-lived)
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
7
|
+
* - Live model catalog fetch from Cognition's GetCascadeModelConfigs
|
|
8
|
+
* after login, filtered to 11 wanted model families
|
|
9
|
+
* - `/devin-refresh` command to manually re-fetch the catalog
|
|
10
|
+
* - `/devin-status` command to check auth state
|
|
11
|
+
* - `session_start` auto-fetch when already logged in
|
|
9
12
|
* - Streaming chat completions through Devin Cloud (`streamDevin`)
|
|
10
13
|
*
|
|
11
|
-
* The provider
|
|
12
|
-
*
|
|
13
|
-
* OAuth-issued api_key.
|
|
14
|
+
* The provider uses `streamSimple` — no background proxy. All routing
|
|
15
|
+
* and auth are handled internally via the OAuth-issued api_key.
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
@@ -22,7 +24,8 @@ import type {
|
|
|
22
24
|
} from '@earendil-works/pi-ai';
|
|
23
25
|
import { streamDevin } from '../src/stream.js';
|
|
24
26
|
import { loginDevin } from '../src/oauth/login.js';
|
|
25
|
-
import { FALLBACK_MODELS } from '../src/models.js';
|
|
27
|
+
import { buildLiveModels, FALLBACK_MODELS, DEFAULT_HOST } from '../src/models.js';
|
|
28
|
+
import { getCachedCatalog, clearCachedCatalog } from '../src/cloud-direct/catalog.js';
|
|
26
29
|
import { DEFAULT_REGION } from '../src/oauth/types.js';
|
|
27
30
|
|
|
28
31
|
const PROVIDER_ID = 'devin';
|
|
@@ -31,40 +34,41 @@ const OAUTH_NAME = 'Devin (Cognition / Windsurf)';
|
|
|
31
34
|
const API_IDENTIFIER = 'devin-cloud';
|
|
32
35
|
// pi requires baseUrl when models are defined, even with streamSimple.
|
|
33
36
|
// streamSimple ignores this — it routes internally — but the field must be present.
|
|
34
|
-
const PLACEHOLDER_BASE_URL =
|
|
37
|
+
const PLACEHOLDER_BASE_URL = DEFAULT_HOST;
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
let _pi: ExtensionAPI | null = null;
|
|
40
|
+
|
|
41
|
+
function registerDevinProvider(pi: ExtensionAPI, models: typeof FALLBACK_MODELS): void {
|
|
37
42
|
pi.registerProvider(PROVIDER_ID, {
|
|
38
43
|
name: PROVIDER_NAME,
|
|
39
44
|
api: API_IDENTIFIER,
|
|
40
45
|
baseUrl: PLACEHOLDER_BASE_URL,
|
|
41
|
-
|
|
42
|
-
// OAuth-issued api_key. baseUrl is only here to satisfy pi's
|
|
43
|
-
// validation requirement when models are defined.
|
|
44
|
-
models: FALLBACK_MODELS,
|
|
46
|
+
models,
|
|
45
47
|
oauth: {
|
|
46
48
|
name: OAUTH_NAME,
|
|
47
|
-
async login(
|
|
48
|
-
callbacks
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
50
|
+
const credentials = await loginDevin(callbacks, DEFAULT_REGION);
|
|
51
|
+
if (_pi) {
|
|
52
|
+
try {
|
|
53
|
+
clearCachedCatalog();
|
|
54
|
+
const catalog = await getCachedCatalog(
|
|
55
|
+
credentials.access,
|
|
56
|
+
DEFAULT_HOST,
|
|
57
|
+
);
|
|
58
|
+
const liveModels = buildLiveModels(catalog);
|
|
59
|
+
registerDevinProvider(_pi, liveModels);
|
|
60
|
+
} catch {
|
|
61
|
+
// keep static models if catalog fetch fails
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return credentials;
|
|
51
65
|
},
|
|
52
|
-
async refreshToken(
|
|
53
|
-
credentials: OAuthCredentials,
|
|
54
|
-
): Promise<OAuthCredentials> {
|
|
55
|
-
// Windsurf's RegisterUser returns a long-lived api_key
|
|
56
|
-
// with no refresh token. The key is effectively
|
|
57
|
-
// non-expiring, so refreshToken is a no-op — return the
|
|
58
|
-
// same credentials.
|
|
66
|
+
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
59
67
|
return credentials;
|
|
60
68
|
},
|
|
61
69
|
getApiKey(credentials: OAuthCredentials): string {
|
|
62
70
|
return credentials.access;
|
|
63
71
|
},
|
|
64
|
-
// modifyModels is sync in pi v0.80.x, so we can't do an async
|
|
65
|
-
// catalog fetch here. The static FALLBACK_MODELS list is used
|
|
66
|
-
// for v0.1.0. Dynamic catalog fetch will be added in a future
|
|
67
|
-
// version once we've verified the gRPC response shape.
|
|
68
72
|
modifyModels(models: Model<Api>[]): Model<Api>[] {
|
|
69
73
|
return models;
|
|
70
74
|
},
|
|
@@ -72,3 +76,66 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
72
76
|
streamSimple: streamDevin,
|
|
73
77
|
});
|
|
74
78
|
}
|
|
79
|
+
|
|
80
|
+
export default async function (pi: ExtensionAPI): Promise<void> {
|
|
81
|
+
_pi = pi;
|
|
82
|
+
|
|
83
|
+
registerDevinProvider(pi, FALLBACK_MODELS);
|
|
84
|
+
|
|
85
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
86
|
+
try {
|
|
87
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
88
|
+
if (apiKey && _pi) {
|
|
89
|
+
const catalog = await getCachedCatalog(apiKey, DEFAULT_HOST);
|
|
90
|
+
const liveModels = buildLiveModels(catalog);
|
|
91
|
+
registerDevinProvider(_pi, liveModels);
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// keep static models
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
pi.registerCommand('devin-refresh', {
|
|
99
|
+
description: 'Refresh Devin model catalog from Cognition',
|
|
100
|
+
handler: async (_args, ctx) => {
|
|
101
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
ctx.ui.notify(
|
|
104
|
+
'Devin: not signed in. Run /login devin',
|
|
105
|
+
'warning',
|
|
106
|
+
);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
clearCachedCatalog();
|
|
110
|
+
try {
|
|
111
|
+
const catalog = await getCachedCatalog(apiKey, DEFAULT_HOST);
|
|
112
|
+
const liveModels = buildLiveModels(catalog);
|
|
113
|
+
registerDevinProvider(pi, liveModels);
|
|
114
|
+
ctx.ui.notify(
|
|
115
|
+
`Devin: refreshed ${liveModels.length} models.`,
|
|
116
|
+
'info',
|
|
117
|
+
);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
ctx.ui.notify(
|
|
120
|
+
`Devin: refresh error - ${e instanceof Error ? e.message : String(e)}`,
|
|
121
|
+
'error',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
pi.registerCommand('devin-status', {
|
|
128
|
+
description: 'Show Devin auth status',
|
|
129
|
+
handler: async (_args, ctx) => {
|
|
130
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
131
|
+
ctx.ui.notify(
|
|
132
|
+
apiKey ? 'Devin: authenticated' : 'Devin: not signed in. Run /login devin',
|
|
133
|
+
apiKey ? 'info' : 'warning',
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
pi.on('session_shutdown', async () => {
|
|
139
|
+
_pi = null;
|
|
140
|
+
});
|
|
141
|
+
}
|
package/package.json
CHANGED
package/src/models.ts
CHANGED
|
@@ -1,135 +1,236 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Model list for the Devin (Cognition) provider.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Two paths:
|
|
5
|
+
* 1. {@link FALLBACK_MODELS} — 11 static entries shown before login or
|
|
6
|
+
* when the live catalog fetch fails. Carries real pricing.
|
|
7
|
+
* 2. {@link buildLiveModels} — filters the live `GetCascadeModelConfigs`
|
|
8
|
+
* catalog to the 11 model families we care about, stamping each entry
|
|
9
|
+
* with pricing/metadata from {@link MODEL_OVERRIDES}.
|
|
10
10
|
*
|
|
11
11
|
* The catalog only carries `modelUid`, `label`, and `disabled` — it does
|
|
12
|
-
* NOT include context window, max output tokens,
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* the
|
|
12
|
+
* NOT include context window, max output tokens, reasoning capability, or
|
|
13
|
+
* pricing. We supply all of those from {@link MODEL_OVERRIDES}, keyed by
|
|
14
|
+
* prefix so variants (e.g. `claude-opus-4-8-medium`, `gpt-5-6-sol-low`)
|
|
15
|
+
* inherit the correct metadata from their parent family.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { ProviderModelConfig } from '@earendil-works/pi-coding-agent';
|
|
19
|
-
import {
|
|
19
|
+
import type { CacheEntry } from './cloud-direct/index.js';
|
|
20
20
|
|
|
21
21
|
/** Default Cognition/Codeium host. */
|
|
22
22
|
const DEFAULT_HOST = 'https://server.codeium.com';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Prefixes of the 11 model families we want from the live catalog.
|
|
26
|
+
* Prefix matching catches variants (e.g. `swe-1-7-lightning`,
|
|
27
|
+
* `gpt-5-6-sol-low`, `claude-opus-4-8-high`).
|
|
27
28
|
*/
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
id: 'swe-1-6-slow',
|
|
40
|
-
name: 'SWE 1.6 (Slow)',
|
|
41
|
-
reasoning: true,
|
|
42
|
-
input: ['text', 'image'],
|
|
43
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
44
|
-
contextWindow: 256000,
|
|
45
|
-
maxTokens: 128000,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'kimi-k2-6',
|
|
49
|
-
name: 'Kimi K2.6',
|
|
50
|
-
reasoning: true,
|
|
51
|
-
input: ['text', 'image'],
|
|
52
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
53
|
-
contextWindow: 256000,
|
|
54
|
-
maxTokens: 128000,
|
|
55
|
-
},
|
|
29
|
+
export const WANTED_PREFIXES: readonly string[] = [
|
|
30
|
+
'swe-1-7',
|
|
31
|
+
'gpt-5-6-sol',
|
|
32
|
+
'gpt-5-6-luna',
|
|
33
|
+
'gpt-5-6-terra',
|
|
34
|
+
'claude-opus-4-8',
|
|
35
|
+
'claude-fable-5',
|
|
36
|
+
'claude-sonnet-5',
|
|
37
|
+
'glm-5-2',
|
|
38
|
+
'kimi-k2-7',
|
|
39
|
+
'grok-4-5',
|
|
56
40
|
];
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
function matchesWantedPrefix(uid: string): boolean {
|
|
43
|
+
for (const prefix of WANTED_PREFIXES) {
|
|
44
|
+
if (uid === prefix || uid.startsWith(prefix + '-') || uid.startsWith(prefix + '_')) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
64
49
|
}
|
|
65
50
|
|
|
66
51
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
52
|
+
* Per-model metadata + pricing, keyed by prefix.
|
|
53
|
+
*
|
|
54
|
+
* All prices are per million tokens (USD). Used by both
|
|
55
|
+
* {@link FALLBACK_MODELS} and {@link buildLiveModels} so there is a single
|
|
56
|
+
* source of truth for pricing.
|
|
71
57
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
58
|
+
interface ModelMeta {
|
|
59
|
+
contextWindow: number;
|
|
60
|
+
maxTokens: number;
|
|
61
|
+
reasoning: boolean;
|
|
62
|
+
input: ('text' | 'image')[];
|
|
63
|
+
cost: {
|
|
64
|
+
input: number;
|
|
65
|
+
output: number;
|
|
66
|
+
cacheRead: number;
|
|
67
|
+
cacheWrite: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MODEL_META: Map<string, ModelMeta> = new Map([
|
|
72
|
+
['swe-1-7', {
|
|
73
|
+
contextWindow: 256_000,
|
|
74
|
+
maxTokens: 128_000,
|
|
75
|
+
reasoning: true,
|
|
76
|
+
input: ['text', 'image'],
|
|
77
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
78
|
+
}],
|
|
79
|
+
['swe-1-7-lightning', {
|
|
80
|
+
contextWindow: 256_000,
|
|
81
|
+
maxTokens: 128_000,
|
|
82
|
+
reasoning: true,
|
|
83
|
+
input: ['text', 'image'],
|
|
84
|
+
cost: { input: 2.50, output: 12.50, cacheRead: 0.25, cacheWrite: 3.13 },
|
|
85
|
+
}],
|
|
86
|
+
['gpt-5-6-sol', {
|
|
87
|
+
contextWindow: 1_050_000,
|
|
88
|
+
maxTokens: 128_000,
|
|
89
|
+
reasoning: true,
|
|
90
|
+
input: ['text', 'image'],
|
|
91
|
+
cost: { input: 5.00, output: 30.00, cacheRead: 0.50, cacheWrite: 6.25 },
|
|
92
|
+
}],
|
|
93
|
+
['gpt-5-6-luna', {
|
|
94
|
+
contextWindow: 1_050_000,
|
|
95
|
+
maxTokens: 128_000,
|
|
96
|
+
reasoning: true,
|
|
97
|
+
input: ['text', 'image'],
|
|
98
|
+
cost: { input: 1.00, output: 6.00, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
99
|
+
}],
|
|
100
|
+
['gpt-5-6-terra', {
|
|
101
|
+
contextWindow: 1_050_000,
|
|
102
|
+
maxTokens: 128_000,
|
|
103
|
+
reasoning: true,
|
|
104
|
+
input: ['text', 'image'],
|
|
105
|
+
cost: { input: 2.50, output: 15.00, cacheRead: 0.25, cacheWrite: 3.13 },
|
|
106
|
+
}],
|
|
107
|
+
['claude-opus-4-8', {
|
|
108
|
+
contextWindow: 200_000,
|
|
109
|
+
maxTokens: 128_000,
|
|
110
|
+
reasoning: true,
|
|
111
|
+
input: ['text', 'image'],
|
|
112
|
+
cost: { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },
|
|
113
|
+
}],
|
|
114
|
+
['claude-fable-5', {
|
|
115
|
+
contextWindow: 1_000_000,
|
|
116
|
+
maxTokens: 128_000,
|
|
117
|
+
reasoning: true,
|
|
118
|
+
input: ['text', 'image'],
|
|
119
|
+
cost: { input: 10.00, output: 50.00, cacheRead: 1.00, cacheWrite: 12.50 },
|
|
120
|
+
}],
|
|
121
|
+
['claude-sonnet-5', {
|
|
122
|
+
contextWindow: 200_000,
|
|
123
|
+
maxTokens: 64_000,
|
|
124
|
+
reasoning: true,
|
|
125
|
+
input: ['text', 'image'],
|
|
126
|
+
cost: { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
127
|
+
}],
|
|
128
|
+
['glm-5-2', {
|
|
129
|
+
contextWindow: 1_000_000,
|
|
130
|
+
maxTokens: 131_000,
|
|
131
|
+
reasoning: true,
|
|
132
|
+
input: ['text', 'image'],
|
|
133
|
+
cost: { input: 0.70, output: 2.20, cacheRead: 0.26, cacheWrite: 0.88 },
|
|
134
|
+
}],
|
|
135
|
+
['kimi-k2-7', {
|
|
136
|
+
contextWindow: 256_000,
|
|
137
|
+
maxTokens: 256_000,
|
|
138
|
+
reasoning: true,
|
|
139
|
+
input: ['text', 'image'],
|
|
140
|
+
cost: { input: 0.95, output: 4.00, cacheRead: 0.19, cacheWrite: 1.19 },
|
|
141
|
+
}],
|
|
142
|
+
['grok-4-5', {
|
|
143
|
+
contextWindow: 500_000,
|
|
144
|
+
maxTokens: 128_000,
|
|
145
|
+
reasoning: true,
|
|
146
|
+
input: ['text', 'image'],
|
|
147
|
+
cost: { input: 2.00, output: 6.00, cacheRead: 0.50, cacheWrite: 2.50 },
|
|
148
|
+
}],
|
|
88
149
|
]);
|
|
89
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Find the best-matching metadata for a UID by longest-prefix match
|
|
153
|
+
* against {@link MODEL_META}. This lets `swe-1-7-lightning` match the
|
|
154
|
+
* `swe-1-7-lightning` entry (specific) and `gpt-5-6-sol-low` match the
|
|
155
|
+
* `gpt-5-6-sol` entry (parent family).
|
|
156
|
+
*/
|
|
157
|
+
function findMeta(uid: string): ModelMeta | undefined {
|
|
158
|
+
let best: { key: string; meta: ModelMeta } | null = null;
|
|
159
|
+
for (const [key, meta] of MODEL_META) {
|
|
160
|
+
if (uid === key || uid.startsWith(key + '-') || uid.startsWith(key + '_')) {
|
|
161
|
+
if (!best || key.length > best.key.length) {
|
|
162
|
+
best = { key, meta };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return best?.meta;
|
|
167
|
+
}
|
|
168
|
+
|
|
90
169
|
/** Conservative defaults for catalog UIDs not in the override table. */
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
170
|
+
const DEFAULT_META: ModelMeta = {
|
|
171
|
+
contextWindow: 256_000,
|
|
172
|
+
maxTokens: 128_000,
|
|
173
|
+
reasoning: true,
|
|
174
|
+
input: ['text', 'image'],
|
|
175
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
function makeModel(id: string, name: string, meta: ModelMeta): ProviderModelConfig {
|
|
179
|
+
return {
|
|
180
|
+
id,
|
|
181
|
+
name,
|
|
182
|
+
reasoning: meta.reasoning,
|
|
183
|
+
input: meta.input,
|
|
184
|
+
cost: meta.cost,
|
|
185
|
+
contextWindow: meta.contextWindow,
|
|
186
|
+
maxTokens: meta.maxTokens,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
95
189
|
|
|
96
190
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
191
|
+
* Static fallback list — 11 models with real pricing.
|
|
192
|
+
* Shown before login or when the live catalog fetch fails.
|
|
193
|
+
*/
|
|
194
|
+
export const FALLBACK_MODELS: ProviderModelConfig[] = [
|
|
195
|
+
makeModel('swe-1-7', 'SWE-1.7', MODEL_META.get('swe-1-7')!),
|
|
196
|
+
makeModel('swe-1-7-lightning', 'SWE-1.7 Lightning', MODEL_META.get('swe-1-7-lightning')!),
|
|
197
|
+
makeModel('gpt-5-6-sol', 'GPT-5.6 Sol', MODEL_META.get('gpt-5-6-sol')!),
|
|
198
|
+
makeModel('gpt-5-6-luna', 'GPT-5.6 Luna', MODEL_META.get('gpt-5-6-luna')!),
|
|
199
|
+
makeModel('gpt-5-6-terra', 'GPT-5.6 Terra', MODEL_META.get('gpt-5-6-terra')!),
|
|
200
|
+
makeModel('claude-opus-4-8', 'Claude Opus 4.8', MODEL_META.get('claude-opus-4-8')!),
|
|
201
|
+
makeModel('claude-fable-5', 'Claude Fable 5', MODEL_META.get('claude-fable-5')!),
|
|
202
|
+
makeModel('claude-sonnet-5', 'Claude Sonnet 5', MODEL_META.get('claude-sonnet-5')!),
|
|
203
|
+
makeModel('glm-5-2', 'GLM-5.2', MODEL_META.get('glm-5-2')!),
|
|
204
|
+
makeModel('kimi-k2-7', 'Kimi K2.7', MODEL_META.get('kimi-k2-7')!),
|
|
205
|
+
makeModel('grok-4-5', 'Grok 4.5', MODEL_META.get('grok-4-5')!),
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Build the model list from a live catalog response.
|
|
108
210
|
*
|
|
109
|
-
*
|
|
211
|
+
* Filters to the 11 wanted families via {@link WANTED_PREFIXES}, skips
|
|
212
|
+
* disabled entries, and stamps each with pricing/metadata from
|
|
213
|
+
* {@link MODEL_META}. Falls back to {@link FALLBACK_MODELS} when the
|
|
214
|
+
* catalog is null, empty, or contains none of our wanted models.
|
|
110
215
|
*/
|
|
111
|
-
export
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const catalog = await getCachedCatalog(apiKey, host).catch(() => null);
|
|
216
|
+
export function buildLiveModels(catalog: CacheEntry | null): ProviderModelConfig[] {
|
|
217
|
+
if (!catalog || catalog.byUid.size === 0) {
|
|
218
|
+
return FALLBACK_MODELS;
|
|
219
|
+
}
|
|
116
220
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
221
|
+
const models: ProviderModelConfig[] = [];
|
|
222
|
+
for (const entry of catalog.byUid.values()) {
|
|
223
|
+
if (entry.disabled) continue;
|
|
224
|
+
if (!matchesWantedPrefix(entry.modelUid)) continue;
|
|
225
|
+
const meta = findMeta(entry.modelUid) ?? DEFAULT_META;
|
|
226
|
+
models.push(makeModel(entry.modelUid, entry.label || entry.modelUid, meta));
|
|
227
|
+
}
|
|
120
228
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
id: entry.modelUid,
|
|
127
|
-
name: entry.label || entry.modelUid,
|
|
128
|
-
reasoning: override?.reasoning ?? DEFAULT_REASONING,
|
|
129
|
-
input: override?.input ?? DEFAULT_INPUT,
|
|
130
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
131
|
-
contextWindow: override?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
132
|
-
maxTokens: override?.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
133
|
-
};
|
|
134
|
-
});
|
|
229
|
+
if (models.length === 0) {
|
|
230
|
+
return FALLBACK_MODELS;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return models;
|
|
135
234
|
}
|
|
235
|
+
|
|
236
|
+
export { DEFAULT_HOST };
|