thebird 1.2.84 → 1.2.85
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/docs/app.js +0 -1
- package/docs/kilo-fs-mirror.js +17 -0
- package/docs/kilo-http-stream.js +20 -11
- package/package.json +1 -1
- package/start-kilo.js +33 -7
package/docs/app.js
CHANGED
|
@@ -12,7 +12,6 @@ const PROVIDERS = {
|
|
|
12
12
|
deepseek: { label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', keyPlaceholder: 'DEEPSEEK_API_KEY', models: ['deepseek-chat', 'deepseek-reasoner'] },
|
|
13
13
|
cerebras: { label: 'Cerebras', baseUrl: 'https://api.cerebras.ai/v1', keyPlaceholder: 'CEREBRAS_API_KEY', models: ['gpt-oss-120b', 'llama3.1-8b'] },
|
|
14
14
|
kilo: { label: 'Kilo Code', baseUrl: 'http://localhost:4780', keyPlaceholder: '(no key needed)', models: ['x-ai/grok-code-fast-1:optimized:free', 'openrouter/free', 'kilo-auto/free'] },
|
|
15
|
-
opencode: { label: 'opencode', baseUrl: 'http://localhost:4790', keyPlaceholder: '(no key needed)', models: ['x-ai/grok-code-fast-1:optimized:free', 'openrouter/free', 'kilo-auto/free'] },
|
|
16
15
|
custom: { label: 'Custom (OpenAI-compat)', baseUrl: '', keyPlaceholder: 'API_KEY', models: [] },
|
|
17
16
|
};
|
|
18
17
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function mirrorFromSandbox(fsBase) {
|
|
2
|
+
try {
|
|
3
|
+
const listRes = await fetch(fsBase + '/__list');
|
|
4
|
+
if (!listRes.ok) return [];
|
|
5
|
+
const relFiles = await listRes.json();
|
|
6
|
+
const snap = window.__debug.idbSnapshot || (window.__debug.idbSnapshot = {});
|
|
7
|
+
const mirrored = [];
|
|
8
|
+
for (const rel of relFiles) {
|
|
9
|
+
const r = await fetch(fsBase + '/' + rel);
|
|
10
|
+
if (!r.ok) continue;
|
|
11
|
+
const content = await r.text();
|
|
12
|
+
if (snap[rel] !== content) { snap[rel] = content; mirrored.push(rel); }
|
|
13
|
+
}
|
|
14
|
+
if (mirrored.length) { window.__debug.idbPersist?.(); window.__debug.shell?.onPreviewWrite?.(); }
|
|
15
|
+
return mirrored;
|
|
16
|
+
} catch (e) { return []; }
|
|
17
|
+
}
|
package/docs/kilo-http-stream.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
import { mirrorFromSandbox } from './kilo-fs-mirror.js';
|
|
2
|
+
|
|
3
|
+
export async function* streamKiloHTTP({ url, model, messages, providerType, agent }) {
|
|
2
4
|
yield { type: 'start-step' };
|
|
3
5
|
const base = (url || 'http://localhost:4780').replace(/\/$/, '');
|
|
6
|
+
const fsBase = base.replace(/:\d+$/, ':4781');
|
|
4
7
|
const isOpencode = providerType === 'opencode';
|
|
5
8
|
const dbgKey = isOpencode ? 'opencode' : 'kilo';
|
|
6
9
|
let sessRes;
|
|
7
10
|
try { sessRes = await fetch(base + '/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); }
|
|
8
|
-
catch (e) { throw new Error(dbgKey + ' serve not reachable at ' + base + ' — start it with:
|
|
11
|
+
catch (e) { throw new Error(dbgKey + ' serve not reachable at ' + base + ' — start it with: node start-kilo.js --origin ' + location.origin); }
|
|
9
12
|
if (!sessRes.ok) throw new Error('/session ' + sessRes.status + ': ' + await sessRes.text());
|
|
10
13
|
const { id: sessionId } = await sessRes.json();
|
|
11
|
-
Object.assign(window.__debug = window.__debug || {}, { [dbgKey]: { sessionId, url: base, lastStatus: null } });
|
|
14
|
+
Object.assign(window.__debug = window.__debug || {}, { [dbgKey]: { sessionId, url: base, fsBase, lastStatus: null } });
|
|
12
15
|
|
|
13
16
|
const userText = messages.filter(m => m.role === 'user').map(m =>
|
|
14
17
|
typeof m.content === 'string' ? m.content : (m.content || []).filter(b => b.type === 'text').map(b => b.text).join('')
|
|
15
18
|
).join('\n');
|
|
16
19
|
|
|
17
20
|
const modelId = model || 'x-ai/grok-code-fast-1:optimized:free';
|
|
18
|
-
const
|
|
21
|
+
const codingIntent = /\b(write|create|make|build|generate|save|file|html|css|script|app|page|code)\b/i.test(userText);
|
|
22
|
+
const agentName = agent || (codingIntent ? 'build' : 'hermes-llm');
|
|
23
|
+
const body = { parts: [{ type: 'text', text: userText }], agent: agentName };
|
|
19
24
|
if (isOpencode) body.model = { providerID: 'kilo', modelID: modelId };
|
|
20
25
|
else { body.providerID = 'kilo'; body.modelID = modelId; }
|
|
21
26
|
|
|
@@ -23,6 +28,7 @@ export async function* streamKiloHTTP({ url, model, messages, providerType }) {
|
|
|
23
28
|
if (isOpencode) {
|
|
24
29
|
const es = new EventSource(base + '/event');
|
|
25
30
|
const textByPart = new Map();
|
|
31
|
+
const assistantMsgs = new Set();
|
|
26
32
|
let done = false;
|
|
27
33
|
const pending = [];
|
|
28
34
|
let resolveNext = null;
|
|
@@ -30,18 +36,19 @@ export async function* streamKiloHTTP({ url, model, messages, providerType }) {
|
|
|
30
36
|
es.onmessage = e => {
|
|
31
37
|
try {
|
|
32
38
|
const m = JSON.parse(e.data);
|
|
33
|
-
if (m.type === 'message.
|
|
39
|
+
if (m.type === 'message.updated') {
|
|
40
|
+
const info = m.properties?.info;
|
|
41
|
+
if (info?.sessionID === sessionId && info.role === 'assistant') {
|
|
42
|
+
assistantMsgs.add(info.id);
|
|
43
|
+
if (info.time?.completed) { done = true; if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } }
|
|
44
|
+
}
|
|
45
|
+
} else if (m.type === 'message.part.updated') {
|
|
34
46
|
const part = m.properties?.part;
|
|
35
|
-
if (part?.sessionID === sessionId && part.type === 'text') {
|
|
47
|
+
if (part?.sessionID === sessionId && part.type === 'text' && assistantMsgs.has(part.messageID)) {
|
|
36
48
|
const prior = textByPart.get(part.id) || '';
|
|
37
49
|
const txt = part.text || '';
|
|
38
50
|
if (txt.length > prior.length) { push({ type:'text-delta', textDelta: txt.slice(prior.length) }); textByPart.set(part.id, txt); }
|
|
39
51
|
}
|
|
40
|
-
} else if (m.type === 'message.updated') {
|
|
41
|
-
const info = m.properties?.info;
|
|
42
|
-
if (info?.sessionID === sessionId && info.role === 'assistant' && info.time?.completed) {
|
|
43
|
-
done = true; if (resolveNext) { const r = resolveNext; resolveNext = null; r(); }
|
|
44
|
-
}
|
|
45
52
|
}
|
|
46
53
|
} catch (_) {}
|
|
47
54
|
};
|
|
@@ -63,5 +70,7 @@ export async function* streamKiloHTTP({ url, model, messages, providerType }) {
|
|
|
63
70
|
window.__debug[dbgKey].lastResult = result;
|
|
64
71
|
for (const tp of (result.parts || []).filter(p => p.type === 'text')) { text += tp.text; yield { type: 'text-delta', textDelta: tp.text }; }
|
|
65
72
|
}
|
|
73
|
+
const mirrored = await mirrorFromSandbox(fsBase);
|
|
74
|
+
if (mirrored.length) { window.__debug[dbgKey].writes = mirrored; window.refreshPreview?.(); }
|
|
66
75
|
yield { type: 'finish-step', finishReason: 'stop' };
|
|
67
76
|
}
|
package/package.json
CHANGED
package/start-kilo.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { spawn } = require('child_process');
|
|
3
|
+
const http = require('http');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const os = require('os');
|
|
@@ -7,6 +8,7 @@ const args = process.argv.slice(2);
|
|
|
7
8
|
const get = (f, d) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : d; };
|
|
8
9
|
const kiloPort = get('--kilo-port', '4780');
|
|
9
10
|
const ocPort = get('--opencode-port', '4790');
|
|
11
|
+
const fsPort = get('--fs-port', '4781');
|
|
10
12
|
const origin = get('--origin', 'http://localhost:8787');
|
|
11
13
|
const sandbox = path.resolve(get('--sandbox', '.sandbox'));
|
|
12
14
|
fs.mkdirSync(sandbox, { recursive: true });
|
|
@@ -18,16 +20,40 @@ const ocBin = isWin ? process.env.USERPROFILE + '\\AppData\\Roaming\\npm\\node_m
|
|
|
18
20
|
|
|
19
21
|
const procs = [];
|
|
20
22
|
const launch = (name, bin, port) => {
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (args.includes('--no-' + name)) return;
|
|
24
|
+
if (!fs.existsSync(bin)) { console.log(`[${name}] skip (${bin} not found)`); return; }
|
|
25
|
+
const p = spawn(bin, ['serve', '--port', port, '--hostname', '127.0.0.1', '--cors', origin], { stdio: 'inherit', env: process.env, cwd: sandbox });
|
|
26
|
+
procs.push(p);
|
|
27
|
+
console.log(`[${name}] serve --port ${port} pid ${p.pid}`);
|
|
26
28
|
};
|
|
27
29
|
launch('kilo', kiloBin, kiloPort);
|
|
28
30
|
launch('opencode', ocBin, ocPort);
|
|
29
31
|
|
|
30
|
-
const
|
|
32
|
+
const cors = { 'Access-Control-Allow-Origin': origin, 'Access-Control-Allow-Methods': 'GET,OPTIONS', 'Access-Control-Allow-Headers': 'content-type' };
|
|
33
|
+
const srv = http.createServer((req, res) => {
|
|
34
|
+
if (req.method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }
|
|
35
|
+
const rel = decodeURIComponent(req.url.replace(/^\/+/, '').split('?')[0]);
|
|
36
|
+
if (rel === '__list') {
|
|
37
|
+
const out = [];
|
|
38
|
+
const walk = d => { for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
39
|
+
if (e.name.startsWith('.')) continue;
|
|
40
|
+
const full = path.join(d, e.name);
|
|
41
|
+
if (e.isDirectory()) walk(full);
|
|
42
|
+
else out.push(path.relative(sandbox, full).replace(/\\/g, '/'));
|
|
43
|
+
}};
|
|
44
|
+
try { walk(sandbox); } catch (e) {}
|
|
45
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' }); res.end(JSON.stringify(out)); return;
|
|
46
|
+
}
|
|
47
|
+
const full = path.resolve(path.join(sandbox, rel));
|
|
48
|
+
if (!full.startsWith(sandbox)) { res.writeHead(403, cors); res.end(); return; }
|
|
49
|
+
fs.readFile(full, (err, data) => {
|
|
50
|
+
if (err) { res.writeHead(404, cors); res.end(); return; }
|
|
51
|
+
const ct = { '.html':'text/html','.js':'application/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.md':'text/plain','.txt':'text/plain' }[path.extname(rel)] || 'application/octet-stream';
|
|
52
|
+
res.writeHead(200, { ...cors, 'Content-Type': ct }); res.end(data);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
srv.listen(fsPort, '127.0.0.1', () => console.log(`[fs-bridge] sandbox=${sandbox} serving http://127.0.0.1:${fsPort}`));
|
|
56
|
+
|
|
57
|
+
const stop = () => { try { srv.close(); } catch (e) {} for (const p of procs) { try { p.kill(); } catch (e) {} } process.exit(0); };
|
|
31
58
|
process.on('SIGINT', stop);
|
|
32
59
|
process.on('SIGTERM', stop);
|
|
33
|
-
Promise.all(procs.map(p => new Promise(r => p.on('exit', r)))).then(() => process.exit(0));
|