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 +7 -0
- package/README.md +1 -1
- package/docs/agent-chat.js +43 -47
- package/docs/app.js +1 -14
- package/docs/vendor/thebird-browser.js +18437 -0
- package/package.json +1 -1
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
|
|
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
|
|
package/docs/agent-chat.js
CHANGED
|
@@ -1,55 +1,51 @@
|
|
|
1
|
-
|
|
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
|
|
8
|
-
read_file:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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,
|
|
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
|
);
|