thebird 1.2.31 → 1.2.33

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [Unreleased - browser-sdk]
2
+
3
+ ### Added
4
+ - `docs/vendor/thebird-browser.js`: thebird `streamGemini`/`generateGemini` bundled for browser via esbuild (712KB, includes @google/genai browser build)
5
+ - `docs/agent-chat.js`: rewritten to use thebird `streamGemini` directly; TOOLS map with `read_file`, `write_file`, `run_command` dispatch to `window.__debug.container` (WebContainer); `window.__debug.agent` live state; removes raw Gemini REST API dependency
6
+ - `docs/app.js`: removed `convertMessages` (now handled internally by thebird); passes raw Anthropic-format messages to `agentGenerate`
7
+
1
8
  # Changelog
2
9
 
3
10
  ## [Unreleased]
package/README.md CHANGED
@@ -182,7 +182,7 @@ Messages follow the Anthropic SDK format. All image block variants are supported
182
182
 
183
183
  Live at **[anentrypoint.github.io/thebird](https://anentrypoint.github.io/thebird/)**
184
184
 
185
- - **Chat tab** — Agentic Gemini chat with tool use: `read_file`, `write_file`, `run_command` dispatch to the WebContainer FS and shell. Gemini API key stored in localStorage.
185
+ - **Chat tab** — Agentic chat powered by thebird `streamGemini` running in-browser (bundled in `docs/vendor/thebird-browser.js`). Tool use: `read_file`, `write_file`, `run_command` dispatch to WebContainer FS and shell. No proxy server required. Gemini API key stored in localStorage.
186
186
  - **Terminal tab** — WebContainer (in-browser Node.js) booting thebird's full stack: `npm install`, `node server.js` (Anthropic→Gemini proxy on port 3000), then a `jsh` shell. Run Agent button validates the agent loop from the terminal.
187
187
  - **Preview tab** — iframe pointed at the WebContainer's HTTP server, live-updated when the server starts
188
188
 
@@ -1,55 +1,51 @@
1
- const TOOLS = [
2
- { name: 'read_file', description: 'Read a file from the filesystem', parameters: { type: 'OBJECT', properties: { path: { type: 'STRING' } }, required: ['path'] } },
3
- { name: 'write_file', description: 'Write content to a file', parameters: { type: 'OBJECT', properties: { path: { type: 'STRING' }, content: { type: 'STRING' } }, required: ['path', 'content'] } },
4
- { name: 'run_command', description: 'Run a shell command', parameters: { type: 'OBJECT', properties: { command: { type: 'STRING' } }, required: ['command'] } },
5
- ];
1
+ import { streamGemini } from './vendor/thebird-browser.js';
6
2
 
7
- const toolHandlers = {
8
- read_file: async ({ path }) => {
9
- const c = window.__debug.container;
10
- if (!c) throw new Error('container not ready');
11
- return await c.fs.readFile(path, 'utf-8');
3
+ const TOOLS = {
4
+ read_file: {
5
+ description: 'Read a file from the filesystem',
6
+ parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
7
+ execute: async ({ path }) => {
8
+ const c = window.__debug.container;
9
+ if (!c) throw new Error('container not ready');
10
+ return await c.fs.readFile(path, 'utf-8');
11
+ },
12
12
  },
13
- write_file: async ({ path, content }) => {
14
- const c = window.__debug.container;
15
- if (!c) throw new Error('container not ready');
16
- await c.fs.writeFile(path, content);
17
- return 'written: ' + path;
13
+ write_file: {
14
+ description: 'Write content to a file',
15
+ parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] },
16
+ execute: async ({ path, content }) => {
17
+ const c = window.__debug.container;
18
+ if (!c) throw new Error('container not ready');
19
+ await c.fs.writeFile(path, content);
20
+ return 'written: ' + path;
21
+ },
18
22
  },
19
- run_command: async ({ command }) => {
20
- const c = window.__debug.container;
21
- if (!c) throw new Error('container not ready');
22
- const proc = await c.spawn('sh', ['-c', command]);
23
- let out = '';
24
- await proc.output.pipeTo(new WritableStream({ write: d => { out += d; } }));
25
- return out || '(no output)';
23
+ run_command: {
24
+ description: 'Run a shell command',
25
+ parameters: { type: 'object', properties: { command: { type: 'string' }, cwd: { type: 'string' } }, required: ['command'] },
26
+ execute: async ({ command, cwd }) => {
27
+ const c = window.__debug.container;
28
+ if (!c) throw new Error('container not ready');
29
+ const proc = await c.spawn('sh', ['-c', command], cwd ? { cwd } : undefined);
30
+ let out = '';
31
+ await proc.output.pipeTo(new WritableStream({ write: d => { out += d; } }));
32
+ await proc.exit;
33
+ return out || '(no output)';
34
+ },
26
35
  },
27
36
  };
28
37
 
29
- const BASE = 'https://generativelanguage.googleapis.com/v1beta';
30
-
31
- export async function agentGenerate(apiKey, model, contents, onChunk, onTool) {
32
- while (true) {
33
- const res = await fetch(`${BASE}/models/${model}:generateContent?key=${apiKey}`, {
34
- method: 'POST',
35
- headers: { 'Content-Type': 'application/json' },
36
- body: JSON.stringify({ contents, tools: [{ functionDeclarations: TOOLS }], generationConfig: { maxOutputTokens: 8192 } }),
37
- });
38
- if (!res.ok) throw new Error('Generate API ' + res.status + ': ' + await res.text());
39
- const data = await res.json();
40
- const parts = data.candidates?.[0]?.content?.parts || [];
41
- const finish = data.candidates?.[0]?.finishReason;
42
- for (const p of parts) if (p.text) onChunk(p.text);
43
- const calls = parts.filter(p => p.functionCall);
44
- if (finish === 'STOP' || calls.length === 0) break;
45
- const toolResults = await Promise.all(calls.map(async p => {
46
- const { name, args } = p.functionCall;
47
- onTool(name, args);
48
- let output;
49
- try { output = String(await toolHandlers[name](args)); }
50
- catch (e) { output = 'error: ' + e.message; }
51
- return { functionResponse: { name, response: { output } } };
52
- }));
53
- contents = [...contents, { role: 'model', parts }, { role: 'user', parts: toolResults }];
38
+ export async function agentGenerate(apiKey, model, messages, onChunk, onTool) {
39
+ Object.assign(window.__debug = window.__debug || {}, {
40
+ agent: { model, active: true },
41
+ });
42
+ try {
43
+ for await (const ev of streamGemini({ model, messages, tools: TOOLS, apiKey, maxOutputTokens: 8192 }).fullStream) {
44
+ if (ev.type === 'text-delta') onChunk(ev.textDelta);
45
+ else if (ev.type === 'tool-call') onTool(ev.toolName, ev.args);
46
+ else if (ev.type === 'error') throw ev.error;
47
+ }
48
+ } finally {
49
+ window.__debug.agent.active = false;
54
50
  }
55
51
  }
package/docs/app.js CHANGED
@@ -14,19 +14,6 @@ async function fetchModels(apiKey) {
14
14
  .map(m => ({ id: m.name.replace('models/', ''), label: m.displayName || m.name }));
15
15
  }
16
16
 
17
- function convertMessages(messages) {
18
- const out = [];
19
- for (const m of messages) {
20
- const role = m.role === 'assistant' ? 'model' : 'user';
21
- if (typeof m.content === 'string') {
22
- if (m.content) out.push({ role, parts: [{ text: m.content }] });
23
- } else if (Array.isArray(m.content)) {
24
- const parts = m.content.flatMap(b => b.type === 'text' && b.text ? [{ text: b.text }] : []);
25
- if (parts.length) out.push({ role, parts });
26
- }
27
- }
28
- return out;
29
- }
30
17
 
31
18
  class BirdChat extends HTMLElement {
32
19
  constructor() {
@@ -132,7 +119,7 @@ class BirdChat extends HTMLElement {
132
119
  wrap.appendChild(cursor);
133
120
  const list = this.querySelector('#msg-list');
134
121
  if (list) list.appendChild(wrap);
135
- await agentGenerate(apiKey, model, convertMessages(messages),
122
+ await agentGenerate(apiKey, model, messages,
136
123
  chunk => { full += chunk; streamEl.textContent = full; const l = this.querySelector('#msg-list'); if (l) l.scrollTop = l.scrollHeight; },
137
124
  (name, args) => { full += `\n[tool: ${name}(${JSON.stringify(args)})]\n`; streamEl.textContent = full; }
138
125
  );