thebird 1.2.10 → 1.2.12

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.
Files changed (2) hide show
  1. package/docs/app.js +42 -28
  2. package/package.json +1 -1
package/docs/app.js CHANGED
@@ -3,17 +3,47 @@ import htm from 'https://esm.sh/htm@3';
3
3
 
4
4
  const html = htm.bind(createElement);
5
5
 
6
- const MODELS_API = key => `https://generativelanguage.googleapis.com/v1beta/models?key=${key}`;
6
+ const BASE = 'https://generativelanguage.googleapis.com/v1beta';
7
7
 
8
8
  async function fetchModels(apiKey) {
9
- const res = await fetch(MODELS_API(apiKey));
10
- if (!res.ok) throw new Error(`Models API ${res.status}: ${await res.text()}`);
9
+ const res = await fetch(`${BASE}/models?key=${apiKey}`);
10
+ if (!res.ok) throw new Error(`Models API ${res.status}`);
11
11
  const { models = [] } = await res.json();
12
12
  return models
13
13
  .filter(m => m.supportedGenerationMethods?.includes('generateContent'))
14
14
  .map(m => ({ id: m.name.replace('models/', ''), label: m.displayName || m.name }));
15
15
  }
16
16
 
17
+ async function* streamGenerate(apiKey, model, contents) {
18
+ const res = await fetch(`${BASE}/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`, {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ contents, generationConfig: { maxOutputTokens: 8192, temperature: 0.7 } }),
22
+ });
23
+ if (!res.ok) throw new Error(`Generate API ${res.status}: ${await res.text()}`);
24
+ const reader = res.body.getReader();
25
+ const dec = new TextDecoder();
26
+ let buf = '';
27
+ while (true) {
28
+ const { done, value } = await reader.read();
29
+ if (done) break;
30
+ buf += dec.decode(value, { stream: true });
31
+ const lines = buf.split('\n');
32
+ buf = lines.pop();
33
+ for (const line of lines) {
34
+ if (!line.startsWith('data: ')) continue;
35
+ const json = line.slice(6).trim();
36
+ if (!json || json === '[DONE]') continue;
37
+ try {
38
+ const chunk = JSON.parse(json);
39
+ for (const c of (chunk.candidates || []))
40
+ for (const p of (c.content?.parts || []))
41
+ if (p.text && !p.thought) yield p.text;
42
+ } catch {}
43
+ }
44
+ }
45
+ }
46
+
17
47
  function convertMessages(messages) {
18
48
  const contents = [];
19
49
  for (const m of messages) {
@@ -25,13 +55,6 @@ function convertMessages(messages) {
25
55
  if (Array.isArray(m.content)) {
26
56
  const parts = m.content.map(b => {
27
57
  if (b.type === 'text' && b.text) return { text: b.text };
28
- if (b.type === 'tool_use') return { functionCall: { name: b.name, args: b.input || {} } };
29
- if (b.type === 'tool_result') {
30
- let resp;
31
- try { resp = typeof b.content === 'string' ? JSON.parse(b.content) : (b.content || {}); }
32
- catch { resp = { result: b.content }; }
33
- return { functionResponse: { name: b.name || 'unknown', response: resp } };
34
- }
35
58
  return null;
36
59
  }).filter(Boolean);
37
60
  if (parts.length) contents.push({ role, parts });
@@ -40,12 +63,6 @@ function convertMessages(messages) {
40
63
  return contents;
41
64
  }
42
65
 
43
- function buildConfig({ system, temperature } = {}) {
44
- const config = { maxOutputTokens: 8192, temperature: temperature ?? 0.7 };
45
- if (system) config.systemInstruction = system;
46
- return config;
47
- }
48
-
49
66
  class BirdChat extends HTMLElement {
50
67
  constructor() {
51
68
  super();
@@ -54,10 +71,11 @@ class BirdChat extends HTMLElement {
54
71
  apiKey: localStorage.getItem('gemini_api_key') || '',
55
72
  models: [], modelsLoading: false, status: '', streamingText: '',
56
73
  };
74
+ const self = this;
57
75
  window.__debug = {
58
- get state() { return this.state; }.bind(this),
59
- get messages() { return this.state.messages; }.bind(this),
60
- get models() { return this.state.models; }.bind(this),
76
+ get state() { return self.state; },
77
+ get messages() { return self.state.messages; },
78
+ get models() { return self.state.models; },
61
79
  };
62
80
  }
63
81
 
@@ -82,7 +100,7 @@ class BirdChat extends HTMLElement {
82
100
 
83
101
  render() {
84
102
  const { messages, streaming, model, apiKey, models, modelsLoading, status, streamingText } = this.state;
85
- const modelOptions = (models.length === 0 ? [{ id: model, label: model }] : models)
103
+ const opts = (models.length === 0 ? [{ id: model, label: model }] : models)
86
104
  .map(m => html`<option value=${m.id} selected=${m.id === model}>${m.label}</option>`);
87
105
 
88
106
  applyDiff(this, html`
@@ -98,7 +116,7 @@ class BirdChat extends HTMLElement {
98
116
  ${modelsLoading
99
117
  ? html`<span class="loading loading-spinner loading-sm text-primary"></span>`
100
118
  : html`<select class="select select-sm select-bordered" value=${model} disabled=${models.length === 0}
101
- onchange=${e => this.setState({ model: e.target.value })}>${modelOptions}</select>`}
119
+ onchange=${e => this.setState({ model: e.target.value })}>${opts}</select>`}
102
120
  </div>
103
121
  <button class="btn btn-sm btn-ghost" onclick=${() => this.setState({ messages: [], status: '' })}>Clear</button>
104
122
  </div>
@@ -141,14 +159,10 @@ class BirdChat extends HTMLElement {
141
159
  const messages = [...this.state.messages, { role: 'user', content: text }];
142
160
  this.setState({ messages, streaming: true, status: '', streamingText: '' });
143
161
  try {
144
- const { GoogleGenAI } = await import('https://esm.sh/@google/genai@1');
145
- const ai = new GoogleGenAI({ apiKey });
146
- const stream = await ai.models.generateContentStream({ model, contents: convertMessages(messages), config: buildConfig() });
147
162
  let full = '';
148
- for await (const chunk of stream) {
149
- for (const candidate of (chunk.candidates || []))
150
- for (const part of (candidate.content?.parts || []))
151
- if (part.text && !part.thought) { full += part.text; this.setState({ streamingText: full }); }
163
+ for await (const chunk of streamGenerate(apiKey, model, convertMessages(messages))) {
164
+ full += chunk;
165
+ this.setState({ streamingText: full });
152
166
  }
153
167
  const list = document.getElementById('msg-list');
154
168
  if (list) list.scrollTop = list.scrollHeight;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
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
  "main": "index.js",
6
6
  "types": "index.d.ts",