@link-assistant/agent 0.0.9 → 0.0.12
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/EXAMPLES.md +36 -0
- package/MODELS.md +72 -24
- package/README.md +59 -2
- package/TOOLS.md +20 -0
- package/package.json +35 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +469 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +144 -119
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +39 -24
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +346 -199
- package/src/json-standard/index.ts +67 -51
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/provider/provider.ts
CHANGED
|
@@ -1,200 +1,272 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import path from
|
|
3
|
-
import { Config } from
|
|
4
|
-
import { mergeDeep, sortBy } from
|
|
5
|
-
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from
|
|
6
|
-
import { Log } from
|
|
7
|
-
import { BunProc } from
|
|
8
|
-
import { ModelsDev } from
|
|
9
|
-
import { NamedError } from
|
|
10
|
-
import { Auth } from
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Config } from '../config/config';
|
|
4
|
+
import { mergeDeep, sortBy } from 'remeda';
|
|
5
|
+
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from 'ai';
|
|
6
|
+
import { Log } from '../util/log';
|
|
7
|
+
import { BunProc } from '../bun';
|
|
8
|
+
import { ModelsDev } from './models';
|
|
9
|
+
import { NamedError } from '../util/error';
|
|
10
|
+
import { Auth } from '../auth';
|
|
11
|
+
import { ClaudeOAuth } from '../auth/claude-oauth';
|
|
12
|
+
import { AuthPlugins } from '../auth/plugins';
|
|
13
|
+
import { Instance } from '../project/instance';
|
|
14
|
+
import { Global } from '../global';
|
|
15
|
+
import { Flag } from '../flag/flag';
|
|
16
|
+
import { iife } from '../util/iife';
|
|
15
17
|
|
|
16
18
|
export namespace Provider {
|
|
17
|
-
const log = Log.create({ service:
|
|
19
|
+
const log = Log.create({ service: 'provider' });
|
|
18
20
|
|
|
19
21
|
type CustomLoader = (provider: ModelsDev.Provider) => Promise<{
|
|
20
|
-
autoload: boolean
|
|
21
|
-
getModel?: (
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
autoload: boolean;
|
|
23
|
+
getModel?: (
|
|
24
|
+
sdk: any,
|
|
25
|
+
modelID: string,
|
|
26
|
+
options?: Record<string, any>
|
|
27
|
+
) => Promise<any>;
|
|
28
|
+
options?: Record<string, any>;
|
|
29
|
+
}>;
|
|
24
30
|
|
|
25
|
-
type Source =
|
|
31
|
+
type Source = 'env' | 'config' | 'custom' | 'api';
|
|
26
32
|
|
|
27
33
|
const CUSTOM_LOADERS: Record<string, CustomLoader> = {
|
|
28
|
-
async anthropic() {
|
|
34
|
+
async anthropic(input) {
|
|
35
|
+
// Check if OAuth credentials are available via the auth plugin
|
|
36
|
+
const auth = await Auth.get('anthropic');
|
|
37
|
+
if (auth?.type === 'oauth') {
|
|
38
|
+
log.info('using anthropic oauth credentials');
|
|
39
|
+
const loaderFn = await AuthPlugins.getLoader('anthropic');
|
|
40
|
+
if (loaderFn) {
|
|
41
|
+
const result = await loaderFn(() => Auth.get('anthropic'), input);
|
|
42
|
+
if (result.fetch) {
|
|
43
|
+
return {
|
|
44
|
+
autoload: true,
|
|
45
|
+
options: {
|
|
46
|
+
apiKey: result.apiKey || '',
|
|
47
|
+
fetch: result.fetch,
|
|
48
|
+
headers: {
|
|
49
|
+
'anthropic-beta':
|
|
50
|
+
'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Default: API key auth
|
|
29
58
|
return {
|
|
30
59
|
autoload: false,
|
|
31
60
|
options: {
|
|
32
61
|
headers: {
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
'anthropic-beta':
|
|
63
|
+
'claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14',
|
|
35
64
|
},
|
|
36
65
|
},
|
|
37
|
-
}
|
|
66
|
+
};
|
|
38
67
|
},
|
|
39
68
|
async opencode(input) {
|
|
40
69
|
const hasKey = await (async () => {
|
|
41
|
-
if (input.env.some((item) => process.env[item])) return true
|
|
42
|
-
if (await Auth.get(input.id)) return true
|
|
43
|
-
return false
|
|
44
|
-
})()
|
|
70
|
+
if (input.env.some((item) => process.env[item])) return true;
|
|
71
|
+
if (await Auth.get(input.id)) return true;
|
|
72
|
+
return false;
|
|
73
|
+
})();
|
|
45
74
|
|
|
46
75
|
if (!hasKey) {
|
|
47
76
|
for (const [key, value] of Object.entries(input.models)) {
|
|
48
|
-
if (value.cost.input === 0) continue
|
|
49
|
-
delete input.models[key]
|
|
77
|
+
if (value.cost.input === 0) continue;
|
|
78
|
+
delete input.models[key];
|
|
50
79
|
}
|
|
51
80
|
}
|
|
52
81
|
|
|
53
82
|
return {
|
|
54
83
|
autoload: Object.keys(input.models).length > 0,
|
|
55
|
-
options: hasKey ? {} : { apiKey:
|
|
56
|
-
}
|
|
84
|
+
options: hasKey ? {} : { apiKey: 'public' },
|
|
85
|
+
};
|
|
57
86
|
},
|
|
58
87
|
openai: async () => {
|
|
59
88
|
return {
|
|
60
89
|
autoload: false,
|
|
61
|
-
async getModel(
|
|
62
|
-
|
|
90
|
+
async getModel(
|
|
91
|
+
sdk: any,
|
|
92
|
+
modelID: string,
|
|
93
|
+
_options?: Record<string, any>
|
|
94
|
+
) {
|
|
95
|
+
return sdk.responses(modelID);
|
|
63
96
|
},
|
|
64
97
|
options: {},
|
|
65
|
-
}
|
|
98
|
+
};
|
|
66
99
|
},
|
|
67
100
|
azure: async () => {
|
|
68
101
|
return {
|
|
69
102
|
autoload: false,
|
|
70
|
-
async getModel(
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
async getModel(
|
|
104
|
+
sdk: any,
|
|
105
|
+
modelID: string,
|
|
106
|
+
options?: Record<string, any>
|
|
107
|
+
) {
|
|
108
|
+
if (options?.['useCompletionUrls']) {
|
|
109
|
+
return sdk.chat(modelID);
|
|
73
110
|
} else {
|
|
74
|
-
return sdk.responses(modelID)
|
|
111
|
+
return sdk.responses(modelID);
|
|
75
112
|
}
|
|
76
113
|
},
|
|
77
114
|
options: {},
|
|
78
|
-
}
|
|
115
|
+
};
|
|
79
116
|
},
|
|
80
|
-
|
|
81
|
-
const resourceName =
|
|
117
|
+
'azure-cognitive-services': async () => {
|
|
118
|
+
const resourceName =
|
|
119
|
+
process.env['AZURE_COGNITIVE_SERVICES_RESOURCE_NAME'];
|
|
82
120
|
return {
|
|
83
121
|
autoload: false,
|
|
84
|
-
async getModel(
|
|
85
|
-
|
|
86
|
-
|
|
122
|
+
async getModel(
|
|
123
|
+
sdk: any,
|
|
124
|
+
modelID: string,
|
|
125
|
+
options?: Record<string, any>
|
|
126
|
+
) {
|
|
127
|
+
if (options?.['useCompletionUrls']) {
|
|
128
|
+
return sdk.chat(modelID);
|
|
87
129
|
} else {
|
|
88
|
-
return sdk.responses(modelID)
|
|
130
|
+
return sdk.responses(modelID);
|
|
89
131
|
}
|
|
90
132
|
},
|
|
91
133
|
options: {
|
|
92
|
-
baseURL: resourceName
|
|
134
|
+
baseURL: resourceName
|
|
135
|
+
? `https://${resourceName}.cognitiveservices.azure.com/openai`
|
|
136
|
+
: undefined,
|
|
93
137
|
},
|
|
94
|
-
}
|
|
138
|
+
};
|
|
95
139
|
},
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
140
|
+
'amazon-bedrock': async () => {
|
|
141
|
+
if (
|
|
142
|
+
!process.env['AWS_PROFILE'] &&
|
|
143
|
+
!process.env['AWS_ACCESS_KEY_ID'] &&
|
|
144
|
+
!process.env['AWS_BEARER_TOKEN_BEDROCK']
|
|
145
|
+
)
|
|
146
|
+
return { autoload: false };
|
|
99
147
|
|
|
100
|
-
const region = process.env[
|
|
148
|
+
const region = process.env['AWS_REGION'] ?? 'us-east-1';
|
|
101
149
|
|
|
102
|
-
const { fromNodeProviderChain } = await import(
|
|
150
|
+
const { fromNodeProviderChain } = await import(
|
|
151
|
+
await BunProc.install('@aws-sdk/credential-providers')
|
|
152
|
+
);
|
|
103
153
|
return {
|
|
104
154
|
autoload: true,
|
|
105
155
|
options: {
|
|
106
156
|
region,
|
|
107
157
|
credentialProvider: fromNodeProviderChain(),
|
|
108
158
|
},
|
|
109
|
-
async getModel(
|
|
110
|
-
|
|
159
|
+
async getModel(
|
|
160
|
+
sdk: any,
|
|
161
|
+
modelID: string,
|
|
162
|
+
_options?: Record<string, any>
|
|
163
|
+
) {
|
|
164
|
+
let regionPrefix = region.split('-')[0];
|
|
111
165
|
|
|
112
166
|
switch (regionPrefix) {
|
|
113
|
-
case
|
|
167
|
+
case 'us': {
|
|
114
168
|
const modelRequiresPrefix = [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
].some((m) => modelID.includes(m))
|
|
122
|
-
const isGovCloud = region.startsWith(
|
|
169
|
+
'nova-micro',
|
|
170
|
+
'nova-lite',
|
|
171
|
+
'nova-pro',
|
|
172
|
+
'nova-premier',
|
|
173
|
+
'claude',
|
|
174
|
+
'deepseek',
|
|
175
|
+
].some((m) => modelID.includes(m));
|
|
176
|
+
const isGovCloud = region.startsWith('us-gov');
|
|
123
177
|
if (modelRequiresPrefix && !isGovCloud) {
|
|
124
|
-
modelID = `${regionPrefix}.${modelID}
|
|
178
|
+
modelID = `${regionPrefix}.${modelID}`;
|
|
125
179
|
}
|
|
126
|
-
break
|
|
180
|
+
break;
|
|
127
181
|
}
|
|
128
|
-
case
|
|
182
|
+
case 'eu': {
|
|
129
183
|
const regionRequiresPrefix = [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
].some((r) => region.includes(r))
|
|
138
|
-
const modelRequiresPrefix = [
|
|
139
|
-
|
|
140
|
-
|
|
184
|
+
'eu-west-1',
|
|
185
|
+
'eu-west-2',
|
|
186
|
+
'eu-west-3',
|
|
187
|
+
'eu-north-1',
|
|
188
|
+
'eu-central-1',
|
|
189
|
+
'eu-south-1',
|
|
190
|
+
'eu-south-2',
|
|
191
|
+
].some((r) => region.includes(r));
|
|
192
|
+
const modelRequiresPrefix = [
|
|
193
|
+
'claude',
|
|
194
|
+
'nova-lite',
|
|
195
|
+
'nova-micro',
|
|
196
|
+
'llama3',
|
|
197
|
+
'pixtral',
|
|
198
|
+
].some((m) => modelID.includes(m));
|
|
141
199
|
if (regionRequiresPrefix && modelRequiresPrefix) {
|
|
142
|
-
modelID = `${regionPrefix}.${modelID}
|
|
200
|
+
modelID = `${regionPrefix}.${modelID}`;
|
|
143
201
|
}
|
|
144
|
-
break
|
|
202
|
+
break;
|
|
145
203
|
}
|
|
146
|
-
case
|
|
147
|
-
const isAustraliaRegion = [
|
|
204
|
+
case 'ap': {
|
|
205
|
+
const isAustraliaRegion = [
|
|
206
|
+
'ap-southeast-2',
|
|
207
|
+
'ap-southeast-4',
|
|
208
|
+
].includes(region);
|
|
148
209
|
if (
|
|
149
210
|
isAustraliaRegion &&
|
|
150
|
-
[
|
|
211
|
+
['anthropic.claude-sonnet-4-5', 'anthropic.claude-haiku'].some(
|
|
212
|
+
(m) => modelID.includes(m)
|
|
213
|
+
)
|
|
151
214
|
) {
|
|
152
|
-
regionPrefix =
|
|
153
|
-
modelID = `${regionPrefix}.${modelID}
|
|
215
|
+
regionPrefix = 'au';
|
|
216
|
+
modelID = `${regionPrefix}.${modelID}`;
|
|
154
217
|
} else {
|
|
155
|
-
const modelRequiresPrefix = [
|
|
156
|
-
|
|
157
|
-
|
|
218
|
+
const modelRequiresPrefix = [
|
|
219
|
+
'claude',
|
|
220
|
+
'nova-lite',
|
|
221
|
+
'nova-micro',
|
|
222
|
+
'nova-pro',
|
|
223
|
+
].some((m) => modelID.includes(m));
|
|
158
224
|
if (modelRequiresPrefix) {
|
|
159
|
-
regionPrefix =
|
|
160
|
-
modelID = `${regionPrefix}.${modelID}
|
|
225
|
+
regionPrefix = 'apac';
|
|
226
|
+
modelID = `${regionPrefix}.${modelID}`;
|
|
161
227
|
}
|
|
162
228
|
}
|
|
163
|
-
break
|
|
229
|
+
break;
|
|
164
230
|
}
|
|
165
231
|
}
|
|
166
232
|
|
|
167
|
-
return sdk.languageModel(modelID)
|
|
233
|
+
return sdk.languageModel(modelID);
|
|
168
234
|
},
|
|
169
|
-
}
|
|
235
|
+
};
|
|
170
236
|
},
|
|
171
237
|
openrouter: async () => {
|
|
172
238
|
return {
|
|
173
239
|
autoload: false,
|
|
174
240
|
options: {
|
|
175
241
|
headers: {
|
|
176
|
-
|
|
177
|
-
|
|
242
|
+
'HTTP-Referer': 'https://opencode.ai/',
|
|
243
|
+
'X-Title': 'opencode',
|
|
178
244
|
},
|
|
179
245
|
},
|
|
180
|
-
}
|
|
246
|
+
};
|
|
181
247
|
},
|
|
182
248
|
vercel: async () => {
|
|
183
249
|
return {
|
|
184
250
|
autoload: false,
|
|
185
251
|
options: {
|
|
186
252
|
headers: {
|
|
187
|
-
|
|
188
|
-
|
|
253
|
+
'http-referer': 'https://opencode.ai/',
|
|
254
|
+
'x-title': 'opencode',
|
|
189
255
|
},
|
|
190
256
|
},
|
|
191
|
-
}
|
|
257
|
+
};
|
|
192
258
|
},
|
|
193
|
-
|
|
194
|
-
const project =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
259
|
+
'google-vertex': async () => {
|
|
260
|
+
const project =
|
|
261
|
+
process.env['GOOGLE_CLOUD_PROJECT'] ??
|
|
262
|
+
process.env['GCP_PROJECT'] ??
|
|
263
|
+
process.env['GCLOUD_PROJECT'];
|
|
264
|
+
const location =
|
|
265
|
+
process.env['GOOGLE_CLOUD_LOCATION'] ??
|
|
266
|
+
process.env['VERTEX_LOCATION'] ??
|
|
267
|
+
'us-east5';
|
|
268
|
+
const autoload = Boolean(project);
|
|
269
|
+
if (!autoload) return { autoload: false };
|
|
198
270
|
return {
|
|
199
271
|
autoload: true,
|
|
200
272
|
options: {
|
|
@@ -202,16 +274,22 @@ export namespace Provider {
|
|
|
202
274
|
location,
|
|
203
275
|
},
|
|
204
276
|
async getModel(sdk: any, modelID: string) {
|
|
205
|
-
const id = String(modelID).trim()
|
|
206
|
-
return sdk.languageModel(id)
|
|
277
|
+
const id = String(modelID).trim();
|
|
278
|
+
return sdk.languageModel(id);
|
|
207
279
|
},
|
|
208
|
-
}
|
|
280
|
+
};
|
|
209
281
|
},
|
|
210
|
-
|
|
211
|
-
const project =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
282
|
+
'google-vertex-anthropic': async () => {
|
|
283
|
+
const project =
|
|
284
|
+
process.env['GOOGLE_CLOUD_PROJECT'] ??
|
|
285
|
+
process.env['GCP_PROJECT'] ??
|
|
286
|
+
process.env['GCLOUD_PROJECT'];
|
|
287
|
+
const location =
|
|
288
|
+
process.env['GOOGLE_CLOUD_LOCATION'] ??
|
|
289
|
+
process.env['VERTEX_LOCATION'] ??
|
|
290
|
+
'global';
|
|
291
|
+
const autoload = Boolean(project);
|
|
292
|
+
if (!autoload) return { autoload: false };
|
|
215
293
|
return {
|
|
216
294
|
autoload: true,
|
|
217
295
|
options: {
|
|
@@ -219,93 +297,226 @@ export namespace Provider {
|
|
|
219
297
|
location,
|
|
220
298
|
},
|
|
221
299
|
async getModel(sdk: any, modelID: string) {
|
|
222
|
-
const id = String(modelID).trim()
|
|
223
|
-
return sdk.languageModel(id)
|
|
300
|
+
const id = String(modelID).trim();
|
|
301
|
+
return sdk.languageModel(id);
|
|
224
302
|
},
|
|
225
|
-
}
|
|
303
|
+
};
|
|
226
304
|
},
|
|
227
305
|
zenmux: async () => {
|
|
228
306
|
return {
|
|
229
307
|
autoload: false,
|
|
230
308
|
options: {
|
|
231
309
|
headers: {
|
|
232
|
-
|
|
233
|
-
|
|
310
|
+
'HTTP-Referer': 'https://opencode.ai/',
|
|
311
|
+
'X-Title': 'opencode',
|
|
234
312
|
},
|
|
235
313
|
},
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
groq: async () => {
|
|
317
|
+
return {
|
|
318
|
+
autoload: false,
|
|
319
|
+
options: {},
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
/**
|
|
323
|
+
* GitHub Copilot OAuth provider
|
|
324
|
+
* Uses OAuth credentials from agent auth login
|
|
325
|
+
*/
|
|
326
|
+
'github-copilot': async (input) => {
|
|
327
|
+
const auth = await Auth.get('github-copilot');
|
|
328
|
+
if (auth?.type === 'oauth') {
|
|
329
|
+
log.info('using github copilot oauth credentials');
|
|
330
|
+
const loaderFn = await AuthPlugins.getLoader('github-copilot');
|
|
331
|
+
if (loaderFn) {
|
|
332
|
+
const result = await loaderFn(
|
|
333
|
+
() => Auth.get('github-copilot'),
|
|
334
|
+
input
|
|
335
|
+
);
|
|
336
|
+
if (result.fetch) {
|
|
337
|
+
return {
|
|
338
|
+
autoload: true,
|
|
339
|
+
options: {
|
|
340
|
+
apiKey: result.apiKey || '',
|
|
341
|
+
baseURL: result.baseURL,
|
|
342
|
+
fetch: result.fetch,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
236
347
|
}
|
|
348
|
+
return { autoload: false };
|
|
237
349
|
},
|
|
238
|
-
|
|
350
|
+
/**
|
|
351
|
+
* GitHub Copilot Enterprise OAuth provider
|
|
352
|
+
* Uses OAuth credentials from agent auth login with enterprise URL
|
|
353
|
+
*/
|
|
354
|
+
'github-copilot-enterprise': async (input) => {
|
|
355
|
+
const auth = await Auth.get('github-copilot-enterprise');
|
|
356
|
+
if (auth?.type === 'oauth') {
|
|
357
|
+
log.info('using github copilot enterprise oauth credentials');
|
|
358
|
+
const loaderFn = await AuthPlugins.getLoader('github-copilot');
|
|
359
|
+
if (loaderFn) {
|
|
360
|
+
const result = await loaderFn(
|
|
361
|
+
() => Auth.get('github-copilot-enterprise'),
|
|
362
|
+
input
|
|
363
|
+
);
|
|
364
|
+
if (result.fetch) {
|
|
365
|
+
return {
|
|
366
|
+
autoload: true,
|
|
367
|
+
options: {
|
|
368
|
+
apiKey: result.apiKey || '',
|
|
369
|
+
baseURL: result.baseURL,
|
|
370
|
+
fetch: result.fetch,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { autoload: false };
|
|
377
|
+
},
|
|
378
|
+
/**
|
|
379
|
+
* Claude OAuth provider - uses Claude OAuth credentials to access
|
|
380
|
+
* Anthropic models via the Claude API.
|
|
381
|
+
*
|
|
382
|
+
* This provider supports two methods:
|
|
383
|
+
* 1. Environment variable: CLAUDE_CODE_OAUTH_TOKEN
|
|
384
|
+
* 2. Credentials file: ~/.claude/.credentials.json (from Claude Code CLI or our auth command)
|
|
385
|
+
*
|
|
386
|
+
* OAuth tokens use Bearer authentication (Authorization header)
|
|
387
|
+
* instead of x-api-key authentication used by standard API keys.
|
|
388
|
+
*
|
|
389
|
+
* To authenticate, run: agent auth claude
|
|
390
|
+
*/
|
|
391
|
+
'claude-oauth': async (input) => {
|
|
392
|
+
// Check for OAuth token from environment variable first
|
|
393
|
+
let oauthToken = process.env['CLAUDE_CODE_OAUTH_TOKEN'];
|
|
394
|
+
let tokenSource = 'environment';
|
|
395
|
+
|
|
396
|
+
if (!oauthToken) {
|
|
397
|
+
// Check for OAuth credentials from credentials file
|
|
398
|
+
const claudeCreds = await ClaudeOAuth.getCredentials();
|
|
399
|
+
if (claudeCreds) {
|
|
400
|
+
oauthToken = claudeCreds.accessToken;
|
|
401
|
+
tokenSource = `credentials file (${claudeCreds.subscriptionType ?? 'unknown'})`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!oauthToken) {
|
|
406
|
+
return { autoload: false };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
log.info('using claude oauth credentials', { source: tokenSource });
|
|
410
|
+
|
|
411
|
+
// Create authenticated fetch with Bearer token and OAuth beta header
|
|
412
|
+
const customFetch = ClaudeOAuth.createAuthenticatedFetch(oauthToken);
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
autoload: true,
|
|
416
|
+
options: {
|
|
417
|
+
// Use a placeholder key to satisfy the SDK's API key requirement
|
|
418
|
+
// The actual authentication is done via Bearer token in customFetch
|
|
419
|
+
apiKey: 'oauth-token-placeholder',
|
|
420
|
+
fetch: customFetch,
|
|
421
|
+
headers: {
|
|
422
|
+
'anthropic-beta':
|
|
423
|
+
'oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14',
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
},
|
|
428
|
+
};
|
|
239
429
|
|
|
240
430
|
const state = Instance.state(async () => {
|
|
241
|
-
using _ = log.time(
|
|
242
|
-
const config = await Config.get()
|
|
243
|
-
const database = await ModelsDev.get()
|
|
431
|
+
using _ = log.time('state');
|
|
432
|
+
const config = await Config.get();
|
|
433
|
+
const database = await ModelsDev.get();
|
|
244
434
|
|
|
245
435
|
const providers: {
|
|
246
436
|
[providerID: string]: {
|
|
247
|
-
source: Source
|
|
248
|
-
info: ModelsDev.Provider
|
|
249
|
-
getModel?: (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
437
|
+
source: Source;
|
|
438
|
+
info: ModelsDev.Provider;
|
|
439
|
+
getModel?: (
|
|
440
|
+
sdk: any,
|
|
441
|
+
modelID: string,
|
|
442
|
+
options?: Record<string, any>
|
|
443
|
+
) => Promise<any>;
|
|
444
|
+
options: Record<string, any>;
|
|
445
|
+
};
|
|
446
|
+
} = {};
|
|
253
447
|
const models = new Map<
|
|
254
448
|
string,
|
|
255
449
|
{
|
|
256
|
-
providerID: string
|
|
257
|
-
modelID: string
|
|
258
|
-
info: ModelsDev.Model
|
|
259
|
-
language: LanguageModel
|
|
260
|
-
npm?: string
|
|
450
|
+
providerID: string;
|
|
451
|
+
modelID: string;
|
|
452
|
+
info: ModelsDev.Model;
|
|
453
|
+
language: LanguageModel;
|
|
454
|
+
npm?: string;
|
|
261
455
|
}
|
|
262
|
-
>()
|
|
263
|
-
const sdk = new Map<number, SDK>()
|
|
456
|
+
>();
|
|
457
|
+
const sdk = new Map<number, SDK>();
|
|
264
458
|
// Maps `${provider}/${key}` to the provider’s actual model ID for custom aliases.
|
|
265
|
-
const realIdByKey = new Map<string, string>()
|
|
459
|
+
const realIdByKey = new Map<string, string>();
|
|
266
460
|
|
|
267
|
-
log.info(
|
|
461
|
+
log.info('init');
|
|
268
462
|
|
|
269
463
|
function mergeProvider(
|
|
270
464
|
id: string,
|
|
271
465
|
options: Record<string, any>,
|
|
272
466
|
source: Source,
|
|
273
|
-
getModel?: (
|
|
467
|
+
getModel?: (
|
|
468
|
+
sdk: any,
|
|
469
|
+
modelID: string,
|
|
470
|
+
options?: Record<string, any>
|
|
471
|
+
) => Promise<any>
|
|
274
472
|
) {
|
|
275
|
-
const provider = providers[id]
|
|
473
|
+
const provider = providers[id];
|
|
276
474
|
if (!provider) {
|
|
277
|
-
const info = database[id]
|
|
278
|
-
if (!info) return
|
|
279
|
-
if (info.api && !options[
|
|
475
|
+
const info = database[id];
|
|
476
|
+
if (!info) return;
|
|
477
|
+
if (info.api && !options['baseURL']) options['baseURL'] = info.api;
|
|
280
478
|
providers[id] = {
|
|
281
479
|
source,
|
|
282
480
|
info,
|
|
283
481
|
options,
|
|
284
482
|
getModel,
|
|
285
|
-
}
|
|
286
|
-
return
|
|
483
|
+
};
|
|
484
|
+
return;
|
|
287
485
|
}
|
|
288
|
-
provider.options = mergeDeep(provider.options, options)
|
|
289
|
-
provider.source = source
|
|
290
|
-
provider.getModel = getModel ?? provider.getModel
|
|
486
|
+
provider.options = mergeDeep(provider.options, options);
|
|
487
|
+
provider.source = source;
|
|
488
|
+
provider.getModel = getModel ?? provider.getModel;
|
|
291
489
|
}
|
|
292
490
|
|
|
293
|
-
const configProviders = Object.entries(config.provider ?? {})
|
|
491
|
+
const configProviders = Object.entries(config.provider ?? {});
|
|
294
492
|
|
|
295
493
|
// Add GitHub Copilot Enterprise provider that inherits from GitHub Copilot
|
|
296
|
-
if (database[
|
|
297
|
-
const githubCopilot = database[
|
|
298
|
-
database[
|
|
494
|
+
if (database['github-copilot']) {
|
|
495
|
+
const githubCopilot = database['github-copilot'];
|
|
496
|
+
database['github-copilot-enterprise'] = {
|
|
299
497
|
...githubCopilot,
|
|
300
|
-
id:
|
|
301
|
-
name:
|
|
498
|
+
id: 'github-copilot-enterprise',
|
|
499
|
+
name: 'GitHub Copilot Enterprise',
|
|
302
500
|
// Enterprise uses a different API endpoint - will be set dynamically based on auth
|
|
303
501
|
api: undefined,
|
|
304
|
-
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Add Claude OAuth provider that inherits from Anthropic
|
|
506
|
+
// This allows using Claude Code CLI OAuth credentials with the Anthropic API
|
|
507
|
+
if (database['anthropic']) {
|
|
508
|
+
const anthropic = database['anthropic'];
|
|
509
|
+
database['claude-oauth'] = {
|
|
510
|
+
...anthropic,
|
|
511
|
+
id: 'claude-oauth',
|
|
512
|
+
name: 'Claude OAuth',
|
|
513
|
+
// Use CLAUDE_CODE_OAUTH_TOKEN environment variable
|
|
514
|
+
env: ['CLAUDE_CODE_OAUTH_TOKEN'],
|
|
515
|
+
};
|
|
305
516
|
}
|
|
306
517
|
|
|
307
518
|
for (const [providerID, provider] of configProviders) {
|
|
308
|
-
const existing = database[providerID]
|
|
519
|
+
const existing = database[providerID];
|
|
309
520
|
const parsed: ModelsDev.Provider = {
|
|
310
521
|
id: providerID,
|
|
311
522
|
npm: provider.npm ?? existing?.npm,
|
|
@@ -313,15 +524,15 @@ export namespace Provider {
|
|
|
313
524
|
env: provider.env ?? existing?.env ?? [],
|
|
314
525
|
api: provider.api ?? existing?.api,
|
|
315
526
|
models: existing?.models ?? {},
|
|
316
|
-
}
|
|
527
|
+
};
|
|
317
528
|
|
|
318
529
|
for (const [modelID, model] of Object.entries(provider.models ?? {})) {
|
|
319
|
-
const existing = parsed.models[model.id ?? modelID]
|
|
530
|
+
const existing = parsed.models[model.id ?? modelID];
|
|
320
531
|
const name = iife(() => {
|
|
321
|
-
if (model.name) return model.name
|
|
322
|
-
if (model.id && model.id !== modelID) return modelID
|
|
323
|
-
return existing?.name ?? modelID
|
|
324
|
-
})
|
|
532
|
+
if (model.name) return model.name;
|
|
533
|
+
if (model.id && model.id !== modelID) return modelID;
|
|
534
|
+
return existing?.name ?? modelID;
|
|
535
|
+
});
|
|
325
536
|
const parsedModel: ModelsDev.Model = {
|
|
326
537
|
id: modelID,
|
|
327
538
|
name,
|
|
@@ -355,54 +566,61 @@ export namespace Provider {
|
|
|
355
566
|
},
|
|
356
567
|
modalities: model.modalities ??
|
|
357
568
|
existing?.modalities ?? {
|
|
358
|
-
input: [
|
|
359
|
-
output: [
|
|
569
|
+
input: ['text'],
|
|
570
|
+
output: ['text'],
|
|
360
571
|
},
|
|
361
572
|
headers: model.headers,
|
|
362
573
|
provider: model.provider ?? existing?.provider,
|
|
363
|
-
}
|
|
574
|
+
};
|
|
364
575
|
if (model.id && model.id !== modelID) {
|
|
365
|
-
realIdByKey.set(`${providerID}/${modelID}`, model.id)
|
|
576
|
+
realIdByKey.set(`${providerID}/${modelID}`, model.id);
|
|
366
577
|
}
|
|
367
|
-
parsed.models[modelID] = parsedModel
|
|
578
|
+
parsed.models[modelID] = parsedModel;
|
|
368
579
|
}
|
|
369
|
-
database[providerID] = parsed
|
|
580
|
+
database[providerID] = parsed;
|
|
370
581
|
}
|
|
371
582
|
|
|
372
|
-
const disabled = await Config.get().then(
|
|
583
|
+
const disabled = await Config.get().then(
|
|
584
|
+
(cfg) => new Set(cfg.disabled_providers ?? [])
|
|
585
|
+
);
|
|
373
586
|
// load env
|
|
374
587
|
for (const [providerID, provider] of Object.entries(database)) {
|
|
375
|
-
if (disabled.has(providerID)) continue
|
|
376
|
-
const apiKey = provider.env.map((item) => process.env[item]).at(0)
|
|
377
|
-
if (!apiKey) continue
|
|
588
|
+
if (disabled.has(providerID)) continue;
|
|
589
|
+
const apiKey = provider.env.map((item) => process.env[item]).at(0);
|
|
590
|
+
if (!apiKey) continue;
|
|
378
591
|
mergeProvider(
|
|
379
592
|
providerID,
|
|
380
593
|
// only include apiKey if there's only one potential option
|
|
381
594
|
provider.env.length === 1 ? { apiKey } : {},
|
|
382
|
-
|
|
383
|
-
)
|
|
595
|
+
'env'
|
|
596
|
+
);
|
|
384
597
|
}
|
|
385
598
|
|
|
386
599
|
// load apikeys
|
|
387
600
|
for (const [providerID, provider] of Object.entries(await Auth.all())) {
|
|
388
|
-
if (disabled.has(providerID)) continue
|
|
389
|
-
if (provider.type ===
|
|
390
|
-
mergeProvider(providerID, { apiKey: provider.key },
|
|
601
|
+
if (disabled.has(providerID)) continue;
|
|
602
|
+
if (provider.type === 'api') {
|
|
603
|
+
mergeProvider(providerID, { apiKey: provider.key }, 'api');
|
|
391
604
|
}
|
|
392
605
|
}
|
|
393
606
|
|
|
394
607
|
// load custom
|
|
395
608
|
for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
|
|
396
|
-
if (disabled.has(providerID)) continue
|
|
397
|
-
const result = await fn(database[providerID])
|
|
609
|
+
if (disabled.has(providerID)) continue;
|
|
610
|
+
const result = await fn(database[providerID]);
|
|
398
611
|
if (result && (result.autoload || providers[providerID])) {
|
|
399
|
-
mergeProvider(
|
|
612
|
+
mergeProvider(
|
|
613
|
+
providerID,
|
|
614
|
+
result.options ?? {},
|
|
615
|
+
'custom',
|
|
616
|
+
result.getModel
|
|
617
|
+
);
|
|
400
618
|
}
|
|
401
619
|
}
|
|
402
620
|
|
|
403
621
|
// load config
|
|
404
622
|
for (const [providerID, provider] of configProviders) {
|
|
405
|
-
mergeProvider(providerID, provider.options ?? {},
|
|
623
|
+
mergeProvider(providerID, provider.options ?? {}, 'config');
|
|
406
624
|
}
|
|
407
625
|
|
|
408
626
|
for (const [providerID, provider] of Object.entries(providers)) {
|
|
@@ -411,22 +629,24 @@ export namespace Provider {
|
|
|
411
629
|
// Filter out blacklisted models
|
|
412
630
|
.filter(
|
|
413
631
|
([modelID]) =>
|
|
414
|
-
modelID !==
|
|
632
|
+
modelID !== 'gpt-5-chat-latest' &&
|
|
633
|
+
!(providerID === 'openrouter' && modelID === 'openai/gpt-5-chat')
|
|
415
634
|
)
|
|
416
635
|
// Filter out experimental models
|
|
417
636
|
.filter(
|
|
418
637
|
([, model]) =>
|
|
419
|
-
((!model.experimental && model.status !==
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
638
|
+
((!model.experimental && model.status !== 'alpha') ||
|
|
639
|
+
Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) &&
|
|
640
|
+
model.status !== 'deprecated'
|
|
641
|
+
)
|
|
642
|
+
);
|
|
643
|
+
provider.info.models = filteredModels;
|
|
424
644
|
|
|
425
645
|
if (Object.keys(provider.info.models).length === 0) {
|
|
426
|
-
delete providers[providerID]
|
|
427
|
-
continue
|
|
646
|
+
delete providers[providerID];
|
|
647
|
+
continue;
|
|
428
648
|
}
|
|
429
|
-
log.info(
|
|
649
|
+
log.info('found', { providerID });
|
|
430
650
|
}
|
|
431
651
|
|
|
432
652
|
return {
|
|
@@ -434,34 +654,37 @@ export namespace Provider {
|
|
|
434
654
|
providers,
|
|
435
655
|
sdk,
|
|
436
656
|
realIdByKey,
|
|
437
|
-
}
|
|
438
|
-
})
|
|
657
|
+
};
|
|
658
|
+
});
|
|
439
659
|
|
|
440
660
|
export async function list() {
|
|
441
|
-
return state().then((state) => state.providers)
|
|
661
|
+
return state().then((state) => state.providers);
|
|
442
662
|
}
|
|
443
663
|
|
|
444
664
|
async function getSDK(provider: ModelsDev.Provider, model: ModelsDev.Model) {
|
|
445
665
|
return (async () => {
|
|
446
|
-
using _ = log.time(
|
|
666
|
+
using _ = log.time('getSDK', {
|
|
447
667
|
providerID: provider.id,
|
|
448
|
-
})
|
|
449
|
-
const s = await state()
|
|
450
|
-
const pkg = model.provider?.npm ?? provider.npm ?? provider.id
|
|
451
|
-
const options = { ...s.providers[provider.id]?.options }
|
|
452
|
-
if (
|
|
453
|
-
|
|
668
|
+
});
|
|
669
|
+
const s = await state();
|
|
670
|
+
const pkg = model.provider?.npm ?? provider.npm ?? provider.id;
|
|
671
|
+
const options = { ...s.providers[provider.id]?.options };
|
|
672
|
+
if (
|
|
673
|
+
pkg.includes('@ai-sdk/openai-compatible') &&
|
|
674
|
+
options['includeUsage'] === undefined
|
|
675
|
+
) {
|
|
676
|
+
options['includeUsage'] = true;
|
|
454
677
|
}
|
|
455
|
-
const key = Bun.hash.xxHash32(JSON.stringify({ pkg, options }))
|
|
456
|
-
const existing = s.sdk.get(key)
|
|
457
|
-
if (existing) return existing
|
|
678
|
+
const key = Bun.hash.xxHash32(JSON.stringify({ pkg, options }));
|
|
679
|
+
const existing = s.sdk.get(key);
|
|
680
|
+
if (existing) return existing;
|
|
458
681
|
|
|
459
|
-
let installedPath: string
|
|
460
|
-
if (!pkg.startsWith(
|
|
461
|
-
installedPath = await BunProc.install(pkg,
|
|
682
|
+
let installedPath: string;
|
|
683
|
+
if (!pkg.startsWith('file://')) {
|
|
684
|
+
installedPath = await BunProc.install(pkg, 'latest');
|
|
462
685
|
} else {
|
|
463
|
-
log.info(
|
|
464
|
-
installedPath = pkg
|
|
686
|
+
log.info('loading local provider', { pkg });
|
|
687
|
+
installedPath = pkg;
|
|
465
688
|
}
|
|
466
689
|
|
|
467
690
|
// The `google-vertex-anthropic` provider points to the `@ai-sdk/google-vertex` package.
|
|
@@ -471,82 +694,86 @@ export namespace Provider {
|
|
|
471
694
|
// In addition, Bun's dynamic import logic does not support subpath imports,
|
|
472
695
|
// so we patch the import path to load directly from `dist`.
|
|
473
696
|
const modPath =
|
|
474
|
-
provider.id ===
|
|
475
|
-
|
|
476
|
-
|
|
697
|
+
provider.id === 'google-vertex-anthropic'
|
|
698
|
+
? `${installedPath}/dist/anthropic/index.mjs`
|
|
699
|
+
: installedPath;
|
|
700
|
+
const mod = await import(modPath);
|
|
701
|
+
if (options['timeout'] !== undefined && options['timeout'] !== null) {
|
|
477
702
|
// Preserve custom fetch if it exists, wrap it with timeout logic
|
|
478
|
-
const customFetch = options[
|
|
479
|
-
options[
|
|
480
|
-
const { signal, ...rest } = init ?? {}
|
|
703
|
+
const customFetch = options['fetch'];
|
|
704
|
+
options['fetch'] = async (input: any, init?: BunFetchRequestInit) => {
|
|
705
|
+
const { signal, ...rest } = init ?? {};
|
|
481
706
|
|
|
482
|
-
const signals: AbortSignal[] = []
|
|
483
|
-
if (signal) signals.push(signal)
|
|
484
|
-
if (options[
|
|
707
|
+
const signals: AbortSignal[] = [];
|
|
708
|
+
if (signal) signals.push(signal);
|
|
709
|
+
if (options['timeout'] !== false)
|
|
710
|
+
signals.push(AbortSignal.timeout(options['timeout']));
|
|
485
711
|
|
|
486
|
-
const combined =
|
|
712
|
+
const combined =
|
|
713
|
+
signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
487
714
|
|
|
488
|
-
const fetchFn = customFetch ?? fetch
|
|
715
|
+
const fetchFn = customFetch ?? fetch;
|
|
489
716
|
return fetchFn(input, {
|
|
490
717
|
...rest,
|
|
491
718
|
signal: combined,
|
|
492
719
|
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
|
|
493
720
|
timeout: false,
|
|
494
|
-
})
|
|
495
|
-
}
|
|
721
|
+
});
|
|
722
|
+
};
|
|
496
723
|
}
|
|
497
|
-
const fn = mod[Object.keys(mod).find((key) => key.startsWith(
|
|
724
|
+
const fn = mod[Object.keys(mod).find((key) => key.startsWith('create'))!];
|
|
498
725
|
const loaded = fn({
|
|
499
726
|
name: provider.id,
|
|
500
727
|
...options,
|
|
501
|
-
})
|
|
502
|
-
s.sdk.set(key, loaded)
|
|
503
|
-
return loaded as SDK
|
|
728
|
+
});
|
|
729
|
+
s.sdk.set(key, loaded);
|
|
730
|
+
return loaded as SDK;
|
|
504
731
|
})().catch((e) => {
|
|
505
|
-
throw new InitError({ providerID: provider.id }, { cause: e })
|
|
506
|
-
})
|
|
732
|
+
throw new InitError({ providerID: provider.id }, { cause: e });
|
|
733
|
+
});
|
|
507
734
|
}
|
|
508
735
|
|
|
509
736
|
export async function getProvider(providerID: string) {
|
|
510
|
-
return state().then((s) => s.providers[providerID])
|
|
737
|
+
return state().then((s) => s.providers[providerID]);
|
|
511
738
|
}
|
|
512
739
|
|
|
513
740
|
export async function getModel(providerID: string, modelID: string) {
|
|
514
|
-
const key = `${providerID}/${modelID}
|
|
515
|
-
const s = await state()
|
|
516
|
-
if (s.models.has(key)) return s.models.get(key)
|
|
741
|
+
const key = `${providerID}/${modelID}`;
|
|
742
|
+
const s = await state();
|
|
743
|
+
if (s.models.has(key)) return s.models.get(key)!;
|
|
517
744
|
|
|
518
|
-
log.info(
|
|
745
|
+
log.info('getModel', {
|
|
519
746
|
providerID,
|
|
520
747
|
modelID,
|
|
521
|
-
})
|
|
748
|
+
});
|
|
522
749
|
|
|
523
|
-
const provider = s.providers[providerID]
|
|
524
|
-
if (!provider) throw new ModelNotFoundError({ providerID, modelID })
|
|
525
|
-
const info = provider.info.models[modelID]
|
|
526
|
-
if (!info) throw new ModelNotFoundError({ providerID, modelID })
|
|
527
|
-
const sdk = await getSDK(provider.info, info)
|
|
750
|
+
const provider = s.providers[providerID];
|
|
751
|
+
if (!provider) throw new ModelNotFoundError({ providerID, modelID });
|
|
752
|
+
const info = provider.info.models[modelID];
|
|
753
|
+
if (!info) throw new ModelNotFoundError({ providerID, modelID });
|
|
754
|
+
const sdk = await getSDK(provider.info, info);
|
|
528
755
|
|
|
529
756
|
try {
|
|
530
|
-
const keyReal = `${providerID}/${modelID}
|
|
531
|
-
const realID = s.realIdByKey.get(keyReal) ?? info.id
|
|
757
|
+
const keyReal = `${providerID}/${modelID}`;
|
|
758
|
+
const realID = s.realIdByKey.get(keyReal) ?? info.id;
|
|
532
759
|
const language = provider.getModel
|
|
533
760
|
? await provider.getModel(sdk, realID, provider.options)
|
|
534
|
-
: sdk.languageModel(realID)
|
|
535
|
-
log.info(
|
|
761
|
+
: sdk.languageModel(realID);
|
|
762
|
+
log.info('found', { providerID, modelID });
|
|
536
763
|
s.models.set(key, {
|
|
537
764
|
providerID,
|
|
538
765
|
modelID,
|
|
539
766
|
info,
|
|
540
767
|
language,
|
|
541
768
|
npm: info.provider?.npm ?? provider.info.npm,
|
|
542
|
-
})
|
|
769
|
+
});
|
|
543
770
|
return {
|
|
544
771
|
modelID,
|
|
545
772
|
providerID,
|
|
546
773
|
info,
|
|
547
774
|
language,
|
|
548
775
|
npm: info.provider?.npm ?? provider.info.npm,
|
|
549
|
-
}
|
|
776
|
+
};
|
|
550
777
|
} catch (e) {
|
|
551
778
|
if (e instanceof NoSuchModelError)
|
|
552
779
|
throw new ModelNotFoundError(
|
|
@@ -554,83 +781,97 @@ export namespace Provider {
|
|
|
554
781
|
modelID: modelID,
|
|
555
782
|
providerID,
|
|
556
783
|
},
|
|
557
|
-
{ cause: e }
|
|
558
|
-
)
|
|
559
|
-
throw e
|
|
784
|
+
{ cause: e }
|
|
785
|
+
);
|
|
786
|
+
throw e;
|
|
560
787
|
}
|
|
561
788
|
}
|
|
562
789
|
|
|
563
790
|
export async function getSmallModel(providerID: string) {
|
|
564
|
-
const cfg = await Config.get()
|
|
791
|
+
const cfg = await Config.get();
|
|
565
792
|
|
|
566
793
|
if (cfg.small_model) {
|
|
567
|
-
const parsed = parseModel(cfg.small_model)
|
|
568
|
-
return getModel(parsed.providerID, parsed.modelID)
|
|
794
|
+
const parsed = parseModel(cfg.small_model);
|
|
795
|
+
return getModel(parsed.providerID, parsed.modelID);
|
|
569
796
|
}
|
|
570
797
|
|
|
571
|
-
const provider = await state().then((state) => state.providers[providerID])
|
|
572
|
-
if (!provider) return
|
|
573
|
-
let priority = [
|
|
798
|
+
const provider = await state().then((state) => state.providers[providerID]);
|
|
799
|
+
if (!provider) return;
|
|
800
|
+
let priority = [
|
|
801
|
+
'claude-haiku-4-5',
|
|
802
|
+
'claude-haiku-4.5',
|
|
803
|
+
'3-5-haiku',
|
|
804
|
+
'3.5-haiku',
|
|
805
|
+
'gemini-2.5-flash',
|
|
806
|
+
'gpt-5-nano',
|
|
807
|
+
];
|
|
574
808
|
// claude-haiku-4.5 is considered a premium model in github copilot, we shouldn't use premium requests for title gen
|
|
575
|
-
if (providerID ===
|
|
576
|
-
priority = priority.filter((m) => m !==
|
|
809
|
+
if (providerID === 'github-copilot') {
|
|
810
|
+
priority = priority.filter((m) => m !== 'claude-haiku-4.5');
|
|
577
811
|
}
|
|
578
|
-
if (providerID ===
|
|
579
|
-
priority = [
|
|
812
|
+
if (providerID === 'opencode' || providerID === 'local') {
|
|
813
|
+
priority = ['gpt-5-nano'];
|
|
580
814
|
}
|
|
581
815
|
for (const item of priority) {
|
|
582
816
|
for (const model of Object.keys(provider.info.models)) {
|
|
583
|
-
if (model.includes(item)) return getModel(providerID, model)
|
|
817
|
+
if (model.includes(item)) return getModel(providerID, model);
|
|
584
818
|
}
|
|
585
819
|
}
|
|
586
820
|
}
|
|
587
821
|
|
|
588
|
-
const priority = [
|
|
822
|
+
const priority = ['gpt-5', 'claude-sonnet-4', 'big-pickle', 'gemini-3-pro'];
|
|
589
823
|
export function sort(models: ModelsDev.Model[]) {
|
|
590
824
|
return sortBy(
|
|
591
825
|
models,
|
|
592
|
-
[
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
826
|
+
[
|
|
827
|
+
(model) => priority.findIndex((filter) => model.id.includes(filter)),
|
|
828
|
+
'desc',
|
|
829
|
+
],
|
|
830
|
+
[(model) => (model.id.includes('latest') ? 0 : 1), 'asc'],
|
|
831
|
+
[(model) => model.id, 'desc']
|
|
832
|
+
);
|
|
596
833
|
}
|
|
597
834
|
|
|
598
835
|
export async function defaultModel() {
|
|
599
|
-
const cfg = await Config.get()
|
|
600
|
-
if (cfg.model) return parseModel(cfg.model)
|
|
836
|
+
const cfg = await Config.get();
|
|
837
|
+
if (cfg.model) return parseModel(cfg.model);
|
|
601
838
|
|
|
602
839
|
const provider = await list()
|
|
603
840
|
.then((val) => Object.values(val))
|
|
604
|
-
.then((x) =>
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
841
|
+
.then((x) =>
|
|
842
|
+
x.find(
|
|
843
|
+
(p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)
|
|
844
|
+
)
|
|
845
|
+
);
|
|
846
|
+
if (!provider) throw new Error('no providers found');
|
|
847
|
+
const [model] = sort(Object.values(provider.info.models));
|
|
848
|
+
if (!model) throw new Error('no models found');
|
|
608
849
|
return {
|
|
609
850
|
providerID: provider.info.id,
|
|
610
851
|
modelID: model.id,
|
|
611
|
-
}
|
|
852
|
+
};
|
|
612
853
|
}
|
|
613
854
|
|
|
614
855
|
export function parseModel(model: string) {
|
|
615
|
-
const [providerID, ...rest] = model.split(
|
|
856
|
+
const [providerID, ...rest] = model.split('/');
|
|
616
857
|
return {
|
|
617
858
|
providerID: providerID,
|
|
618
|
-
modelID: rest.join(
|
|
619
|
-
}
|
|
859
|
+
modelID: rest.join('/'),
|
|
860
|
+
};
|
|
620
861
|
}
|
|
621
862
|
|
|
622
863
|
export const ModelNotFoundError = NamedError.create(
|
|
623
|
-
|
|
864
|
+
'ProviderModelNotFoundError',
|
|
624
865
|
z.object({
|
|
625
866
|
providerID: z.string(),
|
|
626
867
|
modelID: z.string(),
|
|
627
|
-
})
|
|
628
|
-
)
|
|
868
|
+
})
|
|
869
|
+
);
|
|
629
870
|
|
|
630
871
|
export const InitError = NamedError.create(
|
|
631
|
-
|
|
872
|
+
'ProviderInitError',
|
|
632
873
|
z.object({
|
|
633
874
|
providerID: z.string(),
|
|
634
|
-
})
|
|
635
|
-
)
|
|
875
|
+
})
|
|
876
|
+
);
|
|
636
877
|
}
|