pi-freeflow 1.5.0 → 1.7.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.md +62 -86
- package/package.json +2 -3
- package/src/catalog.ts +21 -12
- package/src/commands.ts +308 -146
- package/src/config.ts +24 -2
- package/src/deploy.ts +128 -185
- package/src/health.ts +2 -7
- package/src/index.ts +22 -4
- package/src/logger.ts +52 -24
- package/src/models.ts +139 -67
- package/src/probe.ts +49 -0
- package/src/proxy.ts +150 -57
- package/src/relay-state.ts +380 -69
- package/src/relay.ts +72 -26
- package/src/stream-pipe.ts +307 -271
- package/src/types.ts +4 -2
- package/src/update-checker.ts +21 -5
package/src/logger.ts
CHANGED
|
@@ -21,6 +21,18 @@ export const LOG_LEVEL_ORDER: Record<LogLevel, number> = {
|
|
|
21
21
|
audit: 4,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Levels that actually have emitters. 'audit' exists in LOG_LEVEL_ORDER as a
|
|
26
|
+
* sink for logAudit() callers but nothing decides the threshold by it — a
|
|
27
|
+
* persisted or env-provided 'audit' level would silently suppress everything
|
|
28
|
+
* below it (the audit-level freeze). Treat it as unknown everywhere.
|
|
29
|
+
*/
|
|
30
|
+
const EMITTED_LEVELS: readonly LogLevel[] = ["debug", "info", "warn", "error"];
|
|
31
|
+
|
|
32
|
+
export function isEmittedLogLevel(level: string): level is LogLevel {
|
|
33
|
+
return (EMITTED_LEVELS as readonly string[]).includes(level);
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
let cachedDebugState: DebugState | null | undefined = undefined;
|
|
25
37
|
let cachedDebugMtime = 0;
|
|
26
38
|
let cachedDebugAt = 0;
|
|
@@ -50,10 +62,16 @@ export function loadDebugState(): DebugState | null {
|
|
|
50
62
|
const raw = fs.readFileSync(DEBUG_STATE_FILE, "utf8");
|
|
51
63
|
const parsed = JSON.parse(raw) as DebugState;
|
|
52
64
|
if (typeof parsed?.debug === "boolean") {
|
|
53
|
-
|
|
65
|
+
// A stray 'audit' level (or any unknown level) must not suppress the
|
|
66
|
+
// whole log — keep the debug boolean, drop the unusable level.
|
|
67
|
+
const clean: DebugState = { debug: parsed.debug };
|
|
68
|
+
if (typeof parsed.level === "string" && isEmittedLogLevel(parsed.level)) {
|
|
69
|
+
clean.level = parsed.level;
|
|
70
|
+
}
|
|
71
|
+
cachedDebugState = clean;
|
|
54
72
|
cachedDebugMtime = stat.mtimeMs;
|
|
55
73
|
cachedDebugAt = now;
|
|
56
|
-
return
|
|
74
|
+
return clean;
|
|
57
75
|
}
|
|
58
76
|
} catch {
|
|
59
77
|
// Non-fatal if parsing or reading fails
|
|
@@ -91,6 +109,13 @@ export function saveDebugState(s: DebugState): void {
|
|
|
91
109
|
}
|
|
92
110
|
}
|
|
93
111
|
|
|
112
|
+
/** Test-only: drop the in-memory debug-state cache so a test's disk write is re-read. */
|
|
113
|
+
export function _resetDebugStateCacheForTest(): void {
|
|
114
|
+
cachedDebugState = undefined;
|
|
115
|
+
cachedDebugMtime = 0;
|
|
116
|
+
cachedDebugAt = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
/**
|
|
95
120
|
* Calculate the active minimum log level threshold based on state and env.
|
|
96
121
|
*/
|
|
@@ -99,13 +124,13 @@ export function getMinLogLevel(): number {
|
|
|
99
124
|
if (dbg?.debug) {
|
|
100
125
|
return LOG_LEVEL_ORDER.debug;
|
|
101
126
|
}
|
|
102
|
-
if (dbg?.level && dbg.level
|
|
127
|
+
if (dbg?.level && isEmittedLogLevel(dbg.level)) {
|
|
103
128
|
return LOG_LEVEL_ORDER[dbg.level];
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
const raw = (process.env.FREEFLOW_LOG_LEVEL || "info").toLowerCase();
|
|
107
132
|
|
|
108
|
-
if (raw
|
|
133
|
+
if (isEmittedLogLevel(raw)) {
|
|
109
134
|
return LOG_LEVEL_ORDER[raw as LogLevel];
|
|
110
135
|
}
|
|
111
136
|
|
|
@@ -143,9 +168,7 @@ export function rotateLogsIfNeeded(targetFile: string = LOG_FILE): void {
|
|
|
143
168
|
const dst = `${targetFile}.${i}`;
|
|
144
169
|
try {
|
|
145
170
|
if (fs.existsSync(src)) {
|
|
146
|
-
if (
|
|
147
|
-
fs.unlinkSync(dst);
|
|
148
|
-
} else if (fs.existsSync(dst)) {
|
|
171
|
+
if (fs.existsSync(dst)) {
|
|
149
172
|
fs.unlinkSync(dst);
|
|
150
173
|
}
|
|
151
174
|
fs.renameSync(src, dst);
|
|
@@ -252,14 +275,6 @@ export function logError(
|
|
|
252
275
|
log("error", message, meta, reqId);
|
|
253
276
|
}
|
|
254
277
|
|
|
255
|
-
export function logAudit(
|
|
256
|
-
message: string,
|
|
257
|
-
meta?: Record<string, unknown>,
|
|
258
|
-
reqId?: string,
|
|
259
|
-
): void {
|
|
260
|
-
log("audit", message, meta, reqId);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
278
|
export interface ReadRecentLogsResult {
|
|
264
279
|
lines: string[];
|
|
265
280
|
totalMatched: number;
|
|
@@ -269,20 +284,28 @@ export interface ReadRecentLogsResult {
|
|
|
269
284
|
|
|
270
285
|
/**
|
|
271
286
|
* Read recent log entries from log file and its rotated archives.
|
|
287
|
+
* @param filterLevel Optional log-level filter (case-insensitive label match).
|
|
288
|
+
* @param filterReqId Optional request-ID substring filter.
|
|
289
|
+
* @param count Max lines to return (clamped 1-200).
|
|
290
|
+
* @param filterText Optional case-insensitive text substring filter.
|
|
291
|
+
* @param files Optional log files to read (newest-last order); defaults to
|
|
292
|
+
* LOG_FILE and its rotated archives (.1-<LOG_MAX_FILES>).
|
|
272
293
|
*/
|
|
273
294
|
export function readRecentLogs(
|
|
274
295
|
filterLevel?: LogLevel | null,
|
|
275
296
|
filterReqId?: string | null,
|
|
276
297
|
count = 25,
|
|
298
|
+
filterText?: string | null,
|
|
299
|
+
files?: string[],
|
|
277
300
|
): ReadRecentLogsResult {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (
|
|
301
|
+
const filesList: string[] = (
|
|
302
|
+
files ?? [
|
|
303
|
+
LOG_FILE,
|
|
304
|
+
...Array.from({ length: LOG_MAX_FILES }, (_, i) => `${LOG_FILE}.${i + 1}`),
|
|
305
|
+
]
|
|
306
|
+
).filter((f) => fs.existsSync(f));
|
|
307
|
+
|
|
308
|
+
if (filesList.length === 0) {
|
|
286
309
|
return {
|
|
287
310
|
lines: [],
|
|
288
311
|
totalMatched: 0,
|
|
@@ -292,7 +315,7 @@ export function readRecentLogs(
|
|
|
292
315
|
}
|
|
293
316
|
|
|
294
317
|
let allLines: string[] = [];
|
|
295
|
-
for (const f of
|
|
318
|
+
for (const f of filesList) {
|
|
296
319
|
try {
|
|
297
320
|
const content = fs.readFileSync(f, "utf8");
|
|
298
321
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
@@ -315,6 +338,11 @@ export function readRecentLogs(
|
|
|
315
338
|
);
|
|
316
339
|
}
|
|
317
340
|
|
|
341
|
+
if (filterText) {
|
|
342
|
+
const lower = filterText.toLowerCase();
|
|
343
|
+
filtered = filtered.filter((l) => l.toLowerCase().includes(lower));
|
|
344
|
+
}
|
|
345
|
+
|
|
318
346
|
const clampedCount = Math.min(200, Math.max(1, count));
|
|
319
347
|
const resultLines = filtered.slice(-clampedCount);
|
|
320
348
|
|
package/src/models.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Static model definitions and upstream routing catalogs for pi-freeflow
|
|
3
3
|
*
|
|
4
|
-
* Defines the
|
|
4
|
+
* Defines the 25 verified free models:
|
|
5
5
|
* - 7 OpenCode Zen models (1 Responses API + 6 Chat Completions)
|
|
6
|
-
* -
|
|
6
|
+
* - 18 KiloCode Keyless Gateway models (17 OpenRouter format + 1 Standard format)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ModelDef, Upstream } from "./types.ts";
|
|
9
|
+
import type { ModelDef, ThinkingLevelMap, Upstream } from "./types.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* OpenCode Zen free models verified against the live catalog and inference APIs.
|
|
@@ -21,13 +21,13 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
21
21
|
maxTokens: 131_072,
|
|
22
22
|
api: "openai-responses",
|
|
23
23
|
input: ["text", "image"],
|
|
24
|
-
thinkingLevelMap: {
|
|
25
|
-
off: null,
|
|
26
|
-
minimal: "minimal",
|
|
27
|
-
low: "low",
|
|
28
|
-
medium: "medium",
|
|
29
|
-
high: "high",
|
|
30
|
-
xhigh: "xhigh",
|
|
24
|
+
thinkingLevelMap: {
|
|
25
|
+
off: null,
|
|
26
|
+
minimal: "minimal",
|
|
27
|
+
low: "low",
|
|
28
|
+
medium: "medium",
|
|
29
|
+
high: "high",
|
|
30
|
+
xhigh: "xhigh",
|
|
31
31
|
max: null,},
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -37,13 +37,13 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
37
37
|
contextWindow: 1_048_576,
|
|
38
38
|
maxTokens: 131_072,
|
|
39
39
|
input: ["text", "image"],
|
|
40
|
-
thinkingLevelMap: {
|
|
41
|
-
off: null,
|
|
42
|
-
minimal: "low",
|
|
43
|
-
low: "low",
|
|
44
|
-
medium: "medium",
|
|
45
|
-
high: "high",
|
|
46
|
-
xhigh: "high",
|
|
40
|
+
thinkingLevelMap: {
|
|
41
|
+
off: null,
|
|
42
|
+
minimal: "low",
|
|
43
|
+
low: "low",
|
|
44
|
+
medium: "medium",
|
|
45
|
+
high: "high",
|
|
46
|
+
xhigh: "high",
|
|
47
47
|
max: null,},
|
|
48
48
|
},
|
|
49
49
|
{
|
|
@@ -51,15 +51,15 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
51
51
|
name: "Hy3 (262K)",
|
|
52
52
|
reasoning: true,
|
|
53
53
|
contextWindow: 262_144,
|
|
54
|
-
maxTokens:
|
|
54
|
+
maxTokens: 128_000,
|
|
55
55
|
input: ["text"],
|
|
56
|
-
thinkingLevelMap: {
|
|
57
|
-
off: null,
|
|
58
|
-
minimal: "minimal",
|
|
59
|
-
low: "low",
|
|
60
|
-
medium: "medium",
|
|
61
|
-
high: "high",
|
|
62
|
-
xhigh: "xhigh",
|
|
56
|
+
thinkingLevelMap: {
|
|
57
|
+
off: null,
|
|
58
|
+
minimal: "minimal",
|
|
59
|
+
low: "low",
|
|
60
|
+
medium: "medium",
|
|
61
|
+
high: "high",
|
|
62
|
+
xhigh: "xhigh",
|
|
63
63
|
max: null,},
|
|
64
64
|
},
|
|
65
65
|
{
|
|
@@ -69,13 +69,13 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
69
69
|
contextWindow: 1_000_000,
|
|
70
70
|
maxTokens: 128_000,
|
|
71
71
|
input: ["text"],
|
|
72
|
-
thinkingLevelMap: {
|
|
73
|
-
off: null,
|
|
74
|
-
minimal: "minimal",
|
|
75
|
-
low: "low",
|
|
76
|
-
medium: "medium",
|
|
77
|
-
high: "high",
|
|
78
|
-
xhigh: "xhigh",
|
|
72
|
+
thinkingLevelMap: {
|
|
73
|
+
off: null,
|
|
74
|
+
minimal: "minimal",
|
|
75
|
+
low: "low",
|
|
76
|
+
medium: "medium",
|
|
77
|
+
high: "high",
|
|
78
|
+
xhigh: "xhigh",
|
|
79
79
|
max: null,},
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -85,13 +85,13 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
85
85
|
contextWindow: 1_000_000,
|
|
86
86
|
maxTokens: 262_144,
|
|
87
87
|
input: ["text"],
|
|
88
|
-
thinkingLevelMap: {
|
|
89
|
-
off: null,
|
|
90
|
-
minimal: "minimal",
|
|
91
|
-
low: "low",
|
|
92
|
-
medium: "medium",
|
|
93
|
-
high: "high",
|
|
94
|
-
xhigh: "xhigh",
|
|
88
|
+
thinkingLevelMap: {
|
|
89
|
+
off: null,
|
|
90
|
+
minimal: "minimal",
|
|
91
|
+
low: "low",
|
|
92
|
+
medium: "medium",
|
|
93
|
+
high: "high",
|
|
94
|
+
xhigh: "xhigh",
|
|
95
95
|
max: null,},
|
|
96
96
|
},
|
|
97
97
|
{
|
|
@@ -101,37 +101,49 @@ export const OPENCODE_MODELS: ModelDef[] = [
|
|
|
101
101
|
contextWindow: 200_000,
|
|
102
102
|
maxTokens: 32_000,
|
|
103
103
|
input: ["text"],
|
|
104
|
-
thinkingLevelMap: {
|
|
105
|
-
off: null,
|
|
106
|
-
minimal: null,
|
|
107
|
-
low: null,
|
|
108
|
-
medium: null,
|
|
109
|
-
high: "high",
|
|
110
|
-
xhigh: null,
|
|
104
|
+
thinkingLevelMap: {
|
|
105
|
+
off: null,
|
|
106
|
+
minimal: null,
|
|
107
|
+
low: null,
|
|
108
|
+
medium: null,
|
|
109
|
+
high: "high",
|
|
110
|
+
xhigh: null,
|
|
111
111
|
max: "max",},
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
id: "laguna-s-2.1-free",
|
|
115
|
-
name: "Laguna S 2.1 (
|
|
115
|
+
name: "Laguna S 2.1 (256K)",
|
|
116
116
|
reasoning: true,
|
|
117
|
-
contextWindow:
|
|
118
|
-
maxTokens:
|
|
117
|
+
contextWindow: 262_144,
|
|
118
|
+
maxTokens: 32_768,
|
|
119
119
|
input: ["text"],
|
|
120
|
-
thinkingLevelMap: {
|
|
121
|
-
off: null,
|
|
122
|
-
minimal: "minimal",
|
|
123
|
-
low: "low",
|
|
124
|
-
medium: "medium",
|
|
125
|
-
high: "high",
|
|
126
|
-
xhigh: "xhigh",
|
|
120
|
+
thinkingLevelMap: {
|
|
121
|
+
off: null,
|
|
122
|
+
minimal: "minimal",
|
|
123
|
+
low: "low",
|
|
124
|
+
medium: "medium",
|
|
125
|
+
high: "high",
|
|
126
|
+
xhigh: "xhigh",
|
|
127
127
|
max: null,},
|
|
128
128
|
},
|
|
129
129
|
];
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
132
|
+
* Shared effort map for Kilo reasoning models — verified live 2026-08-29:
|
|
133
|
+
* gateway accepts flat reasoning_effort minimal..xhigh for every reasoning
|
|
134
|
+
* model; stepfun/step-3.7-flash measured monotonic 77→313 thinking chars
|
|
135
|
+
* across minimal→xhigh. Declaring the map locks the picker (instead of
|
|
136
|
+
* host guessing) and matches the OpenCode-model pattern.
|
|
133
137
|
*/
|
|
134
|
-
|
|
138
|
+
const KILO_REASONING_MAP: ThinkingLevelMap = {
|
|
139
|
+
off: null,
|
|
140
|
+
minimal: "minimal",
|
|
141
|
+
low: "low",
|
|
142
|
+
medium: "medium",
|
|
143
|
+
high: "high",
|
|
144
|
+
xhigh: "xhigh",
|
|
145
|
+
max: null,
|
|
146
|
+
};
|
|
135
147
|
|
|
136
148
|
/**
|
|
137
149
|
* KiloCode Gateway free models (keyless — https://kilo.ai/docs/gateway).
|
|
@@ -146,6 +158,7 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
146
158
|
maxTokens: 512_000,
|
|
147
159
|
input: ["text", "image"],
|
|
148
160
|
thinkingFormat: "openrouter",
|
|
161
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
149
162
|
},
|
|
150
163
|
{
|
|
151
164
|
id: "stepfun/step-3.7-flash:free",
|
|
@@ -155,24 +168,27 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
155
168
|
maxTokens: 262_144,
|
|
156
169
|
input: ["text", "image"],
|
|
157
170
|
thinkingFormat: "openrouter",
|
|
171
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
158
172
|
},
|
|
159
173
|
{
|
|
160
174
|
id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
161
175
|
name: "Nemotron 3 Nano Omni",
|
|
162
176
|
reasoning: true,
|
|
163
177
|
contextWindow: 256_000,
|
|
164
|
-
maxTokens:
|
|
178
|
+
maxTokens: 131_072,
|
|
165
179
|
input: ["text", "image"],
|
|
166
180
|
thinkingFormat: "openrouter",
|
|
181
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
167
182
|
},
|
|
168
183
|
{
|
|
169
184
|
id: "nvidia/nemotron-3-ultra-550b-a55b:free",
|
|
170
185
|
name: "Nemotron 3 Ultra 550B (1M)",
|
|
171
186
|
reasoning: true,
|
|
172
187
|
contextWindow: 1_000_000,
|
|
173
|
-
maxTokens:
|
|
188
|
+
maxTokens: 128_000,
|
|
174
189
|
input: ["text"],
|
|
175
190
|
thinkingFormat: "openrouter",
|
|
191
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
176
192
|
},
|
|
177
193
|
{
|
|
178
194
|
id: "nvidia/nemotron-3.5-lightning:free",
|
|
@@ -182,6 +198,7 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
182
198
|
maxTokens: 262_144,
|
|
183
199
|
input: ["text"],
|
|
184
200
|
thinkingFormat: "openrouter",
|
|
201
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
185
202
|
},
|
|
186
203
|
{
|
|
187
204
|
id: "nvidia/nemotron-3-super-120b-a12b:free",
|
|
@@ -191,15 +208,17 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
191
208
|
maxTokens: 262_144,
|
|
192
209
|
input: ["text"],
|
|
193
210
|
thinkingFormat: "openrouter",
|
|
211
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
194
212
|
},
|
|
195
213
|
{
|
|
196
214
|
id: "tencent/hy3:free",
|
|
197
215
|
name: "Tencent Hy3 (Kilo)",
|
|
198
216
|
reasoning: true,
|
|
199
217
|
contextWindow: 262_144,
|
|
200
|
-
maxTokens:
|
|
218
|
+
maxTokens: 128_000,
|
|
201
219
|
input: ["text"],
|
|
202
220
|
thinkingFormat: "openrouter",
|
|
221
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
203
222
|
},
|
|
204
223
|
{
|
|
205
224
|
id: "cohere/north-mini-code:free",
|
|
@@ -209,15 +228,17 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
209
228
|
maxTokens: 64_000,
|
|
210
229
|
input: ["text"],
|
|
211
230
|
thinkingFormat: "openrouter",
|
|
231
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
212
232
|
},
|
|
213
233
|
{
|
|
214
234
|
id: "poolside/laguna-s-2.1:free",
|
|
215
235
|
name: "Laguna S 2.1 (Kilo)",
|
|
216
236
|
reasoning: true,
|
|
217
|
-
contextWindow:
|
|
218
|
-
maxTokens:
|
|
237
|
+
contextWindow: 262_144,
|
|
238
|
+
maxTokens: 32_768,
|
|
219
239
|
input: ["text"],
|
|
220
240
|
thinkingFormat: "openrouter",
|
|
241
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
221
242
|
},
|
|
222
243
|
{
|
|
223
244
|
id: "poolside/laguna-xs-2.1:free",
|
|
@@ -227,31 +248,37 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
227
248
|
maxTokens: 32_768,
|
|
228
249
|
input: ["text"],
|
|
229
250
|
thinkingFormat: "openrouter",
|
|
251
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
230
252
|
},
|
|
231
253
|
{
|
|
232
254
|
id: "liquid/lfm-2.5-2.6b:free",
|
|
233
255
|
name: "Liquid LFM 2.5",
|
|
234
256
|
reasoning: true,
|
|
235
|
-
contextWindow:
|
|
257
|
+
contextWindow: 65_536,
|
|
236
258
|
maxTokens: 32_768,
|
|
237
259
|
input: ["text"],
|
|
238
260
|
thinkingFormat: "openrouter",
|
|
261
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
239
262
|
},
|
|
240
263
|
{
|
|
241
264
|
id: "kilo-auto/free",
|
|
242
265
|
name: "Kilo Auto",
|
|
243
|
-
reasoning:
|
|
266
|
+
reasoning: true,
|
|
244
267
|
contextWindow: 256_000,
|
|
245
268
|
maxTokens: 10_000,
|
|
246
269
|
input: ["text"],
|
|
270
|
+
thinkingFormat: "openrouter",
|
|
271
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
247
272
|
},
|
|
248
273
|
{
|
|
249
274
|
id: "openrouter/free",
|
|
250
275
|
name: "OpenRouter Auto",
|
|
251
|
-
reasoning:
|
|
276
|
+
reasoning: true,
|
|
252
277
|
contextWindow: 200_000,
|
|
253
278
|
maxTokens: 65_536,
|
|
254
279
|
input: ["text", "image"],
|
|
280
|
+
thinkingFormat: "openrouter",
|
|
281
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
255
282
|
},
|
|
256
283
|
{
|
|
257
284
|
id: "nvidia/nemotron-3.5-content-safety:free",
|
|
@@ -261,6 +288,46 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
261
288
|
maxTokens: 8_192,
|
|
262
289
|
input: ["text", "image"],
|
|
263
290
|
},
|
|
291
|
+
{
|
|
292
|
+
id: "meituan/longcat-2.0-free",
|
|
293
|
+
name: "LongCat 2.0 (1M)",
|
|
294
|
+
reasoning: true,
|
|
295
|
+
contextWindow: 1_048_756,
|
|
296
|
+
maxTokens: 262_144,
|
|
297
|
+
input: ["text"],
|
|
298
|
+
thinkingFormat: "openrouter",
|
|
299
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "minimax/minimax-m2.7:free",
|
|
303
|
+
name: "MiniMax M2.7 (free)",
|
|
304
|
+
reasoning: true,
|
|
305
|
+
contextWindow: 196_608,
|
|
306
|
+
maxTokens: 196_608,
|
|
307
|
+
input: ["text"],
|
|
308
|
+
thinkingFormat: "openrouter",
|
|
309
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "minimax/minimax-m3:free",
|
|
313
|
+
name: "MiniMax M3 (free)",
|
|
314
|
+
reasoning: true,
|
|
315
|
+
contextWindow: 1_048_576,
|
|
316
|
+
maxTokens: 524_288,
|
|
317
|
+
input: ["text"],
|
|
318
|
+
thinkingFormat: "openrouter",
|
|
319
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "inclusionai/ling-3.0-flash-fin:free",
|
|
323
|
+
name: "Ling 3.0 Flash Fin",
|
|
324
|
+
reasoning: true,
|
|
325
|
+
contextWindow: 262_144,
|
|
326
|
+
maxTokens: 32_768,
|
|
327
|
+
input: ["text"],
|
|
328
|
+
thinkingFormat: "openrouter",
|
|
329
|
+
thinkingLevelMap: KILO_REASONING_MAP,
|
|
330
|
+
},
|
|
264
331
|
];
|
|
265
332
|
|
|
266
333
|
/**
|
|
@@ -276,10 +343,15 @@ export const MODEL_ALIASES: Record<string, string> = {
|
|
|
276
343
|
"step-3.7-flash": "stepfun/step-3.7-flash:free",
|
|
277
344
|
"nemotron-3-nano-omni": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
278
345
|
"nemotron-3-ultra-550b": "nvidia/nemotron-3-ultra-550b-a55b:free",
|
|
346
|
+
"nemotron-3.5-lightning": "nvidia/nemotron-3.5-lightning:free",
|
|
279
347
|
"nemotron-3-super": "nvidia/nemotron-3-super-120b-a12b:free",
|
|
280
348
|
"north-mini-code": "cohere/north-mini-code:free",
|
|
281
349
|
"lfm-2.5": "liquid/lfm-2.5-2.6b:free",
|
|
282
350
|
"content-safety": "nvidia/nemotron-3.5-content-safety:free",
|
|
351
|
+
"longcat-2.0": "meituan/longcat-2.0-free",
|
|
352
|
+
"minimax-m2.7": "minimax/minimax-m2.7:free",
|
|
353
|
+
"minimax-m3": "minimax/minimax-m3:free",
|
|
354
|
+
"ling-3.0-flash-fin": "inclusionai/ling-3.0-flash-fin:free",
|
|
283
355
|
// provider-prefixed short aliases (slash-normalized)
|
|
284
356
|
"hy3:free": "tencent/hy3:free",
|
|
285
357
|
"laguna-s-2.1:free": "poolside/laguna-s-2.1:free",
|
|
@@ -307,7 +379,7 @@ export const KILO_MODEL_IDS = new Set<string>([
|
|
|
307
379
|
]);
|
|
308
380
|
|
|
309
381
|
/**
|
|
310
|
-
* Combined list of all
|
|
382
|
+
* Combined list of all 25 static free models (canonical)
|
|
311
383
|
*/
|
|
312
384
|
export const ALL_MODELS: ModelDef[] = [...OPENCODE_MODELS, ...KILO_MODELS];
|
|
313
385
|
|
package/src/probe.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay reachability probe: verifies a deployed relay answers requests
|
|
3
|
+
* by issuing a minimal round trip through it and timing the response.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface RelayProbeResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
status: number;
|
|
9
|
+
latencyMs: number;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Probe a relay endpoint to confirm it is reachable.
|
|
15
|
+
*
|
|
16
|
+
* Sends a minimal request to `<url>/v1/models` carrying the x-relay-target /
|
|
17
|
+
* x-relay-path headers the relay expects, timing the round trip. Never
|
|
18
|
+
* throws: network failures, timeouts, and non-2xx statuses are all reported
|
|
19
|
+
* on the returned result instead.
|
|
20
|
+
*
|
|
21
|
+
* The path mirrors the proxy's real relay contract (`relayFetch` in relay.ts):
|
|
22
|
+
* `x-relay-target` is the upstream origin and `x-relay-path` the full path
|
|
23
|
+
* including the `/zen` prefix — the workers only allow the exact origins
|
|
24
|
+
* `https://opencode.ai` / `https://api.kilo.ai` and forward `target + path`.
|
|
25
|
+
* @param auth Optional per-relay shared secret; sent as x-relay-auth so
|
|
26
|
+
* deployed relays (which enforce it) answer the probe instead of 401.
|
|
27
|
+
*/
|
|
28
|
+
export async function probeRelay(url: string, auth?: string): Promise<RelayProbeResult> {
|
|
29
|
+
const cleanUrl = url.trim().replace(/\/+$/, "");
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`${cleanUrl}/v1/models`, {
|
|
33
|
+
headers: {
|
|
34
|
+
"x-relay-target": "https://opencode.ai",
|
|
35
|
+
"x-relay-path": "/zen/v1/models",
|
|
36
|
+
...(auth ? { "x-relay-auth": auth } : {}),
|
|
37
|
+
},
|
|
38
|
+
signal: AbortSignal.timeout(5_000),
|
|
39
|
+
});
|
|
40
|
+
return { ok: res.ok, status: res.status, latencyMs: Date.now() - start };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
status: 0,
|
|
45
|
+
latencyMs: Date.now() - start,
|
|
46
|
+
error: (e as Error)?.message || String(e),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|