thebird 1.2.31 → 1.2.32

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]
@@ -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
  );