thebird 1.2.82 → 1.2.83
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/kilo-http-stream.js +41 -16
- package/index.js +2 -1
- package/lib/providers/acp.js +88 -0
- package/package.json +1 -1
package/docs/kilo-http-stream.js
CHANGED
|
@@ -9,18 +9,36 @@ export async function* streamKiloHTTP({ url, model, messages }) {
|
|
|
9
9
|
catch (e) { throw new Error('kilo serve not reachable at ' + base + ' — start it with: node start-kilo.js --origin ' + location.origin); }
|
|
10
10
|
if (!sessRes.ok) throw new Error('kilo /session ' + sessRes.status + ': ' + await sessRes.text());
|
|
11
11
|
const { id: sessionId } = await sessRes.json();
|
|
12
|
-
Object.assign(window.__debug = window.__debug || {}, { kilo: { sessionId, url: base, fsBase, writes: [], lastStatus: null } });
|
|
12
|
+
Object.assign(window.__debug = window.__debug || {}, { kilo: { sessionId, url: base, fsBase, writes: [], toolCalls: [], lastStatus: null } });
|
|
13
|
+
|
|
14
|
+
const queue = [];
|
|
15
|
+
let resolveNext = null;
|
|
16
|
+
let streamEnded = false;
|
|
17
|
+
const push = ev => { queue.push(ev); if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } };
|
|
18
|
+
const textSeen = new Set();
|
|
19
|
+
const toolState = new Map();
|
|
13
20
|
|
|
14
21
|
const es = new EventSource(base + '/event');
|
|
15
|
-
const pendingWrites = new Set();
|
|
16
22
|
es.onmessage = (ev) => {
|
|
17
23
|
try {
|
|
18
24
|
const msg = JSON.parse(ev.data);
|
|
19
|
-
if (msg.type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
if (msg.type !== 'message.part.updated') return;
|
|
26
|
+
const part = msg.properties?.part;
|
|
27
|
+
if (!part) return;
|
|
28
|
+
if (part.type === 'text' && part.messageID && !textSeen.has(part.id)) {
|
|
29
|
+
const prior = toolState.get('__text_' + part.id) || '';
|
|
30
|
+
const txt = part.text || '';
|
|
31
|
+
if (txt.length > prior.length) { push({ type: 'text-delta', textDelta: txt.slice(prior.length) }); toolState.set('__text_' + part.id, txt); }
|
|
32
|
+
} else if (part.type === 'tool') {
|
|
33
|
+
const cid = part.callID;
|
|
34
|
+
const st = part.state?.status;
|
|
35
|
+
if (st === 'running' && !toolState.has(cid)) {
|
|
36
|
+
toolState.set(cid, { name: part.tool, args: part.state.input || {} });
|
|
37
|
+
push({ type: 'tool-call', toolCallId: cid, toolName: part.tool, args: part.state.input || {} });
|
|
38
|
+
window.__debug.kilo.toolCalls.push({ id: cid, name: part.tool, args: part.state.input || {} });
|
|
39
|
+
} else if (st === 'completed' && toolState.has(cid) && !toolState.get(cid).completed) {
|
|
40
|
+
toolState.get(cid).completed = true;
|
|
41
|
+
push({ type: 'tool-result', toolCallId: cid, toolName: part.tool, args: part.state.input || {}, result: part.state.output || '' });
|
|
24
42
|
}
|
|
25
43
|
}
|
|
26
44
|
} catch (_) {}
|
|
@@ -31,17 +49,24 @@ export async function* streamKiloHTTP({ url, model, messages }) {
|
|
|
31
49
|
).join('\n');
|
|
32
50
|
|
|
33
51
|
const body = { parts: [{ type: 'text', text: userText }], providerID: 'kilo', modelID: model || 'x-ai/grok-code-fast-1:optimized:free' };
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
const msgPromise = fetch(base + '/session/' + sessionId + '/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(async r => {
|
|
53
|
+
window.__debug.kilo.lastStatus = r.status;
|
|
54
|
+
const json = await r.json();
|
|
55
|
+
window.__debug.kilo.lastResult = json;
|
|
56
|
+
streamEnded = true;
|
|
57
|
+
if (resolveNext) { const x = resolveNext; resolveNext = null; x(); }
|
|
58
|
+
return json;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
while (!streamEnded || queue.length) {
|
|
62
|
+
if (queue.length) { yield queue.shift(); continue; }
|
|
63
|
+
await new Promise(r => { resolveNext = r; });
|
|
64
|
+
}
|
|
65
|
+
const result = await msgPromise;
|
|
39
66
|
es.close();
|
|
40
|
-
const
|
|
67
|
+
const touched = [...toolState.values()].filter(v => v.completed && (v.name === 'write' || v.name === 'edit')).map(v => v.args.filePath).filter(Boolean);
|
|
68
|
+
const mirrored = await mirrorFromSandbox(fsBase, touched);
|
|
41
69
|
window.__debug.kilo.writes = mirrored;
|
|
42
70
|
if (mirrored.length) window.refreshPreview?.();
|
|
43
|
-
const textParts = (result.parts || []).filter(p => p.type === 'text');
|
|
44
|
-
for (const tp of textParts) yield { type: 'text-delta', textDelta: tp.text };
|
|
45
|
-
if (mirrored.length) yield { type: 'text-delta', textDelta: '\n\n[mirrored to preview: ' + mirrored.join(', ') + ']' };
|
|
46
71
|
yield { type: 'finish-step', finishReason: result.info?.finish || 'stop' };
|
|
47
72
|
}
|
package/index.js
CHANGED
|
@@ -99,5 +99,6 @@ const { streamRouter, generateRouter, createRouter } = require('./lib/router-str
|
|
|
99
99
|
const { cloudGenerate, streamCloud, cloudStream } = require('./lib/cloud-generate');
|
|
100
100
|
const { ensureAuth, login: oauthLogin } = require('./lib/oauth');
|
|
101
101
|
const { BridgeError, AuthError, RateLimitError, TimeoutError, ContextWindowError, ContentPolicyError, ProviderError, classifyError, redactKeys } = require('./lib/errors');
|
|
102
|
+
const { streamACP, generateACP } = require('./lib/providers/acp');
|
|
102
103
|
|
|
103
|
-
module.exports = { streamGemini, createFullStream, generateGemini, streamRouter, generateRouter, createRouter, convertMessages, convertTools, cleanSchema, GeminiError, BridgeError, AuthError, RateLimitError, TimeoutError, ContextWindowError, ContentPolicyError, ProviderError, classifyError, redactKeys, cloudGenerate, streamCloud, cloudStream, ensureAuth, oauthLogin };
|
|
104
|
+
module.exports = { streamGemini, createFullStream, generateGemini, streamRouter, generateRouter, createRouter, convertMessages, convertTools, cleanSchema, GeminiError, BridgeError, AuthError, RateLimitError, TimeoutError, ContextWindowError, ContentPolicyError, ProviderError, classifyError, redactKeys, cloudGenerate, streamCloud, cloudStream, ensureAuth, oauthLogin, streamACP, generateACP };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const { BridgeError } = require('../errors');
|
|
2
|
+
|
|
3
|
+
async function postJson(url, body) {
|
|
4
|
+
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
5
|
+
if (!res.ok) { const t = await res.text(); throw new BridgeError(t, { status: res.status, retryable: res.status === 429 || res.status >= 500 }); }
|
|
6
|
+
return res.json();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function subscribeSSE(url, onEvent) {
|
|
10
|
+
const ctrl = new AbortController();
|
|
11
|
+
(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
14
|
+
if (!res.ok || !res.body) return;
|
|
15
|
+
const reader = res.body.getReader();
|
|
16
|
+
const dec = new TextDecoder();
|
|
17
|
+
let buf = '';
|
|
18
|
+
while (true) {
|
|
19
|
+
const { done, value } = await reader.read();
|
|
20
|
+
if (done) return;
|
|
21
|
+
buf += dec.decode(value, { stream: true });
|
|
22
|
+
let i;
|
|
23
|
+
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
24
|
+
const chunk = buf.slice(0, i); buf = buf.slice(i + 2);
|
|
25
|
+
const line = chunk.split('\n').find(l => l.startsWith('data:'));
|
|
26
|
+
if (!line) continue;
|
|
27
|
+
try { onEvent(JSON.parse(line.slice(5))); } catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (e) { if (e.name !== 'AbortError') throw e; }
|
|
31
|
+
})();
|
|
32
|
+
return () => ctrl.abort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toUserText(messages) {
|
|
36
|
+
return messages.filter(m => m.role === 'user').map(m =>
|
|
37
|
+
typeof m.content === 'string' ? m.content : (m.content || []).filter(b => b.type === 'text').map(b => b.text).join('')
|
|
38
|
+
).join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function* streamACP({ url, model, messages, onStepFinish }) {
|
|
42
|
+
yield { type: 'start-step' };
|
|
43
|
+
const base = (url || 'http://localhost:4780').replace(/\/$/, '');
|
|
44
|
+
const { id: sessionId } = await postJson(base + '/session', {});
|
|
45
|
+
const queue = []; let resolveNext = null; let done = false;
|
|
46
|
+
const push = ev => { queue.push(ev); if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } };
|
|
47
|
+
const textSeen = new Map();
|
|
48
|
+
const toolState = new Map();
|
|
49
|
+
const unsubscribe = subscribeSSE(base + '/event', (msg) => {
|
|
50
|
+
if (msg.type !== 'message.part.updated') return;
|
|
51
|
+
const part = msg.properties?.part;
|
|
52
|
+
if (!part) return;
|
|
53
|
+
if (part.type === 'text' && part.messageID) {
|
|
54
|
+
const prior = textSeen.get(part.id) || '';
|
|
55
|
+
const txt = part.text || '';
|
|
56
|
+
if (txt.length > prior.length) { push({ type: 'text-delta', textDelta: txt.slice(prior.length) }); textSeen.set(part.id, txt); }
|
|
57
|
+
} else if (part.type === 'tool') {
|
|
58
|
+
const cid = part.callID; const st = part.state?.status;
|
|
59
|
+
if (st === 'running' && !toolState.has(cid)) { toolState.set(cid, { name: part.tool, args: part.state.input || {} }); push({ type: 'tool-call', toolCallId: cid, toolName: part.tool, args: part.state.input || {} }); }
|
|
60
|
+
else if (st === 'completed' && toolState.has(cid) && !toolState.get(cid).completed) { toolState.get(cid).completed = true; push({ type: 'tool-result', toolCallId: cid, toolName: part.tool, args: part.state.input || {}, result: part.state.output || '' }); }
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const promptPromise = postJson(base + '/session/' + sessionId + '/message', {
|
|
64
|
+
parts: [{ type: 'text', text: toUserText(messages) }],
|
|
65
|
+
providerID: 'kilo',
|
|
66
|
+
modelID: model || 'x-ai/grok-code-fast-1:optimized:free',
|
|
67
|
+
}).finally(() => { done = true; if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } });
|
|
68
|
+
while (!done || queue.length) {
|
|
69
|
+
if (queue.length) { yield queue.shift(); continue; }
|
|
70
|
+
await new Promise(r => { resolveNext = r; });
|
|
71
|
+
}
|
|
72
|
+
const result = await promptPromise;
|
|
73
|
+
unsubscribe();
|
|
74
|
+
yield { type: 'finish-step', finishReason: result.info?.finish || 'stop' };
|
|
75
|
+
if (onStepFinish) await onStepFinish();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function generateACP(opts) {
|
|
79
|
+
let text = '';
|
|
80
|
+
const toolCalls = [];
|
|
81
|
+
for await (const ev of streamACP(opts)) {
|
|
82
|
+
if (ev.type === 'text-delta') text += ev.textDelta;
|
|
83
|
+
else if (ev.type === 'tool-call') toolCalls.push({ id: ev.toolCallId, name: ev.toolName, args: ev.args });
|
|
84
|
+
}
|
|
85
|
+
return { text, toolCalls };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { streamACP, generateACP };
|
package/package.json
CHANGED