thebird 1.2.18 → 1.2.19
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/examples/sdk-validate.js +31 -0
- package/package.json +2 -2
- package/server.js +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,3 +5,10 @@
|
|
|
5
5
|
### Added
|
|
6
6
|
- `wasi/cli.ts`: Deno CLI — Anthropic-format prompt → Gemini streaming via REST, flags: `--model`, `--system`
|
|
7
7
|
- `deno.json`: tasks `cli` (run) and `cli:compile` (single binary)
|
|
8
|
+
|
|
9
|
+
## [Unreleased - 2]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `server.js`: HTTP proxy on port 3456, serves Anthropic Messages API wire format (streaming SSE + non-streaming JSON), backed by thebird → Gemini. Observability at `GET /debug/server`.
|
|
13
|
+
- `examples/sdk-validate.js`: Anthropic SDK (`@anthropic-ai/sdk`) client pointing at local proxy, validates both streaming and non-streaming paths.
|
|
14
|
+
- `@anthropic-ai/sdk` added to dependencies.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const Anthropic = require('@anthropic-ai/sdk').default;
|
|
2
|
+
|
|
3
|
+
const client = new Anthropic({
|
|
4
|
+
apiKey: 'placeholder',
|
|
5
|
+
baseURL: process.env.THEBIRD_URL || 'http://localhost:3456',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
process.stdout.write('[streaming] ');
|
|
10
|
+
const stream = client.messages.stream({
|
|
11
|
+
model: 'gemini-2.5-flash',
|
|
12
|
+
max_tokens: 256,
|
|
13
|
+
messages: [{ role: 'user', content: 'Say exactly: thebird works' }],
|
|
14
|
+
});
|
|
15
|
+
for await (const ev of stream) {
|
|
16
|
+
if (ev.type === 'content_block_delta' && ev.delta.type === 'text_delta') {
|
|
17
|
+
process.stdout.write(ev.delta.text);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write('\n');
|
|
21
|
+
|
|
22
|
+
process.stdout.write('[non-streaming] ');
|
|
23
|
+
const msg = await client.messages.create({
|
|
24
|
+
model: 'gemini-2.5-flash',
|
|
25
|
+
max_tokens: 256,
|
|
26
|
+
messages: [{ role: 'user', content: 'Say exactly: thebird works' }],
|
|
27
|
+
});
|
|
28
|
+
console.log(msg.content[0].text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main().catch(e => { process.stderr.write(e.message + '\n'); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thebird",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.19",
|
|
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",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"url": "https://github.com/AnEntrypoint/thebird.git"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@anthropic-ai/sdk": "^0.88.0",
|
|
38
39
|
"@google/genai": "^1.0.0"
|
|
39
40
|
},
|
|
40
|
-
"peerDependencies": {},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=18"
|
|
43
43
|
}
|
package/server.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const { streamGemini, generateGemini } = require('./index.js');
|
|
3
|
+
|
|
4
|
+
const PORT = process.env.PORT || 3456;
|
|
5
|
+
const state = { requests: 0, errors: 0, active: 0 };
|
|
6
|
+
|
|
7
|
+
const sse = (ev, data) => `event: ${ev}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
8
|
+
|
|
9
|
+
const msgId = () => 'msg_' + Math.random().toString(36).slice(2, 12);
|
|
10
|
+
|
|
11
|
+
async function handleMessages(req, res) {
|
|
12
|
+
let body = '';
|
|
13
|
+
for await (const chunk of req) body += chunk;
|
|
14
|
+
const { model, messages, system, stream, max_tokens } = JSON.parse(body);
|
|
15
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
16
|
+
if (!apiKey) { res.writeHead(500); res.end(JSON.stringify({ error: 'GEMINI_API_KEY required' })); return; }
|
|
17
|
+
const params = { model: model || 'gemini-2.5-flash', messages, system, apiKey, maxOutputTokens: max_tokens || 8192 };
|
|
18
|
+
|
|
19
|
+
if (!stream) {
|
|
20
|
+
const result = await generateGemini(params);
|
|
21
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
22
|
+
res.end(JSON.stringify({
|
|
23
|
+
id: msgId(), type: 'message', role: 'assistant', model: params.model,
|
|
24
|
+
content: [{ type: 'text', text: result.text }],
|
|
25
|
+
stop_reason: 'end_turn', usage: { input_tokens: 0, output_tokens: 0 },
|
|
26
|
+
}));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
res.writeHead(200, {
|
|
31
|
+
'Content-Type': 'text/event-stream',
|
|
32
|
+
'Cache-Control': 'no-cache',
|
|
33
|
+
'Connection': 'keep-alive',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const id = msgId();
|
|
37
|
+
res.write(sse('message_start', { type: 'message_start', message: { id, type: 'message', role: 'assistant', content: [], model: params.model, stop_reason: null, usage: { input_tokens: 0, output_tokens: 0 } } }));
|
|
38
|
+
res.write(sse('content_block_start', { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }));
|
|
39
|
+
res.write(sse('ping', { type: 'ping' }));
|
|
40
|
+
|
|
41
|
+
let outputTokens = 0;
|
|
42
|
+
for await (const ev of streamGemini(params).fullStream) {
|
|
43
|
+
if (ev.type === 'text-delta') {
|
|
44
|
+
outputTokens += ev.textDelta.length;
|
|
45
|
+
res.write(sse('content_block_delta', { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: ev.textDelta } }));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
res.write(sse('content_block_stop', { type: 'content_block_stop', index: 0 }));
|
|
50
|
+
res.write(sse('message_delta', { type: 'message_delta', delta: { stop_reason: 'end_turn', stop_sequence: null }, usage: { output_tokens: outputTokens } }));
|
|
51
|
+
res.write(sse('message_stop', { type: 'message_stop' }));
|
|
52
|
+
res.end();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
http.createServer(async (req, res) => {
|
|
56
|
+
state.requests++;
|
|
57
|
+
state.active++;
|
|
58
|
+
try {
|
|
59
|
+
if (req.method === 'GET' && req.url === '/debug/server') {
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify(state));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (req.method === 'POST' && req.url === '/v1/messages') {
|
|
65
|
+
await handleMessages(req, res);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
res.writeHead(404);
|
|
69
|
+
res.end(JSON.stringify({ error: 'not found' }));
|
|
70
|
+
} catch (err) {
|
|
71
|
+
state.errors++;
|
|
72
|
+
res.writeHead(500);
|
|
73
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
74
|
+
} finally {
|
|
75
|
+
state.active--;
|
|
76
|
+
}
|
|
77
|
+
}).listen(PORT, () => process.stderr.write(`thebird proxy listening on ${PORT}\n`));
|