@link-assistant/agent 0.12.3 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/auth/plugins.ts +168 -0
- package/src/bun/index.ts +73 -7
- package/src/cli/cmd/auth.ts +3 -2
- package/src/index.js +2 -0
- package/src/provider/provider.ts +103 -66
package/package.json
CHANGED
package/src/auth/plugins.ts
CHANGED
|
@@ -2630,6 +2630,173 @@ const AlibabaPlugin: AuthPlugin = {
|
|
|
2630
2630
|
},
|
|
2631
2631
|
};
|
|
2632
2632
|
|
|
2633
|
+
/**
|
|
2634
|
+
* Kilo Gateway constants
|
|
2635
|
+
* @see https://github.com/Kilo-Org/kilo/blob/main/packages/kilo-gateway/src/api/constants.ts
|
|
2636
|
+
*/
|
|
2637
|
+
const KILO_API_BASE = 'https://api.kilo.ai';
|
|
2638
|
+
const KILO_POLL_INTERVAL_MS = 3000;
|
|
2639
|
+
|
|
2640
|
+
/**
|
|
2641
|
+
* Kilo Gateway Auth Plugin
|
|
2642
|
+
* Supports device authorization flow for Kilo Gateway
|
|
2643
|
+
*
|
|
2644
|
+
* @see https://github.com/Kilo-Org/kilo/blob/main/packages/kilo-gateway/src/auth/device-auth.ts
|
|
2645
|
+
*/
|
|
2646
|
+
const KiloPlugin: AuthPlugin = {
|
|
2647
|
+
provider: 'kilo',
|
|
2648
|
+
methods: [
|
|
2649
|
+
{
|
|
2650
|
+
label: 'Kilo Gateway (Device Authorization)',
|
|
2651
|
+
type: 'oauth',
|
|
2652
|
+
async authorize() {
|
|
2653
|
+
// Initiate device authorization
|
|
2654
|
+
const initResponse = await fetch(
|
|
2655
|
+
`${KILO_API_BASE}/api/device-auth/codes`,
|
|
2656
|
+
{
|
|
2657
|
+
method: 'POST',
|
|
2658
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2659
|
+
}
|
|
2660
|
+
);
|
|
2661
|
+
|
|
2662
|
+
if (!initResponse.ok) {
|
|
2663
|
+
if (initResponse.status === 429) {
|
|
2664
|
+
log.error(() => ({
|
|
2665
|
+
message:
|
|
2666
|
+
'kilo device auth rate limited - too many pending requests',
|
|
2667
|
+
}));
|
|
2668
|
+
return {
|
|
2669
|
+
method: 'auto' as const,
|
|
2670
|
+
async callback(): Promise<AuthResult> {
|
|
2671
|
+
return { type: 'failed' };
|
|
2672
|
+
},
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
log.error(() => ({
|
|
2676
|
+
message: 'kilo device auth initiation failed',
|
|
2677
|
+
status: initResponse.status,
|
|
2678
|
+
}));
|
|
2679
|
+
return {
|
|
2680
|
+
method: 'auto' as const,
|
|
2681
|
+
async callback(): Promise<AuthResult> {
|
|
2682
|
+
return { type: 'failed' };
|
|
2683
|
+
},
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
const authData = (await initResponse.json()) as {
|
|
2688
|
+
code: string;
|
|
2689
|
+
verificationUrl: string;
|
|
2690
|
+
expiresIn: number;
|
|
2691
|
+
};
|
|
2692
|
+
|
|
2693
|
+
return {
|
|
2694
|
+
url: authData.verificationUrl,
|
|
2695
|
+
instructions: `Enter code: ${authData.code}\nWaiting for authorization...`,
|
|
2696
|
+
method: 'auto' as const,
|
|
2697
|
+
async callback(): Promise<AuthResult> {
|
|
2698
|
+
const maxAttempts = Math.ceil(
|
|
2699
|
+
(authData.expiresIn * 1000) / KILO_POLL_INTERVAL_MS
|
|
2700
|
+
);
|
|
2701
|
+
|
|
2702
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2703
|
+
await new Promise((resolve) =>
|
|
2704
|
+
setTimeout(resolve, KILO_POLL_INTERVAL_MS)
|
|
2705
|
+
);
|
|
2706
|
+
|
|
2707
|
+
const pollResponse = await fetch(
|
|
2708
|
+
`${KILO_API_BASE}/api/device-auth/codes/${authData.code}`
|
|
2709
|
+
);
|
|
2710
|
+
|
|
2711
|
+
if (pollResponse.status === 202) {
|
|
2712
|
+
// Still pending
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
if (pollResponse.status === 403) {
|
|
2717
|
+
log.error(() => ({
|
|
2718
|
+
message: 'kilo device auth denied by user',
|
|
2719
|
+
}));
|
|
2720
|
+
return { type: 'failed' };
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
if (pollResponse.status === 410) {
|
|
2724
|
+
log.error(() => ({
|
|
2725
|
+
message: 'kilo device auth code expired',
|
|
2726
|
+
}));
|
|
2727
|
+
return { type: 'failed' };
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
if (!pollResponse.ok) {
|
|
2731
|
+
log.error(() => ({
|
|
2732
|
+
message: 'kilo device auth poll failed',
|
|
2733
|
+
status: pollResponse.status,
|
|
2734
|
+
}));
|
|
2735
|
+
return { type: 'failed' };
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
const data = (await pollResponse.json()) as {
|
|
2739
|
+
status: string;
|
|
2740
|
+
token?: string;
|
|
2741
|
+
userEmail?: string;
|
|
2742
|
+
};
|
|
2743
|
+
|
|
2744
|
+
if (data.status === 'approved' && data.token) {
|
|
2745
|
+
log.info(() => ({
|
|
2746
|
+
message: 'kilo device auth approved',
|
|
2747
|
+
email: data.userEmail,
|
|
2748
|
+
}));
|
|
2749
|
+
|
|
2750
|
+
// Token from Kilo device auth is long-lived (1 year)
|
|
2751
|
+
const TOKEN_EXPIRATION_MS = 365 * 24 * 60 * 60 * 1000;
|
|
2752
|
+
return {
|
|
2753
|
+
type: 'success',
|
|
2754
|
+
provider: 'kilo',
|
|
2755
|
+
refresh: data.token,
|
|
2756
|
+
access: data.token,
|
|
2757
|
+
expires: Date.now() + TOKEN_EXPIRATION_MS,
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
log.error(() => ({
|
|
2763
|
+
message: 'kilo device auth timed out',
|
|
2764
|
+
}));
|
|
2765
|
+
return { type: 'failed' };
|
|
2766
|
+
},
|
|
2767
|
+
};
|
|
2768
|
+
},
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
label: 'API Key',
|
|
2772
|
+
type: 'api',
|
|
2773
|
+
async authorize(inputs: Record<string, string>) {
|
|
2774
|
+
const key = inputs['key'];
|
|
2775
|
+
if (!key) return { type: 'failed' };
|
|
2776
|
+
return {
|
|
2777
|
+
type: 'success',
|
|
2778
|
+
provider: 'kilo',
|
|
2779
|
+
key,
|
|
2780
|
+
};
|
|
2781
|
+
},
|
|
2782
|
+
},
|
|
2783
|
+
],
|
|
2784
|
+
async loader(getAuth) {
|
|
2785
|
+
const auth = await getAuth();
|
|
2786
|
+
if (!auth) return {};
|
|
2787
|
+
|
|
2788
|
+
if (auth.type === 'api') {
|
|
2789
|
+
return { apiKey: auth.key };
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
if (auth.type === 'oauth') {
|
|
2793
|
+
return { apiKey: auth.access };
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
return {};
|
|
2797
|
+
},
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2633
2800
|
/**
|
|
2634
2801
|
* Registry of all auth plugins
|
|
2635
2802
|
*/
|
|
@@ -2640,6 +2807,7 @@ const plugins: Record<string, AuthPlugin> = {
|
|
|
2640
2807
|
google: GooglePlugin,
|
|
2641
2808
|
'qwen-coder': QwenPlugin,
|
|
2642
2809
|
alibaba: AlibabaPlugin,
|
|
2810
|
+
kilo: KiloPlugin,
|
|
2643
2811
|
};
|
|
2644
2812
|
|
|
2645
2813
|
/**
|
package/src/bun/index.ts
CHANGED
|
@@ -13,26 +13,69 @@ export namespace BunProc {
|
|
|
13
13
|
// Lock key for serializing package installations to prevent race conditions
|
|
14
14
|
const INSTALL_LOCK_KEY = 'bun-install';
|
|
15
15
|
|
|
16
|
+
// Default timeout for subprocess commands (2 minutes)
|
|
17
|
+
// This prevents indefinite hangs from known Bun issues:
|
|
18
|
+
// - HTTP 304 response handling (https://github.com/oven-sh/bun/issues/5831)
|
|
19
|
+
// - Failed dependency fetch (https://github.com/oven-sh/bun/issues/26341)
|
|
20
|
+
// - IPv6 configuration issues
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 120000;
|
|
22
|
+
|
|
23
|
+
// Timeout specifically for package installation (60 seconds)
|
|
24
|
+
// Package installations should complete within this time for typical packages
|
|
25
|
+
const INSTALL_TIMEOUT_MS = 60000;
|
|
26
|
+
|
|
27
|
+
export const TimeoutError = NamedError.create(
|
|
28
|
+
'BunTimeoutError',
|
|
29
|
+
z.object({
|
|
30
|
+
cmd: z.array(z.string()),
|
|
31
|
+
timeoutMs: z.number(),
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
16
35
|
export async function run(
|
|
17
36
|
cmd: string[],
|
|
18
|
-
options?: Bun.SpawnOptions.OptionsObject<any, any, any>
|
|
37
|
+
options?: Bun.SpawnOptions.OptionsObject<any, any, any> & {
|
|
38
|
+
timeout?: number;
|
|
39
|
+
}
|
|
19
40
|
) {
|
|
41
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
42
|
+
|
|
20
43
|
log.info(() => ({
|
|
21
44
|
message: 'running',
|
|
22
45
|
cmd: [which(), ...cmd],
|
|
23
|
-
|
|
46
|
+
timeout,
|
|
47
|
+
cwd: options?.cwd,
|
|
24
48
|
}));
|
|
49
|
+
|
|
25
50
|
const result = Bun.spawn([which(), ...cmd], {
|
|
26
51
|
...options,
|
|
27
52
|
stdout: 'pipe',
|
|
28
53
|
stderr: 'pipe',
|
|
54
|
+
timeout, // Automatically kills process after timeout
|
|
55
|
+
killSignal: 'SIGTERM', // Graceful termination signal
|
|
29
56
|
env: {
|
|
30
57
|
...process.env,
|
|
31
58
|
...options?.env,
|
|
32
59
|
BUN_BE_BUN: '1',
|
|
33
60
|
},
|
|
34
61
|
});
|
|
62
|
+
|
|
35
63
|
const code = await result.exited;
|
|
64
|
+
|
|
65
|
+
// Check if process was killed due to timeout
|
|
66
|
+
if (result.signalCode === 'SIGTERM' && code !== 0) {
|
|
67
|
+
log.error(() => ({
|
|
68
|
+
message: 'command timed out',
|
|
69
|
+
cmd: [which(), ...cmd],
|
|
70
|
+
timeout,
|
|
71
|
+
signalCode: result.signalCode,
|
|
72
|
+
}));
|
|
73
|
+
throw new TimeoutError({
|
|
74
|
+
cmd: [which(), ...cmd],
|
|
75
|
+
timeoutMs: timeout,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
const stdout = result.stdout
|
|
37
80
|
? typeof result.stdout === 'number'
|
|
38
81
|
? result.stdout
|
|
@@ -84,6 +127,13 @@ export namespace BunProc {
|
|
|
84
127
|
);
|
|
85
128
|
}
|
|
86
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Check if an error is a timeout error
|
|
132
|
+
*/
|
|
133
|
+
function isTimeoutError(error: unknown): boolean {
|
|
134
|
+
return error instanceof TimeoutError;
|
|
135
|
+
}
|
|
136
|
+
|
|
87
137
|
/**
|
|
88
138
|
* Wait for a specified duration
|
|
89
139
|
*/
|
|
@@ -139,12 +189,13 @@ export namespace BunProc {
|
|
|
139
189
|
version,
|
|
140
190
|
}));
|
|
141
191
|
|
|
142
|
-
// Retry logic for cache-related errors
|
|
192
|
+
// Retry logic for cache-related errors and timeout errors
|
|
143
193
|
let lastError: Error | undefined;
|
|
144
194
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
145
195
|
try {
|
|
146
196
|
await BunProc.run(args, {
|
|
147
197
|
cwd: Global.Path.cache,
|
|
198
|
+
timeout: INSTALL_TIMEOUT_MS, // Use specific timeout for package installation
|
|
148
199
|
});
|
|
149
200
|
|
|
150
201
|
log.info(() => ({
|
|
@@ -159,6 +210,7 @@ export namespace BunProc {
|
|
|
159
210
|
} catch (e) {
|
|
160
211
|
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
161
212
|
const isCacheError = isCacheRelatedError(errorMsg);
|
|
213
|
+
const isTimeout = isTimeoutError(e);
|
|
162
214
|
|
|
163
215
|
log.warn(() => ({
|
|
164
216
|
message: 'package installation attempt failed',
|
|
@@ -168,11 +220,15 @@ export namespace BunProc {
|
|
|
168
220
|
maxRetries: MAX_RETRIES,
|
|
169
221
|
error: errorMsg,
|
|
170
222
|
isCacheError,
|
|
223
|
+
isTimeout,
|
|
171
224
|
}));
|
|
172
225
|
|
|
173
|
-
|
|
226
|
+
// Retry on cache-related errors or timeout errors
|
|
227
|
+
if ((isCacheError || isTimeout) && attempt < MAX_RETRIES) {
|
|
174
228
|
log.info(() => ({
|
|
175
|
-
message:
|
|
229
|
+
message: isTimeout
|
|
230
|
+
? 'retrying installation after timeout (possible network issue)'
|
|
231
|
+
: 'retrying installation after cache-related error',
|
|
176
232
|
pkg,
|
|
177
233
|
version,
|
|
178
234
|
attempt,
|
|
@@ -184,7 +240,7 @@ export namespace BunProc {
|
|
|
184
240
|
continue;
|
|
185
241
|
}
|
|
186
242
|
|
|
187
|
-
// Non-
|
|
243
|
+
// Non-retriable error or final attempt - log and throw
|
|
188
244
|
log.error(() => ({
|
|
189
245
|
message: 'package installation failed',
|
|
190
246
|
pkg,
|
|
@@ -192,10 +248,11 @@ export namespace BunProc {
|
|
|
192
248
|
error: errorMsg,
|
|
193
249
|
stack: e instanceof Error ? e.stack : undefined,
|
|
194
250
|
possibleCacheCorruption: isCacheError,
|
|
251
|
+
timedOut: isTimeout,
|
|
195
252
|
attempts: attempt,
|
|
196
253
|
}));
|
|
197
254
|
|
|
198
|
-
// Provide helpful recovery instructions
|
|
255
|
+
// Provide helpful recovery instructions
|
|
199
256
|
if (isCacheError) {
|
|
200
257
|
log.error(() => ({
|
|
201
258
|
message:
|
|
@@ -203,6 +260,15 @@ export namespace BunProc {
|
|
|
203
260
|
}));
|
|
204
261
|
}
|
|
205
262
|
|
|
263
|
+
if (isTimeout) {
|
|
264
|
+
log.error(() => ({
|
|
265
|
+
message:
|
|
266
|
+
'Package installation timed out. This may be due to network issues or Bun hanging. ' +
|
|
267
|
+
'Try: 1) Check network connectivity, 2) Run "bun pm cache rm" to clear cache, ' +
|
|
268
|
+
'3) Check for IPv6 issues (try disabling IPv6)',
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
206
272
|
throw new InstallFailedError(
|
|
207
273
|
{ pkg, version, details: errorMsg },
|
|
208
274
|
{
|
package/src/cli/cmd/auth.ts
CHANGED
|
@@ -128,8 +128,9 @@ export const AuthLoginCommand = cmd({
|
|
|
128
128
|
'github-copilot': 1,
|
|
129
129
|
openai: 2,
|
|
130
130
|
google: 3,
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
kilo: 4,
|
|
132
|
+
openrouter: 5,
|
|
133
|
+
vercel: 6,
|
|
133
134
|
};
|
|
134
135
|
|
|
135
136
|
// Note: Using `select` instead of `autocomplete` because `autocomplete` is only
|
package/src/index.js
CHANGED
|
@@ -172,8 +172,10 @@ async function parseModelConfig(argv) {
|
|
|
172
172
|
modelID = modelID || 'kimi-k2.5-free';
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// Log raw and parsed values to help diagnose model routing issues (#171)
|
|
175
176
|
Log.Default.info(() => ({
|
|
176
177
|
message: 'using explicit provider/model',
|
|
178
|
+
rawModel: modelArg,
|
|
177
179
|
providerID,
|
|
178
180
|
modelID,
|
|
179
181
|
}));
|
package/src/provider/provider.ts
CHANGED
|
@@ -324,19 +324,21 @@ export namespace Provider {
|
|
|
324
324
|
},
|
|
325
325
|
/**
|
|
326
326
|
* Kilo provider - access to 500+ AI models through Kilo Gateway
|
|
327
|
-
* Uses
|
|
327
|
+
* Uses OpenRouter-compatible API at https://api.kilo.ai/api/openrouter
|
|
328
328
|
*
|
|
329
|
-
*
|
|
329
|
+
* Authentication required: run `agent auth login` and select "Kilo Gateway"
|
|
330
|
+
* For API key authentication, set KILO_API_KEY environment variable
|
|
331
|
+
*
|
|
332
|
+
* Free models available after authentication:
|
|
330
333
|
* - GLM-5 (z-ai/glm-5) - Free limited time, flagship Z.AI model
|
|
331
334
|
* - GLM 4.7 (z-ai/glm-4.7:free) - Free, agent-centric model
|
|
332
335
|
* - Kimi K2.5 (moonshot/kimi-k2.5:free) - Free, agentic capabilities
|
|
333
336
|
* - MiniMax M2.1 (minimax/m2.1:free) - Free, general-purpose
|
|
334
337
|
* - Giga Potato (giga-potato:free) - Free evaluation model
|
|
335
338
|
*
|
|
336
|
-
* For paid models, set KILO_API_KEY environment variable
|
|
337
|
-
*
|
|
338
339
|
* @see https://kilo.ai/docs/gateway
|
|
339
340
|
* @see https://kilo.ai/docs/advanced-usage/free-and-budget-models
|
|
341
|
+
* @see https://github.com/Kilo-Org/kilo/tree/main/packages/kilo-gateway
|
|
340
342
|
*/
|
|
341
343
|
kilo: async (input) => {
|
|
342
344
|
const hasKey = await (async () => {
|
|
@@ -345,8 +347,7 @@ export namespace Provider {
|
|
|
345
347
|
return false;
|
|
346
348
|
})();
|
|
347
349
|
|
|
348
|
-
//
|
|
349
|
-
// For paid models, user needs to set KILO_API_KEY
|
|
350
|
+
// Filter to only free models when no API key
|
|
350
351
|
if (!hasKey) {
|
|
351
352
|
for (const [key, value] of Object.entries(input.models)) {
|
|
352
353
|
// Keep only free models (cost.input === 0) when no API key
|
|
@@ -355,13 +356,44 @@ export namespace Provider {
|
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
|
|
359
|
+
// Build options
|
|
360
|
+
const options: Record<string, any> = {};
|
|
361
|
+
|
|
362
|
+
// Kilo-specific headers for the OpenRouter-compatible API
|
|
363
|
+
// @see https://github.com/Kilo-Org/kilo/blob/main/packages/kilo-gateway/src/headers.ts
|
|
364
|
+
const headers: Record<string, string> = {
|
|
365
|
+
'User-Agent': 'opencode-kilo-provider',
|
|
366
|
+
'X-KILOCODE-EDITORNAME': 'link-assistant-agent',
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Pass KILO_ORG_ID if available
|
|
370
|
+
if (process.env['KILO_ORG_ID']) {
|
|
371
|
+
headers['X-KILOCODE-ORGANIZATIONID'] = process.env['KILO_ORG_ID'];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
options.headers = headers;
|
|
375
|
+
|
|
376
|
+
// Use auth token if available (from `agent auth login` -> Kilo device auth)
|
|
377
|
+
if (!hasKey) {
|
|
378
|
+
// Without authentication, use 'anonymous' key
|
|
379
|
+
// Note: The Kilo API may reject anonymous requests for completions.
|
|
380
|
+
// Users should authenticate via `agent auth login` for reliable access.
|
|
381
|
+
options.apiKey = 'anonymous';
|
|
382
|
+
} else {
|
|
383
|
+
// If we have stored auth, the loader will provide the token
|
|
384
|
+
const auth = await Auth.get(input.id);
|
|
385
|
+
if (auth) {
|
|
386
|
+
if (auth.type === 'api') {
|
|
387
|
+
options.apiKey = auth.key;
|
|
388
|
+
} else if (auth.type === 'oauth') {
|
|
389
|
+
options.apiKey = auth.access;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
358
394
|
return {
|
|
359
395
|
autoload: Object.keys(input.models).length > 0,
|
|
360
|
-
options
|
|
361
|
-
? {}
|
|
362
|
-
: {
|
|
363
|
-
apiKey: 'public',
|
|
364
|
-
},
|
|
396
|
+
options,
|
|
365
397
|
};
|
|
366
398
|
},
|
|
367
399
|
/**
|
|
@@ -769,13 +801,14 @@ export namespace Provider {
|
|
|
769
801
|
database['kilo'] = {
|
|
770
802
|
id: 'kilo',
|
|
771
803
|
name: 'Kilo Gateway',
|
|
772
|
-
npm: '@ai-sdk
|
|
773
|
-
api: 'https://api.kilo.ai/api/
|
|
804
|
+
npm: '@openrouter/ai-sdk-provider',
|
|
805
|
+
api: 'https://api.kilo.ai/api/openrouter',
|
|
774
806
|
env: ['KILO_API_KEY'],
|
|
775
807
|
models: {
|
|
776
|
-
// GLM-5 - Flagship Z.AI model, free
|
|
808
|
+
// GLM-5 - Flagship Z.AI model, free tier
|
|
809
|
+
// Kilo API model ID: z-ai/glm-5:free (NOT z-ai/glm-5 which is paid)
|
|
777
810
|
'glm-5-free': {
|
|
778
|
-
id: 'z-ai/glm-5',
|
|
811
|
+
id: 'z-ai/glm-5:free',
|
|
779
812
|
name: 'GLM-5 (Free)',
|
|
780
813
|
release_date: '2026-02-11',
|
|
781
814
|
attachment: false,
|
|
@@ -789,7 +822,7 @@ export namespace Provider {
|
|
|
789
822
|
cache_write: 0,
|
|
790
823
|
},
|
|
791
824
|
limit: {
|
|
792
|
-
context:
|
|
825
|
+
context: 202800,
|
|
793
826
|
output: 131072,
|
|
794
827
|
},
|
|
795
828
|
modalities: {
|
|
@@ -798,10 +831,11 @@ export namespace Provider {
|
|
|
798
831
|
},
|
|
799
832
|
options: {},
|
|
800
833
|
},
|
|
801
|
-
// GLM 4.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
834
|
+
// GLM 4.5 Air - Free Z.AI model (replaces non-existent glm-4.7:free)
|
|
835
|
+
// Kilo API model ID: z-ai/glm-4.5-air:free
|
|
836
|
+
'glm-4.5-air-free': {
|
|
837
|
+
id: 'z-ai/glm-4.5-air:free',
|
|
838
|
+
name: 'GLM 4.5 Air (Free)',
|
|
805
839
|
release_date: '2026-01-15',
|
|
806
840
|
attachment: false,
|
|
807
841
|
reasoning: true,
|
|
@@ -815,7 +849,7 @@ export namespace Provider {
|
|
|
815
849
|
},
|
|
816
850
|
limit: {
|
|
817
851
|
context: 131072,
|
|
818
|
-
output:
|
|
852
|
+
output: 96000,
|
|
819
853
|
},
|
|
820
854
|
modalities: {
|
|
821
855
|
input: ['text'],
|
|
@@ -823,13 +857,14 @@ export namespace Provider {
|
|
|
823
857
|
},
|
|
824
858
|
options: {},
|
|
825
859
|
},
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
860
|
+
// MiniMax M2.5 - General-purpose, free (upgraded from M2.1)
|
|
861
|
+
// Kilo API model ID: minimax/minimax-m2.5:free
|
|
862
|
+
'minimax-m2.5-free': {
|
|
863
|
+
id: 'minimax/minimax-m2.5:free',
|
|
864
|
+
name: 'MiniMax M2.5 (Free)',
|
|
865
|
+
release_date: '2026-01-01',
|
|
831
866
|
attachment: false,
|
|
832
|
-
reasoning:
|
|
867
|
+
reasoning: true,
|
|
833
868
|
temperature: true,
|
|
834
869
|
tool_call: true,
|
|
835
870
|
cost: {
|
|
@@ -839,8 +874,8 @@ export namespace Provider {
|
|
|
839
874
|
cache_write: 0,
|
|
840
875
|
},
|
|
841
876
|
limit: {
|
|
842
|
-
context:
|
|
843
|
-
output:
|
|
877
|
+
context: 204800,
|
|
878
|
+
output: 131072,
|
|
844
879
|
},
|
|
845
880
|
modalities: {
|
|
846
881
|
input: ['text'],
|
|
@@ -848,13 +883,14 @@ export namespace Provider {
|
|
|
848
883
|
},
|
|
849
884
|
options: {},
|
|
850
885
|
},
|
|
851
|
-
//
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
886
|
+
// Giga Potato - Free evaluation model
|
|
887
|
+
// Kilo API model ID: giga-potato (no :free suffix)
|
|
888
|
+
'giga-potato-free': {
|
|
889
|
+
id: 'giga-potato',
|
|
890
|
+
name: 'Giga Potato (Free)',
|
|
891
|
+
release_date: '2026-01-01',
|
|
892
|
+
attachment: true,
|
|
893
|
+
reasoning: true,
|
|
858
894
|
temperature: true,
|
|
859
895
|
tool_call: true,
|
|
860
896
|
cost: {
|
|
@@ -864,19 +900,20 @@ export namespace Provider {
|
|
|
864
900
|
cache_write: 0,
|
|
865
901
|
},
|
|
866
902
|
limit: {
|
|
867
|
-
context:
|
|
868
|
-
output:
|
|
903
|
+
context: 256000,
|
|
904
|
+
output: 32000,
|
|
869
905
|
},
|
|
870
906
|
modalities: {
|
|
871
|
-
input: ['text'],
|
|
907
|
+
input: ['text', 'image'],
|
|
872
908
|
output: ['text'],
|
|
873
909
|
},
|
|
874
910
|
options: {},
|
|
875
911
|
},
|
|
876
|
-
//
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
912
|
+
// Trinity Large Preview - Preview model from Arcee AI
|
|
913
|
+
// Kilo API model ID: arcee-ai/trinity-large-preview:free
|
|
914
|
+
'trinity-large-preview': {
|
|
915
|
+
id: 'arcee-ai/trinity-large-preview:free',
|
|
916
|
+
name: 'Trinity Large Preview (Free)',
|
|
880
917
|
release_date: '2026-01-01',
|
|
881
918
|
attachment: false,
|
|
882
919
|
reasoning: false,
|
|
@@ -889,8 +926,8 @@ export namespace Provider {
|
|
|
889
926
|
cache_write: 0,
|
|
890
927
|
},
|
|
891
928
|
limit: {
|
|
892
|
-
context:
|
|
893
|
-
output:
|
|
929
|
+
context: 131000,
|
|
930
|
+
output: 65536,
|
|
894
931
|
},
|
|
895
932
|
modalities: {
|
|
896
933
|
input: ['text'],
|
|
@@ -898,15 +935,16 @@ export namespace Provider {
|
|
|
898
935
|
},
|
|
899
936
|
options: {},
|
|
900
937
|
},
|
|
901
|
-
//
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
938
|
+
// DeepSeek R1 - Reasoning model, free
|
|
939
|
+
// Kilo API model ID: deepseek/deepseek-r1-0528:free
|
|
940
|
+
'deepseek-r1-free': {
|
|
941
|
+
id: 'deepseek/deepseek-r1-0528:free',
|
|
942
|
+
name: 'DeepSeek R1 (Free)',
|
|
943
|
+
release_date: '2025-05-28',
|
|
906
944
|
attachment: false,
|
|
907
|
-
reasoning:
|
|
945
|
+
reasoning: true,
|
|
908
946
|
temperature: true,
|
|
909
|
-
tool_call:
|
|
947
|
+
tool_call: false,
|
|
910
948
|
cost: {
|
|
911
949
|
input: 0,
|
|
912
950
|
output: 0,
|
|
@@ -914,8 +952,8 @@ export namespace Provider {
|
|
|
914
952
|
cache_write: 0,
|
|
915
953
|
},
|
|
916
954
|
limit: {
|
|
917
|
-
context:
|
|
918
|
-
output:
|
|
955
|
+
context: 163840,
|
|
956
|
+
output: 163840,
|
|
919
957
|
},
|
|
920
958
|
modalities: {
|
|
921
959
|
input: ['text'],
|
|
@@ -1293,9 +1331,9 @@ export namespace Provider {
|
|
|
1293
1331
|
if (providerID === 'kilo') {
|
|
1294
1332
|
priority = [
|
|
1295
1333
|
'glm-5-free',
|
|
1296
|
-
'glm-4.
|
|
1297
|
-
'
|
|
1298
|
-
'
|
|
1334
|
+
'glm-4.5-air-free',
|
|
1335
|
+
'minimax-m2.5-free',
|
|
1336
|
+
'deepseek-r1-free',
|
|
1299
1337
|
'giga-potato-free',
|
|
1300
1338
|
];
|
|
1301
1339
|
}
|
|
@@ -1391,10 +1429,9 @@ export namespace Provider {
|
|
|
1391
1429
|
* Priority for free models:
|
|
1392
1430
|
* 1. If model is uniquely available in one provider, use that provider
|
|
1393
1431
|
* 2. If model is available in multiple providers, prioritize based on free model availability:
|
|
1394
|
-
* - kilo: glm-5-free, glm-4.
|
|
1395
|
-
* - opencode:
|
|
1396
|
-
*
|
|
1397
|
-
* 3. For shared models like kimi-k2.5-free, prefer OpenCode first, then fall back to Kilo on rate limit
|
|
1432
|
+
* - kilo: glm-5-free, glm-4.5-air-free, minimax-m2.5-free, giga-potato-free, deepseek-r1-free (unique to Kilo)
|
|
1433
|
+
* - opencode: big-pickle, gpt-5-nano (unique to OpenCode)
|
|
1434
|
+
* 3. For shared models, prefer OpenCode first, then fall back to Kilo on rate limit
|
|
1398
1435
|
*
|
|
1399
1436
|
* @param modelID - Short model name without provider prefix
|
|
1400
1437
|
* @returns Provider ID that should handle this model, or undefined if not found
|
|
@@ -1408,9 +1445,11 @@ export namespace Provider {
|
|
|
1408
1445
|
// Models unique to Kilo (GLM models from Z.AI are only free on Kilo)
|
|
1409
1446
|
const kiloUniqueModels = [
|
|
1410
1447
|
'glm-5-free',
|
|
1411
|
-
'glm-4.
|
|
1448
|
+
'glm-4.5-air-free',
|
|
1449
|
+
'minimax-m2.5-free',
|
|
1412
1450
|
'giga-potato-free',
|
|
1413
1451
|
'trinity-large-preview',
|
|
1452
|
+
'deepseek-r1-free',
|
|
1414
1453
|
];
|
|
1415
1454
|
|
|
1416
1455
|
// Check if it's a Kilo-unique model
|
|
@@ -1515,10 +1554,8 @@ export namespace Provider {
|
|
|
1515
1554
|
* If user specifies "kilo/kimi-k2.5-free", no fallback will occur.
|
|
1516
1555
|
*/
|
|
1517
1556
|
const SHARED_FREE_MODELS: Record<string, string[]> = {
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1520
|
-
// Note: minimax-m2.1-free is Kilo only, minimax-m2.5-free is OpenCode only
|
|
1521
|
-
// They are different model versions, not shared
|
|
1557
|
+
// Currently no shared models between OpenCode and Kilo providers.
|
|
1558
|
+
// Kilo models use different IDs than OpenCode models.
|
|
1522
1559
|
};
|
|
1523
1560
|
|
|
1524
1561
|
/**
|