thebird 1.2.81 → 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/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <body>
12
12
  <div style="display:flex;flex-direction:column;height:100%">
13
13
  <div class="tui-header"><pre class="logo" style="margin:0;padding:2px 1ch"> ▀█▀ █░█ █▀▀ █▄▄ █ █▀█ █▀▄ <span class="ver">v1.2</span>
14
- █ █▀█ ██▄ █▄█ █ █▀▄ █▄▀ <span style="color:#1a9a1a">anthropic gemini/openai bridge</span></pre></div>
14
+ █ █▀█ ██▄ █▄█ █ █▀▄ █▄▀ <span style="color:#1a9a1a">instant posix js</span></pre></div>
15
15
  <div class="tui-tabs">
16
16
  <button id="tab-chat" class="tui-tab active" onclick="switchTab('chat')">Chat</button>
17
17
  <button id="tab-term" class="tui-tab" onclick="switchTab('term')">Terminal</button>
@@ -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 === 'message.part.updated') {
20
- const part = msg.properties?.part;
21
- if (part?.type === 'tool' && part.state?.status === 'completed' && (part.tool === 'write' || part.tool === 'edit')) {
22
- const abs = part.state.input?.filePath;
23
- if (abs) pendingWrites.add(abs);
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 msgRes = await fetch(base + '/session/' + sessionId + '/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
35
- window.__debug.kilo.lastStatus = msgRes.status;
36
- if (!msgRes.ok) { es.close(); throw new Error('kilo message ' + msgRes.status + ': ' + await msgRes.text()); }
37
- const result = await msgRes.json();
38
- window.__debug.kilo.lastResult = result;
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 mirrored = await mirrorFromSandbox(fsBase, [...pendingWrites]);
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/docs/shell.js CHANGED
@@ -35,6 +35,7 @@ export function createShell({ term, onPreviewWrite }) {
35
35
  const toKey = p => p.replace(/^\//, '');
36
36
  const snap = () => window.__debug.idbSnapshot || {};
37
37
 
38
+ let expandTokens, captureRun;
38
39
  const BUILTINS = makeBuiltins(ctx, actor, invokeBuiltin);
39
40
  ctx.builtinsRef = BUILTINS;
40
41
  const _exp = makeExpander(ctx, l => captureRun(l), t => parseRedirect(t));
@@ -55,8 +56,6 @@ export function createShell({ term, onPreviewWrite }) {
55
56
  const runNode = makeNodeRunner(ctx, actor);
56
57
  const runNpmResult = makeNpmResultRunner(ctx, line => run(line));
57
58
 
58
- let expandTokens, captureRun;
59
-
60
59
  async function captureFn(fn) {
61
60
  let out = ''; const orig = term.write.bind(term); term.write = s => { out += s; };
62
61
  try { await fn(); } finally { term.write = orig; }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.81",
3
+ "version": "1.2.83",
4
4
  "description": "Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini",
5
5
  "scripts": {
6
6
  "start": "node serve.js"