thebird 1.2.36 → 1.2.38
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 +6 -0
- package/docs/agent-chat.js +12 -4
- package/docs/app.js +74 -19
- package/docs/vendor/thebird-browser.js +142 -8
- package/index.js +2 -2
- package/package.json +1 -1
- package/thebird-browser-entry-esm.js +4 -0
- package/thebird-browser-entry.js +196 -0
package/CHANGELOG.md
CHANGED
package/docs/agent-chat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { streamGemini } from './vendor/thebird-browser.js';
|
|
1
|
+
import { streamGemini, streamOpenAI } from './vendor/thebird-browser.js';
|
|
2
2
|
|
|
3
3
|
function idbRead(path) {
|
|
4
4
|
const snap = window.__debug.idbSnapshot;
|
|
@@ -85,12 +85,20 @@ const TOOLS = {
|
|
|
85
85
|
},
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
function buildStream(provider) {
|
|
89
|
+
if (provider.type === 'gemini') {
|
|
90
|
+
return streamGemini({ model: provider.model, messages: provider.messages, tools: TOOLS, apiKey: provider.apiKey, maxOutputTokens: 8192 }).fullStream;
|
|
91
|
+
}
|
|
92
|
+
const url = (provider.baseUrl || '').replace(/\/$/, '') + '/chat/completions';
|
|
93
|
+
return streamOpenAI({ url, apiKey: provider.apiKey, messages: provider.messages, model: provider.model, tools: TOOLS, maxOutputTokens: 8192 });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function agentGenerate(provider, messages, onChunk, onTool) {
|
|
89
97
|
Object.assign(window.__debug = window.__debug || {}, {
|
|
90
|
-
agent: { model, active: true, lastTool: null },
|
|
98
|
+
agent: { provider: provider.type, model: provider.model, active: true, lastTool: null },
|
|
91
99
|
});
|
|
92
100
|
try {
|
|
93
|
-
for await (const ev of
|
|
101
|
+
for await (const ev of buildStream({ ...provider, messages })) {
|
|
94
102
|
if (ev.type === 'text-delta') onChunk(ev.textDelta);
|
|
95
103
|
else if (ev.type === 'tool-call') {
|
|
96
104
|
window.__debug.agent.lastTool = { name: ev.toolName, args: ev.args };
|
package/docs/app.js
CHANGED
|
@@ -3,10 +3,18 @@ import { agentGenerate } from './agent-chat.js';
|
|
|
3
3
|
|
|
4
4
|
const html = htm.bind(createElement);
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const PROVIDERS = {
|
|
7
|
+
gemini: { label: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta', keyPlaceholder: 'GEMINI_API_KEY', models: [] },
|
|
8
|
+
openai: { label: 'OpenAI', baseUrl: 'https://api.openai.com/v1', keyPlaceholder: 'OPENAI_API_KEY', models: ['gpt-4.1', 'gpt-4o', 'gpt-4o-mini', 'o3', 'o4-mini'] },
|
|
9
|
+
xai: { label: 'xAI Grok', baseUrl: 'https://api.x.ai/v1', keyPlaceholder: 'XAI_API_KEY', models: ['grok-3', 'grok-3-mini', 'grok-3-fast'] },
|
|
10
|
+
groq: { label: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', keyPlaceholder: 'GROQ_API_KEY', models: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768'] },
|
|
11
|
+
mistral: { label: 'Mistral', baseUrl: 'https://api.mistral.ai/v1', keyPlaceholder: 'MISTRAL_API_KEY', models: ['mistral-large-latest', 'mistral-small-latest', 'codestral-latest'] },
|
|
12
|
+
deepseek: { label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', keyPlaceholder: 'DEEPSEEK_API_KEY', models: ['deepseek-chat', 'deepseek-reasoner'] },
|
|
13
|
+
custom: { label: 'Custom (OpenAI-compat)', baseUrl: '', keyPlaceholder: 'API_KEY', models: [] },
|
|
14
|
+
};
|
|
7
15
|
|
|
8
|
-
async function
|
|
9
|
-
const res = await fetch(
|
|
16
|
+
async function fetchGeminiModels(apiKey) {
|
|
17
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`);
|
|
10
18
|
if (!res.ok) throw new Error(`Models API ${res.status}`);
|
|
11
19
|
const { models = [] } = await res.json();
|
|
12
20
|
return models
|
|
@@ -14,13 +22,35 @@ async function fetchModels(apiKey) {
|
|
|
14
22
|
.map(m => ({ id: m.name.replace('models/', ''), label: m.displayName || m.name }));
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
async function fetchOpenAIModels(baseUrl, apiKey) {
|
|
26
|
+
const url = baseUrl.replace(/\/$/, '') + '/models';
|
|
27
|
+
const res = await fetch(url, { headers: { 'Authorization': `Bearer ${apiKey}` } });
|
|
28
|
+
if (!res.ok) throw new Error(`Models API ${res.status}`);
|
|
29
|
+
const { data = [] } = await res.json();
|
|
30
|
+
return data.map(m => ({ id: m.id, label: m.id })).sort((a, b) => a.id.localeCompare(b.id));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchModels(providerType, baseUrl, apiKey) {
|
|
34
|
+
if (providerType === 'gemini') return fetchGeminiModels(apiKey);
|
|
35
|
+
const staticModels = PROVIDERS[providerType]?.models || [];
|
|
36
|
+
try {
|
|
37
|
+
return await fetchOpenAIModels(baseUrl, apiKey);
|
|
38
|
+
} catch {
|
|
39
|
+
return staticModels.map(id => ({ id, label: id }));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
17
42
|
|
|
18
43
|
class BirdChat extends HTMLElement {
|
|
19
44
|
constructor() {
|
|
20
45
|
super();
|
|
46
|
+
const savedProvider = localStorage.getItem('provider_type') || 'gemini';
|
|
47
|
+
const savedBaseUrl = localStorage.getItem('provider_base_url') || PROVIDERS[savedProvider]?.baseUrl || '';
|
|
21
48
|
this.state = {
|
|
22
|
-
messages: [], streaming: false,
|
|
23
|
-
|
|
49
|
+
messages: [], streaming: false,
|
|
50
|
+
providerType: savedProvider,
|
|
51
|
+
baseUrl: savedBaseUrl,
|
|
52
|
+
model: localStorage.getItem('provider_model') || (savedProvider === 'gemini' ? 'gemini-2.5-flash' : (PROVIDERS[savedProvider]?.models[0] || '')),
|
|
53
|
+
apiKey: localStorage.getItem('provider_api_key') || '',
|
|
24
54
|
models: [], modelsLoading: false, status: '', streamingText: '',
|
|
25
55
|
};
|
|
26
56
|
const self = this;
|
|
@@ -33,15 +63,16 @@ class BirdChat extends HTMLElement {
|
|
|
33
63
|
|
|
34
64
|
connectedCallback() {
|
|
35
65
|
this.render();
|
|
36
|
-
if (this.state.apiKey) this.loadModels(
|
|
66
|
+
if (this.state.apiKey) this.loadModels();
|
|
37
67
|
}
|
|
38
68
|
|
|
39
69
|
setState(patch) { Object.assign(this.state, patch); this.render(); }
|
|
40
70
|
|
|
41
|
-
async loadModels(
|
|
71
|
+
async loadModels() {
|
|
72
|
+
const { providerType, baseUrl, apiKey } = this.state;
|
|
42
73
|
this.setState({ modelsLoading: true, status: '' });
|
|
43
74
|
try {
|
|
44
|
-
const models = await fetchModels(apiKey);
|
|
75
|
+
const models = await fetchModels(providerType, baseUrl, apiKey);
|
|
45
76
|
const current = this.state.model;
|
|
46
77
|
const model = models.find(m => m.id === current) ? current : (models[0]?.id || current);
|
|
47
78
|
this.setState({ models, model, modelsLoading: false });
|
|
@@ -50,25 +81,48 @@ class BirdChat extends HTMLElement {
|
|
|
50
81
|
}
|
|
51
82
|
}
|
|
52
83
|
|
|
84
|
+
setProvider(type) {
|
|
85
|
+
const def = PROVIDERS[type] || {};
|
|
86
|
+
const baseUrl = type === 'custom' ? '' : (def.baseUrl || '');
|
|
87
|
+
const model = def.models?.[0] || '';
|
|
88
|
+
localStorage.setItem('provider_type', type);
|
|
89
|
+
localStorage.setItem('provider_base_url', baseUrl);
|
|
90
|
+
localStorage.setItem('provider_model', model);
|
|
91
|
+
this.setState({ providerType: type, baseUrl, model, models: [], apiKey: localStorage.getItem('provider_api_key') || '' });
|
|
92
|
+
}
|
|
93
|
+
|
|
53
94
|
render() {
|
|
54
|
-
const { messages, streaming, model, apiKey, models, modelsLoading, status,
|
|
55
|
-
const
|
|
95
|
+
const { messages, streaming, model, apiKey, models, modelsLoading, status, providerType, baseUrl } = this.state;
|
|
96
|
+
const provDef = PROVIDERS[providerType] || PROVIDERS.custom;
|
|
97
|
+
const opts = (models.length === 0 ? (provDef.models.length ? provDef.models.map(id => ({ id, label: id })) : [{ id: model, label: model }]) : models)
|
|
56
98
|
.map(m => html`<option value=${m.id} selected=${m.id === model}>${m.label}</option>`);
|
|
99
|
+
const provOpts = Object.entries(PROVIDERS).map(([id, p]) =>
|
|
100
|
+
html`<option value=${id} selected=${id === providerType}>${p.label}</option>`);
|
|
57
101
|
|
|
58
102
|
applyDiff(this, html`
|
|
59
103
|
<div class="flex flex-col h-full">
|
|
60
104
|
<header class="navbar bg-base-200 border-b border-base-300 gap-2 flex-wrap px-4 py-2">
|
|
61
105
|
<span class="text-primary font-bold text-lg mr-2">🐦 thebird</span>
|
|
62
|
-
<span class="text-base-content/50 text-xs hidden sm:inline">Anthropic SDK format → Gemini API</span>
|
|
63
106
|
<div class="flex gap-2 flex-1 min-w-0 items-center flex-wrap">
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
107
|
+
<select class="select select-sm select-bordered"
|
|
108
|
+
onchange=${e => this.setProvider(e.target.value)}>${provOpts}</select>
|
|
109
|
+
${providerType === 'custom' ? html`
|
|
110
|
+
<input type="text" class="input input-sm input-bordered flex-1 min-w-[160px]"
|
|
111
|
+
placeholder="https://your-endpoint/v1" value=${baseUrl}
|
|
112
|
+
onchange=${e => { localStorage.setItem('provider_base_url', e.target.value); this.setState({ baseUrl: e.target.value }); }} />` : ''}
|
|
113
|
+
<input id="api-key-input" type="password" class="input input-sm input-bordered flex-1 min-w-[140px]"
|
|
114
|
+
placeholder=${provDef.keyPlaceholder} value=${apiKey}
|
|
115
|
+
onchange=${e => {
|
|
116
|
+
const v = e.target.value.trim();
|
|
117
|
+
localStorage.setItem('provider_api_key', v);
|
|
118
|
+
this.setState({ apiKey: v });
|
|
119
|
+
if (v) this.loadModels();
|
|
120
|
+
}} />
|
|
67
121
|
<div class="relative">
|
|
68
122
|
${modelsLoading
|
|
69
123
|
? html`<span class="loading loading-spinner loading-sm text-primary"></span>`
|
|
70
|
-
: html`<select class="select select-sm select-bordered" value=${model}
|
|
71
|
-
onchange=${e => this.setState({ model: e.target.value })}>${opts}</select>`}
|
|
124
|
+
: html`<select class="select select-sm select-bordered" value=${model}
|
|
125
|
+
onchange=${e => { localStorage.setItem('provider_model', e.target.value); this.setState({ model: e.target.value }); }}>${opts}</select>`}
|
|
72
126
|
</div>
|
|
73
127
|
<button class="btn btn-sm btn-ghost" onclick=${() => this.setState({ messages: [], status: '' })}>Clear</button>
|
|
74
128
|
</div>
|
|
@@ -100,12 +154,13 @@ class BirdChat extends HTMLElement {
|
|
|
100
154
|
const input = this.querySelector('#chat-input');
|
|
101
155
|
const text = input?.value.trim();
|
|
102
156
|
if (!text || this.state.streaming) return;
|
|
103
|
-
const { apiKey, model } = this.state;
|
|
104
|
-
if (!apiKey) { this.setState({ status: 'Enter
|
|
157
|
+
const { apiKey, model, providerType, baseUrl } = this.state;
|
|
158
|
+
if (!apiKey) { this.setState({ status: 'Enter an API key above.' }); return; }
|
|
105
159
|
input.value = '';
|
|
106
160
|
input.style.height = 'auto';
|
|
107
161
|
const messages = [...this.state.messages, { role: 'user', content: text }];
|
|
108
162
|
this.setState({ messages, streaming: true, status: '', streamingText: '' });
|
|
163
|
+
const provider = { type: providerType, apiKey, model, baseUrl: providerType === 'gemini' ? '' : baseUrl };
|
|
109
164
|
try {
|
|
110
165
|
let full = '';
|
|
111
166
|
const streamEl = document.createElement('div');
|
|
@@ -119,7 +174,7 @@ class BirdChat extends HTMLElement {
|
|
|
119
174
|
wrap.appendChild(cursor);
|
|
120
175
|
const list = this.querySelector('#msg-list');
|
|
121
176
|
if (list) list.appendChild(wrap);
|
|
122
|
-
await agentGenerate(
|
|
177
|
+
await agentGenerate(provider, messages,
|
|
123
178
|
chunk => { full += chunk; streamEl.textContent = full; const l = this.querySelector('#msg-list'); if (l) l.scrollTop = l.scrollHeight; },
|
|
124
179
|
(name, args) => { full += `\n[tool: ${name}(${JSON.stringify(args)})]\n`; streamEl.textContent = full; }
|
|
125
180
|
);
|
|
@@ -18321,10 +18321,7 @@ var require_thebird_browser_entry = __commonJS({
|
|
|
18321
18321
|
var { GeminiError, withRetry } = require_errors();
|
|
18322
18322
|
var { convertMessages, convertTools, cleanSchema, extractModelId, buildConfig } = require_convert();
|
|
18323
18323
|
function streamGemini2({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
18324
|
-
return {
|
|
18325
|
-
fullStream: createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }),
|
|
18326
|
-
warnings: Promise.resolve([])
|
|
18327
|
-
};
|
|
18324
|
+
return { fullStream: createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }), warnings: Promise.resolve([]) };
|
|
18328
18325
|
}
|
|
18329
18326
|
async function* createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
18330
18327
|
const client = getClient(apiKey);
|
|
@@ -18366,7 +18363,7 @@ var require_thebird_browser_entry = __commonJS({
|
|
|
18366
18363
|
}
|
|
18367
18364
|
}
|
|
18368
18365
|
yield { type: "tool-result", toolCallId: toolId, toolName: name, args, result };
|
|
18369
|
-
toolResultParts.push({ functionResponse: { name, response: result || {} } });
|
|
18366
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === "string" ? { output: result } : result || {} } });
|
|
18370
18367
|
}
|
|
18371
18368
|
yield { type: "finish-step", finishReason: "tool-calls" };
|
|
18372
18369
|
if (onStepFinish) await onStepFinish();
|
|
@@ -18408,13 +18405,148 @@ var require_thebird_browser_entry = __commonJS({
|
|
|
18408
18405
|
result = { error: true, message: e.message };
|
|
18409
18406
|
}
|
|
18410
18407
|
}
|
|
18411
|
-
toolResultParts.push({ functionResponse: { name, response: result || {} } });
|
|
18408
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === "string" ? { output: result } : result || {} } });
|
|
18412
18409
|
}
|
|
18413
18410
|
contents.push({ role: "model", parts: allParts });
|
|
18414
18411
|
contents.push({ role: "user", parts: toolResultParts });
|
|
18415
18412
|
}
|
|
18416
18413
|
}
|
|
18417
|
-
|
|
18414
|
+
function convertMessagesOAI(messages, system) {
|
|
18415
|
+
const result = [];
|
|
18416
|
+
if (system) result.push({ role: "system", content: typeof system === "string" ? system : JSON.stringify(system) });
|
|
18417
|
+
for (const m of messages) {
|
|
18418
|
+
if (typeof m.content === "string") {
|
|
18419
|
+
result.push({ role: m.role, content: m.content });
|
|
18420
|
+
continue;
|
|
18421
|
+
}
|
|
18422
|
+
if (!Array.isArray(m.content)) continue;
|
|
18423
|
+
const toolCalls = m.content.filter((b) => b.type === "tool_use");
|
|
18424
|
+
const toolResults = m.content.filter((b) => b.type === "tool_result");
|
|
18425
|
+
if (toolResults.length) {
|
|
18426
|
+
for (const b of toolResults) {
|
|
18427
|
+
const c = typeof b.content === "string" ? b.content : JSON.stringify(b.content || "");
|
|
18428
|
+
result.push({ role: "tool", tool_call_id: b.tool_use_id || b.id || b.name, content: c });
|
|
18429
|
+
}
|
|
18430
|
+
continue;
|
|
18431
|
+
}
|
|
18432
|
+
const textParts = m.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
18433
|
+
if (toolCalls.length) {
|
|
18434
|
+
result.push({
|
|
18435
|
+
role: "assistant",
|
|
18436
|
+
content: textParts || null,
|
|
18437
|
+
tool_calls: toolCalls.map((b) => ({
|
|
18438
|
+
id: b.id || "call_" + Math.random().toString(36).slice(2, 8),
|
|
18439
|
+
type: "function",
|
|
18440
|
+
function: { name: b.name, arguments: JSON.stringify(b.input || {}) }
|
|
18441
|
+
}))
|
|
18442
|
+
});
|
|
18443
|
+
} else {
|
|
18444
|
+
result.push({ role: m.role, content: textParts });
|
|
18445
|
+
}
|
|
18446
|
+
}
|
|
18447
|
+
return result;
|
|
18448
|
+
}
|
|
18449
|
+
function convertToolsOAI(tools) {
|
|
18450
|
+
if (!tools || typeof tools !== "object") return void 0;
|
|
18451
|
+
const list = Object.entries(tools).map(([name, t]) => ({
|
|
18452
|
+
type: "function",
|
|
18453
|
+
function: {
|
|
18454
|
+
name,
|
|
18455
|
+
description: t.description || "",
|
|
18456
|
+
parameters: t.parameters?.jsonSchema || t.parameters || { type: "object" }
|
|
18457
|
+
}
|
|
18458
|
+
}));
|
|
18459
|
+
return list.length ? list : void 0;
|
|
18460
|
+
}
|
|
18461
|
+
async function* streamOpenAI2({ url, apiKey, messages, system, model, tools, maxOutputTokens, temperature, onStepFinish }) {
|
|
18462
|
+
const oaiMsgs = convertMessagesOAI(messages, system);
|
|
18463
|
+
const oaiTools = convertToolsOAI(tools);
|
|
18464
|
+
let body = { messages: oaiMsgs, model, max_tokens: maxOutputTokens || 8192, temperature: temperature ?? 0.5 };
|
|
18465
|
+
if (oaiTools) body.tools = oaiTools;
|
|
18466
|
+
while (true) {
|
|
18467
|
+
yield { type: "start-step" };
|
|
18468
|
+
const res = await fetch(url, {
|
|
18469
|
+
method: "POST",
|
|
18470
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
|
|
18471
|
+
body: JSON.stringify({ ...body, stream: true })
|
|
18472
|
+
});
|
|
18473
|
+
if (!res.ok) {
|
|
18474
|
+
const t = await res.text();
|
|
18475
|
+
throw new Error(t);
|
|
18476
|
+
}
|
|
18477
|
+
const reader = res.body.getReader();
|
|
18478
|
+
const dec = new TextDecoder();
|
|
18479
|
+
let buf = "", toolCallsMap = {};
|
|
18480
|
+
try {
|
|
18481
|
+
while (true) {
|
|
18482
|
+
const { done, value } = await reader.read();
|
|
18483
|
+
if (done) break;
|
|
18484
|
+
buf += dec.decode(value, { stream: true });
|
|
18485
|
+
const lines = buf.split("\n");
|
|
18486
|
+
buf = lines.pop();
|
|
18487
|
+
for (const line of lines) {
|
|
18488
|
+
if (!line.startsWith("data: ")) continue;
|
|
18489
|
+
const d = line.slice(6).trim();
|
|
18490
|
+
if (d === "[DONE]") break;
|
|
18491
|
+
let chunk;
|
|
18492
|
+
try {
|
|
18493
|
+
chunk = JSON.parse(d);
|
|
18494
|
+
} catch {
|
|
18495
|
+
continue;
|
|
18496
|
+
}
|
|
18497
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
18498
|
+
if (!delta) continue;
|
|
18499
|
+
if (delta.content) yield { type: "text-delta", textDelta: delta.content };
|
|
18500
|
+
if (delta.tool_calls) {
|
|
18501
|
+
for (const tc of delta.tool_calls) {
|
|
18502
|
+
const idx = tc.index ?? 0;
|
|
18503
|
+
if (!toolCallsMap[idx]) toolCallsMap[idx] = { id: tc.id || "", name: "", args: "" };
|
|
18504
|
+
if (tc.id) toolCallsMap[idx].id = tc.id;
|
|
18505
|
+
if (tc.function?.name) toolCallsMap[idx].name += tc.function.name;
|
|
18506
|
+
if (tc.function?.arguments) toolCallsMap[idx].args += tc.function.arguments;
|
|
18507
|
+
}
|
|
18508
|
+
}
|
|
18509
|
+
}
|
|
18510
|
+
}
|
|
18511
|
+
} finally {
|
|
18512
|
+
reader.releaseLock();
|
|
18513
|
+
}
|
|
18514
|
+
const pending = Object.values(toolCallsMap);
|
|
18515
|
+
if (!pending.length) {
|
|
18516
|
+
yield { type: "finish-step", finishReason: "stop" };
|
|
18517
|
+
if (onStepFinish) await onStepFinish();
|
|
18518
|
+
return;
|
|
18519
|
+
}
|
|
18520
|
+
const toolResultMsgs = [];
|
|
18521
|
+
for (const tc of pending) {
|
|
18522
|
+
let args;
|
|
18523
|
+
try {
|
|
18524
|
+
args = JSON.parse(tc.args || "{}");
|
|
18525
|
+
} catch {
|
|
18526
|
+
args = {};
|
|
18527
|
+
}
|
|
18528
|
+
const toolDef = tools?.[tc.name];
|
|
18529
|
+
let result = toolDef ? null : { error: true, message: "Tool not found: " + tc.name };
|
|
18530
|
+
if (toolDef?.execute) try {
|
|
18531
|
+
result = await toolDef.execute(args, { toolCallId: tc.id });
|
|
18532
|
+
} catch (e) {
|
|
18533
|
+
result = { error: true, message: e.message };
|
|
18534
|
+
}
|
|
18535
|
+
yield { type: "tool-call", toolCallId: tc.id, toolName: tc.name, args };
|
|
18536
|
+
yield { type: "tool-result", toolCallId: tc.id, toolName: tc.name, args, result };
|
|
18537
|
+
toolResultMsgs.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(result ?? "") });
|
|
18538
|
+
}
|
|
18539
|
+
yield { type: "finish-step", finishReason: "tool-calls" };
|
|
18540
|
+
if (onStepFinish) await onStepFinish();
|
|
18541
|
+
body = { ...body, messages: [
|
|
18542
|
+
...body.messages,
|
|
18543
|
+
{ role: "assistant", content: null, tool_calls: pending.map((tc) => ({ id: tc.id, type: "function", function: { name: tc.name, arguments: tc.args } })) },
|
|
18544
|
+
...toolResultMsgs
|
|
18545
|
+
] };
|
|
18546
|
+
toolCallsMap = {};
|
|
18547
|
+
}
|
|
18548
|
+
}
|
|
18549
|
+
module.exports = { streamGemini: streamGemini2, generateGemini: generateGemini2, streamOpenAI: streamOpenAI2 };
|
|
18418
18550
|
}
|
|
18419
18551
|
});
|
|
18420
18552
|
|
|
@@ -18422,9 +18554,11 @@ var require_thebird_browser_entry = __commonJS({
|
|
|
18422
18554
|
var import_thebird_browser_entry = __toESM(require_thebird_browser_entry());
|
|
18423
18555
|
var streamGemini = import_thebird_browser_entry.default.streamGemini;
|
|
18424
18556
|
var generateGemini = import_thebird_browser_entry.default.generateGemini;
|
|
18557
|
+
var streamOpenAI = import_thebird_browser_entry.default.streamOpenAI;
|
|
18425
18558
|
export {
|
|
18426
18559
|
generateGemini,
|
|
18427
|
-
streamGemini
|
|
18560
|
+
streamGemini,
|
|
18561
|
+
streamOpenAI
|
|
18428
18562
|
};
|
|
18429
18563
|
/*! Bundled license information:
|
|
18430
18564
|
|
package/index.js
CHANGED
|
@@ -51,7 +51,7 @@ async function* createFullStream({ model, system, messages, tools, onStepFinish,
|
|
|
51
51
|
catch (e) { result = { error: true, message: e.message }; }
|
|
52
52
|
}
|
|
53
53
|
yield { type: 'tool-result', toolCallId: toolId, toolName: name, args, result };
|
|
54
|
-
toolResultParts.push({ functionResponse: { name, response: result || {} } });
|
|
54
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === 'string' ? { output: result } : (result || {}) } });
|
|
55
55
|
}
|
|
56
56
|
yield { type: 'finish-step', finishReason: 'tool-calls' };
|
|
57
57
|
if (onStepFinish) await onStepFinish();
|
|
@@ -91,7 +91,7 @@ async function generateGemini({ model, system, messages, tools, apiKey, temperat
|
|
|
91
91
|
try { result = await toolDef.execute(args); }
|
|
92
92
|
catch (e) { result = { error: true, message: e.message }; }
|
|
93
93
|
}
|
|
94
|
-
toolResultParts.push({ functionResponse: { name, response: result || {} } });
|
|
94
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === 'string' ? { output: result } : (result || {}) } });
|
|
95
95
|
}
|
|
96
96
|
contents.push({ role: 'model', parts: allParts });
|
|
97
97
|
contents.push({ role: 'user', parts: toolResultParts });
|
package/package.json
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const { getClient } = require('./lib/client.js');
|
|
2
|
+
const { GeminiError, withRetry } = require('./lib/errors.js');
|
|
3
|
+
const { convertMessages, convertTools, cleanSchema, extractModelId, buildConfig } = require('./lib/convert.js');
|
|
4
|
+
|
|
5
|
+
function streamGemini({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
6
|
+
return { fullStream: createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }), warnings: Promise.resolve([]) };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function* createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
10
|
+
const client = getClient(apiKey);
|
|
11
|
+
const modelId = extractModelId(model);
|
|
12
|
+
let contents = convertMessages(messages);
|
|
13
|
+
const { config } = buildConfig({ system, tools, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities });
|
|
14
|
+
while (true) {
|
|
15
|
+
yield { type: 'start-step' };
|
|
16
|
+
try {
|
|
17
|
+
const stream = await withRetry(() => client.models.generateContentStream({ model: modelId, contents, config }));
|
|
18
|
+
const allParts = [];
|
|
19
|
+
for await (const chunk of stream) {
|
|
20
|
+
for (const candidate of (chunk.candidates || [])) {
|
|
21
|
+
for (const part of (candidate.content?.parts || [])) {
|
|
22
|
+
allParts.push(part);
|
|
23
|
+
if (part.text && !part.thought) yield { type: 'text-delta', textDelta: part.text };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const fcParts = allParts.filter(p => p.functionCall);
|
|
28
|
+
if (fcParts.length === 0) {
|
|
29
|
+
yield { type: 'finish-step', finishReason: 'stop' };
|
|
30
|
+
if (onStepFinish) await onStepFinish();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const toolResultParts = [];
|
|
34
|
+
for (const part of fcParts) {
|
|
35
|
+
const name = part.functionCall.name;
|
|
36
|
+
const args = part.functionCall.args || {};
|
|
37
|
+
const toolId = 'toolu_' + Math.random().toString(36).slice(2, 10);
|
|
38
|
+
yield { type: 'tool-call', toolCallId: toolId, toolName: name, args };
|
|
39
|
+
const toolDef = tools?.[name];
|
|
40
|
+
let result = toolDef ? null : { error: true, message: 'Tool not found: ' + name };
|
|
41
|
+
if (toolDef?.execute) {
|
|
42
|
+
try { result = await toolDef.execute(args, { toolCallId: toolId }); }
|
|
43
|
+
catch (e) { result = { error: true, message: e.message }; }
|
|
44
|
+
}
|
|
45
|
+
yield { type: 'tool-result', toolCallId: toolId, toolName: name, args, result };
|
|
46
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === 'string' ? { output: result } : (result || {}) } });
|
|
47
|
+
}
|
|
48
|
+
yield { type: 'finish-step', finishReason: 'tool-calls' };
|
|
49
|
+
if (onStepFinish) await onStepFinish();
|
|
50
|
+
contents.push({ role: 'model', parts: allParts });
|
|
51
|
+
contents.push({ role: 'user', parts: toolResultParts });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
yield { type: 'error', error: err };
|
|
54
|
+
yield { type: 'finish-step', finishReason: 'error' };
|
|
55
|
+
if (onStepFinish) await onStepFinish();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function generateGemini({ model, system, messages, tools, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
62
|
+
const client = getClient(apiKey);
|
|
63
|
+
const modelId = extractModelId(model);
|
|
64
|
+
let contents = convertMessages(messages);
|
|
65
|
+
const { config } = buildConfig({ system, tools, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities });
|
|
66
|
+
while (true) {
|
|
67
|
+
const response = await withRetry(() => client.models.generateContent({ model: modelId, contents, config }));
|
|
68
|
+
const candidate = response.candidates?.[0];
|
|
69
|
+
if (!candidate) throw new GeminiError('No candidates returned', { retryable: false });
|
|
70
|
+
const allParts = candidate.content?.parts || [];
|
|
71
|
+
const fcParts = allParts.filter(p => p.functionCall);
|
|
72
|
+
if (fcParts.length === 0) {
|
|
73
|
+
const text = allParts.filter(p => p.text && !p.thought).map(p => p.text).join('');
|
|
74
|
+
return { text, parts: allParts, response };
|
|
75
|
+
}
|
|
76
|
+
const toolResultParts = [];
|
|
77
|
+
for (const part of fcParts) {
|
|
78
|
+
const name = part.functionCall.name;
|
|
79
|
+
const args = part.functionCall.args || {};
|
|
80
|
+
const toolDef = tools?.[name];
|
|
81
|
+
let result = toolDef ? null : { error: true, message: 'Tool not found: ' + name };
|
|
82
|
+
if (toolDef?.execute) {
|
|
83
|
+
try { result = await toolDef.execute(args); }
|
|
84
|
+
catch (e) { result = { error: true, message: e.message }; }
|
|
85
|
+
}
|
|
86
|
+
toolResultParts.push({ functionResponse: { name, response: typeof result === 'string' ? { output: result } : (result || {}) } });
|
|
87
|
+
}
|
|
88
|
+
contents.push({ role: 'model', parts: allParts });
|
|
89
|
+
contents.push({ role: 'user', parts: toolResultParts });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function convertMessagesOAI(messages, system) {
|
|
94
|
+
const result = [];
|
|
95
|
+
if (system) result.push({ role: 'system', content: typeof system === 'string' ? system : JSON.stringify(system) });
|
|
96
|
+
for (const m of messages) {
|
|
97
|
+
if (typeof m.content === 'string') { result.push({ role: m.role, content: m.content }); continue; }
|
|
98
|
+
if (!Array.isArray(m.content)) continue;
|
|
99
|
+
const toolCalls = m.content.filter(b => b.type === 'tool_use');
|
|
100
|
+
const toolResults = m.content.filter(b => b.type === 'tool_result');
|
|
101
|
+
if (toolResults.length) {
|
|
102
|
+
for (const b of toolResults) {
|
|
103
|
+
const c = typeof b.content === 'string' ? b.content : JSON.stringify(b.content || '');
|
|
104
|
+
result.push({ role: 'tool', tool_call_id: b.tool_use_id || b.id || b.name, content: c });
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const textParts = m.content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
109
|
+
if (toolCalls.length) {
|
|
110
|
+
result.push({ role: 'assistant', content: textParts || null,
|
|
111
|
+
tool_calls: toolCalls.map(b => ({ id: b.id || ('call_' + Math.random().toString(36).slice(2,8)), type: 'function',
|
|
112
|
+
function: { name: b.name, arguments: JSON.stringify(b.input || {}) } })) });
|
|
113
|
+
} else {
|
|
114
|
+
result.push({ role: m.role, content: textParts });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function convertToolsOAI(tools) {
|
|
121
|
+
if (!tools || typeof tools !== 'object') return undefined;
|
|
122
|
+
const list = Object.entries(tools).map(([name, t]) => ({
|
|
123
|
+
type: 'function', function: { name, description: t.description || '',
|
|
124
|
+
parameters: t.parameters?.jsonSchema || t.parameters || { type: 'object' } }
|
|
125
|
+
}));
|
|
126
|
+
return list.length ? list : undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function* streamOpenAI({ url, apiKey, messages, system, model, tools, maxOutputTokens, temperature, onStepFinish }) {
|
|
130
|
+
const oaiMsgs = convertMessagesOAI(messages, system);
|
|
131
|
+
const oaiTools = convertToolsOAI(tools);
|
|
132
|
+
let body = { messages: oaiMsgs, model, max_tokens: maxOutputTokens || 8192, temperature: temperature ?? 0.5 };
|
|
133
|
+
if (oaiTools) body.tools = oaiTools;
|
|
134
|
+
while (true) {
|
|
135
|
+
yield { type: 'start-step' };
|
|
136
|
+
const res = await fetch(url, { method: 'POST',
|
|
137
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
138
|
+
body: JSON.stringify({ ...body, stream: true }) });
|
|
139
|
+
if (!res.ok) { const t = await res.text(); throw new Error(t); }
|
|
140
|
+
const reader = res.body.getReader();
|
|
141
|
+
const dec = new TextDecoder();
|
|
142
|
+
let buf = '', toolCallsMap = {};
|
|
143
|
+
try {
|
|
144
|
+
while (true) {
|
|
145
|
+
const { done, value } = await reader.read();
|
|
146
|
+
if (done) break;
|
|
147
|
+
buf += dec.decode(value, { stream: true });
|
|
148
|
+
const lines = buf.split('\n');
|
|
149
|
+
buf = lines.pop();
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
if (!line.startsWith('data: ')) continue;
|
|
152
|
+
const d = line.slice(6).trim();
|
|
153
|
+
if (d === '[DONE]') break;
|
|
154
|
+
let chunk; try { chunk = JSON.parse(d); } catch { continue; }
|
|
155
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
156
|
+
if (!delta) continue;
|
|
157
|
+
if (delta.content) yield { type: 'text-delta', textDelta: delta.content };
|
|
158
|
+
if (delta.tool_calls) {
|
|
159
|
+
for (const tc of delta.tool_calls) {
|
|
160
|
+
const idx = tc.index ?? 0;
|
|
161
|
+
if (!toolCallsMap[idx]) toolCallsMap[idx] = { id: tc.id || '', name: '', args: '' };
|
|
162
|
+
if (tc.id) toolCallsMap[idx].id = tc.id;
|
|
163
|
+
if (tc.function?.name) toolCallsMap[idx].name += tc.function.name;
|
|
164
|
+
if (tc.function?.arguments) toolCallsMap[idx].args += tc.function.arguments;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} finally { reader.releaseLock(); }
|
|
170
|
+
const pending = Object.values(toolCallsMap);
|
|
171
|
+
if (!pending.length) {
|
|
172
|
+
yield { type: 'finish-step', finishReason: 'stop' };
|
|
173
|
+
if (onStepFinish) await onStepFinish();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const toolResultMsgs = [];
|
|
177
|
+
for (const tc of pending) {
|
|
178
|
+
let args; try { args = JSON.parse(tc.args || '{}'); } catch { args = {}; }
|
|
179
|
+
const toolDef = tools?.[tc.name];
|
|
180
|
+
let result = toolDef ? null : { error: true, message: 'Tool not found: ' + tc.name };
|
|
181
|
+
if (toolDef?.execute) try { result = await toolDef.execute(args, { toolCallId: tc.id }); } catch(e) { result = { error: true, message: e.message }; }
|
|
182
|
+
yield { type: 'tool-call', toolCallId: tc.id, toolName: tc.name, args };
|
|
183
|
+
yield { type: 'tool-result', toolCallId: tc.id, toolName: tc.name, args, result };
|
|
184
|
+
toolResultMsgs.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result ?? '') });
|
|
185
|
+
}
|
|
186
|
+
yield { type: 'finish-step', finishReason: 'tool-calls' };
|
|
187
|
+
if (onStepFinish) await onStepFinish();
|
|
188
|
+
body = { ...body, messages: [...body.messages,
|
|
189
|
+
{ role: 'assistant', content: null, tool_calls: pending.map(tc => ({ id: tc.id, type: 'function', function: { name: tc.name, arguments: tc.args } })) },
|
|
190
|
+
...toolResultMsgs
|
|
191
|
+
]};
|
|
192
|
+
toolCallsMap = {};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { streamGemini, generateGemini, streamOpenAI };
|