thebird 1.2.99 → 1.2.101

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.
@@ -1,88 +0,0 @@
1
- const { BridgeError } = require('../errors');
2
-
3
- async function postJson(url, body) {
4
- const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
5
- if (!res.ok) { const t = await res.text(); throw new BridgeError(t, { status: res.status, retryable: res.status === 429 || res.status >= 500 }); }
6
- return res.json();
7
- }
8
-
9
- function subscribeSSE(url, onEvent) {
10
- const ctrl = new AbortController();
11
- (async () => {
12
- try {
13
- const res = await fetch(url, { signal: ctrl.signal });
14
- if (!res.ok || !res.body) return;
15
- const reader = res.body.getReader();
16
- const dec = new TextDecoder();
17
- let buf = '';
18
- while (true) {
19
- const { done, value } = await reader.read();
20
- if (done) return;
21
- buf += dec.decode(value, { stream: true });
22
- let i;
23
- while ((i = buf.indexOf('\n\n')) >= 0) {
24
- const chunk = buf.slice(0, i); buf = buf.slice(i + 2);
25
- const line = chunk.split('\n').find(l => l.startsWith('data:'));
26
- if (!line) continue;
27
- try { onEvent(JSON.parse(line.slice(5))); } catch (_) {}
28
- }
29
- }
30
- } catch (e) { if (e.name !== 'AbortError') throw e; }
31
- })();
32
- return () => ctrl.abort();
33
- }
34
-
35
- function toUserText(messages) {
36
- return messages.filter(m => m.role === 'user').map(m =>
37
- typeof m.content === 'string' ? m.content : (m.content || []).filter(b => b.type === 'text').map(b => b.text).join('')
38
- ).join('\n');
39
- }
40
-
41
- async function* streamACP({ url, model, messages, onStepFinish }) {
42
- yield { type: 'start-step' };
43
- const base = (url || 'http://localhost:4780').replace(/\/$/, '');
44
- const { id: sessionId } = await postJson(base + '/session', {});
45
- const queue = []; let resolveNext = null; let done = false;
46
- const push = ev => { queue.push(ev); if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } };
47
- const textSeen = new Map();
48
- const toolState = new Map();
49
- const unsubscribe = subscribeSSE(base + '/event', (msg) => {
50
- if (msg.type !== 'message.part.updated') return;
51
- const part = msg.properties?.part;
52
- if (!part) return;
53
- if (part.type === 'text' && part.messageID) {
54
- const prior = textSeen.get(part.id) || '';
55
- const txt = part.text || '';
56
- if (txt.length > prior.length) { push({ type: 'text-delta', textDelta: txt.slice(prior.length) }); textSeen.set(part.id, txt); }
57
- } else if (part.type === 'tool') {
58
- const cid = part.callID; const st = part.state?.status;
59
- if (st === 'running' && !toolState.has(cid)) { toolState.set(cid, { name: part.tool, args: part.state.input || {} }); push({ type: 'tool-call', toolCallId: cid, toolName: part.tool, args: part.state.input || {} }); }
60
- else if (st === 'completed' && toolState.has(cid) && !toolState.get(cid).completed) { toolState.get(cid).completed = true; push({ type: 'tool-result', toolCallId: cid, toolName: part.tool, args: part.state.input || {}, result: part.state.output || '' }); }
61
- }
62
- });
63
- const promptPromise = postJson(base + '/session/' + sessionId + '/message', {
64
- parts: [{ type: 'text', text: toUserText(messages) }],
65
- providerID: 'kilo',
66
- modelID: model || 'x-ai/grok-code-fast-1:optimized:free',
67
- }).finally(() => { done = true; if (resolveNext) { const r = resolveNext; resolveNext = null; r(); } });
68
- while (!done || queue.length) {
69
- if (queue.length) { yield queue.shift(); continue; }
70
- await new Promise(r => { resolveNext = r; });
71
- }
72
- const result = await promptPromise;
73
- unsubscribe();
74
- yield { type: 'finish-step', finishReason: result.info?.finish || 'stop' };
75
- if (onStepFinish) await onStepFinish();
76
- }
77
-
78
- async function generateACP(opts) {
79
- let text = '';
80
- const toolCalls = [];
81
- for await (const ev of streamACP(opts)) {
82
- if (ev.type === 'text-delta') text += ev.textDelta;
83
- else if (ev.type === 'tool-call') toolCalls.push({ id: ev.toolCallId, name: ev.toolName, args: ev.args });
84
- }
85
- return { text, toolCalls };
86
- }
87
-
88
- module.exports = { streamACP, generateACP };
@@ -1,134 +0,0 @@
1
- const { GeminiError } = require('../errors');
2
- const { guardStream } = require('../stream-guard');
3
-
4
- function convertMessages(messages, system) {
5
- const result = [];
6
- if (system) result.push({ role: 'system', content: typeof system === 'string' ? system : JSON.stringify(system) });
7
- for (const m of messages) {
8
- if (typeof m.content === 'string') { result.push({ role: m.role, content: m.content }); continue; }
9
- if (!Array.isArray(m.content)) continue;
10
- const toolCalls = m.content.filter(b => b.type === 'tool_use');
11
- const toolResults = m.content.filter(b => b.type === 'tool_result');
12
- if (toolResults.length) {
13
- for (const b of toolResults) {
14
- const c = typeof b.content === 'string' ? b.content : JSON.stringify(b.content || '');
15
- result.push({ role: 'tool', tool_call_id: b.tool_use_id || b.id || b.name, content: c });
16
- }
17
- continue;
18
- }
19
- const textParts = m.content.filter(b => b.type === 'text').map(b => b.text).join('');
20
- if (toolCalls.length) {
21
- result.push({ role: 'assistant', content: textParts || null,
22
- tool_calls: toolCalls.map(b => ({ id: b.id || ('call_' + Math.random().toString(36).slice(2,8)), type: 'function',
23
- function: { name: b.name, arguments: JSON.stringify(b.input || {}) } })) });
24
- } else {
25
- result.push({ role: m.role, content: textParts });
26
- }
27
- }
28
- return result;
29
- }
30
-
31
- function convertTools(tools) {
32
- if (!tools || typeof tools !== 'object') return undefined;
33
- const list = Object.entries(tools).map(([name, t]) => ({
34
- type: 'function', function: { name, description: t.description || '',
35
- parameters: t.parameters?.jsonSchema || t.parameters || { type: 'object' } }
36
- }));
37
- return list.length ? list : undefined;
38
- }
39
-
40
- async function callOpenAI({ url, apiKey, headers, body }) {
41
- const res = await fetch(url, { method: 'POST',
42
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, ...(headers || {}) },
43
- body: JSON.stringify(body) });
44
- if (!res.ok) { const t = await res.text(); throw new GeminiError(t, { status: res.status, retryable: res.status === 429 || res.status >= 500, headers: res.headers }); }
45
- return res;
46
- }
47
-
48
- async function* readerIterable(reader) {
49
- const dec = new TextDecoder();
50
- while (true) {
51
- const { done, value } = await reader.read();
52
- if (done) return;
53
- yield dec.decode(value, { stream: true });
54
- }
55
- }
56
-
57
- async function* streamOpenAI({ url, apiKey, headers, body, tools, onStepFinish, streamGuard }) {
58
- while (true) {
59
- yield { type: 'start-step' };
60
- const res = await callOpenAI({ url, apiKey, headers, body: { ...body, stream: true } });
61
- const reader = res.body.getReader();
62
- let buf = '', toolCallsMap = {};
63
- try {
64
- for await (const text of guardStream(readerIterable(reader), streamGuard)) {
65
- buf += text;
66
- const lines = buf.split('\n');
67
- buf = lines.pop();
68
- for (const line of lines) {
69
- if (!line.startsWith('data: ')) continue;
70
- const d = line.slice(6).trim();
71
- if (d === '[DONE]') break;
72
- let chunk; try { chunk = JSON.parse(d); } catch { continue; }
73
- const delta = chunk.choices?.[0]?.delta;
74
- if (!delta) continue;
75
- if (delta.content) yield { type: 'text-delta', textDelta: delta.content };
76
- if (delta.tool_calls) {
77
- for (const tc of delta.tool_calls) {
78
- const idx = tc.index ?? 0;
79
- if (!toolCallsMap[idx]) toolCallsMap[idx] = { id: tc.id || '', name: '', args: '' };
80
- if (tc.id) toolCallsMap[idx].id = tc.id;
81
- if (tc.function?.name) toolCallsMap[idx].name += tc.function.name;
82
- if (tc.function?.arguments) toolCallsMap[idx].args += tc.function.arguments;
83
- }
84
- }
85
- }
86
- }
87
- } finally { reader.releaseLock(); }
88
-
89
- const pending = Object.values(toolCallsMap);
90
- if (!pending.length) {
91
- yield { type: 'finish-step', finishReason: 'stop' };
92
- if (onStepFinish) await onStepFinish();
93
- return;
94
- }
95
- const toolResultMsgs = [];
96
- for (const tc of pending) {
97
- let args; try { args = JSON.parse(tc.args || '{}'); } catch { args = {}; }
98
- const toolDef = tools?.[tc.name];
99
- let result = toolDef ? null : { error: true, message: 'Tool not found: ' + tc.name };
100
- if (toolDef?.execute) try { result = await toolDef.execute(args, { toolCallId: tc.id }); } catch(e) { result = { error: true, message: e.message }; }
101
- yield { type: 'tool-call', toolCallId: tc.id, toolName: tc.name, args };
102
- yield { type: 'tool-result', toolCallId: tc.id, toolName: tc.name, args, result };
103
- toolResultMsgs.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result ?? '') });
104
- }
105
- yield { type: 'finish-step', finishReason: 'tool-calls' };
106
- if (onStepFinish) await onStepFinish();
107
- body = { ...body, messages: [...body.messages,
108
- { role: 'assistant', content: null, tool_calls: pending.map(tc => ({ id: tc.id, type: 'function', function: { name: tc.name, arguments: tc.args } })) },
109
- ...toolResultMsgs
110
- ]};
111
- toolCallsMap = {};
112
- }
113
- }
114
-
115
- async function generateOpenAI({ url, apiKey, headers, body, tools }) {
116
- while (true) {
117
- const res = await callOpenAI({ url, apiKey, headers, body: { ...body, stream: false } });
118
- const data = await res.json();
119
- const msg = data.choices?.[0]?.message;
120
- if (!msg) throw new GeminiError('No message in response', { retryable: false });
121
- if (!msg.tool_calls?.length) return { text: msg.content || '', response: data };
122
- const toolResultMsgs = [];
123
- for (const tc of msg.tool_calls) {
124
- let args; try { args = JSON.parse(tc.function?.arguments || '{}'); } catch { args = {}; }
125
- const toolDef = tools?.[tc.function?.name];
126
- let result = toolDef ? null : { error: true, message: 'Tool not found: ' + tc.function?.name };
127
- if (toolDef?.execute) try { result = await toolDef.execute(args); } catch(e) { result = { error: true, message: e.message }; }
128
- toolResultMsgs.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result ?? '') });
129
- }
130
- body = { ...body, messages: [...body.messages, msg, ...toolResultMsgs] };
131
- }
132
- }
133
-
134
- module.exports = { streamOpenAI, generateOpenAI, convertMessages, convertTools };
@@ -1,95 +0,0 @@
1
- const { extractModelId } = require('./convert');
2
- const { resolveTransformers, applyRequestTransformers } = require('./transformers');
3
- const { loadConfig } = require('./config');
4
- const { route } = require('./router');
5
- const openaiProv = require('./providers/openai');
6
- const { createCircuitBreaker } = require('./circuit-breaker');
7
- const { getCapabilities, stripUnsupported } = require('./capabilities');
8
-
9
- function isGeminiProvider(p) {
10
- return p.name === 'gemini' || (p.api_base_url || '').includes('generativelanguage.googleapis.com');
11
- }
12
-
13
- function findProvider(providers, providerName, modelName) {
14
- if (providerName) return providers.find(p => p.name === providerName);
15
- if (modelName) return providers.find(p => (p.models || []).includes(modelName));
16
- return providers[0];
17
- }
18
-
19
- function buildOpenAIUrl(base) {
20
- const clean = (base || '').replace(/\/$/g, '');
21
- return clean.includes('/completions') ? clean : clean + '/chat/completions';
22
- }
23
-
24
- function resolveForProvider(provider, model, customMap) {
25
- const useList = provider.transformer?.[model]?.use || provider.transformer?.use || [];
26
- return resolveTransformers(useList, customMap);
27
- }
28
-
29
- async function* routerStream(params, resolver) {
30
- const { createFullStream } = require('../index');
31
- const { provider, actualModel, transformers, caps } = await resolver(params);
32
- const stripped = stripUnsupported(params, caps);
33
- params = stripped.params;
34
- if (isGeminiProvider(provider)) {
35
- yield* createFullStream({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
36
- } else {
37
- const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
38
- const oaiTools = openaiProv.convertTools(params.tools);
39
- let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
40
- if (oaiTools) req.tools = oaiTools;
41
- req = applyRequestTransformers(req, transformers);
42
- yield* openaiProv.streamOpenAI({ url: buildOpenAIUrl(provider.api_base_url), apiKey: provider.api_key, headers: req._extraHeaders, body: req, tools: params.tools, onStepFinish: params.onStepFinish, streamGuard: params.streamGuard });
43
- }
44
- }
45
-
46
- function createRouter(config) {
47
- const { generateGemini } = require('../index');
48
- const providers = config.Providers || config.providers || [];
49
- const routerCfg = config.Router || {};
50
- const breaker = createCircuitBreaker(config.circuitBreaker);
51
- async function resolve(params) {
52
- const { providerName, modelName } = await route(params, routerCfg, config.customRouter);
53
- let provider = findProvider(providers, providerName, modelName) || providers[0];
54
- if (provider && breaker.isOpen(provider.name)) {
55
- const fallback = providers.find(p => p !== provider && !breaker.isOpen(p.name));
56
- if (fallback) provider = fallback;
57
- }
58
- if (!provider) throw new Error('[thebird] no provider configured');
59
- const actualModel = modelName || (provider.models || [])[0] || extractModelId(params.model) || 'gemini-2.0-flash';
60
- const transformers = resolveForProvider(provider, actualModel, config._transformers);
61
- const caps = getCapabilities(provider);
62
- return { provider, actualModel, transformers, caps };
63
- }
64
- return {
65
- breaker,
66
- stream(params) { return { fullStream: routerStream(params, resolve), warnings: Promise.resolve([]) }; },
67
- async generate(params) {
68
- const { provider, actualModel, transformers, caps } = await resolve(params);
69
- params = stripUnsupported(params, caps).params;
70
- if (isGeminiProvider(provider)) return generateGemini({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
71
- const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
72
- const oaiTools = openaiProv.convertTools(params.tools);
73
- let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
74
- if (oaiTools) req.tools = oaiTools;
75
- req = applyRequestTransformers(req, transformers);
76
- return openaiProv.generateOpenAI({ url: buildOpenAIUrl(provider.api_base_url), apiKey: provider.api_key, headers: req._extraHeaders, body: req, tools: params.tools });
77
- }
78
- };
79
- }
80
-
81
- function streamRouter(params) {
82
- const { streamGemini } = require('../index');
83
- const config = loadConfig(params.configPath);
84
- if (!(config.Providers || config.providers)?.length) return streamGemini(params);
85
- return createRouter(config).stream(params);
86
- }
87
-
88
- async function generateRouter(params) {
89
- const { generateGemini } = require('../index');
90
- const config = loadConfig(params.configPath);
91
- if (!(config.Providers || config.providers)?.length) return generateGemini(params);
92
- return createRouter(config).generate(params);
93
- }
94
-
95
- module.exports = { routerStream, createRouter, streamRouter, generateRouter, findProvider, buildOpenAIUrl, resolveForProvider, isGeminiProvider };
package/lib/router.js DELETED
@@ -1,51 +0,0 @@
1
- const { loadConfig } = require('./config');
2
-
3
- const SUBAGENT_RE = /<CCR-SUBAGENT-MODEL>([^<]+)<\/CCR-SUBAGENT-MODEL>/;
4
-
5
- function estimateTokens(messages, system) {
6
- let chars = typeof system === 'string' ? system.length : (system ? JSON.stringify(system).length : 0);
7
- for (const m of (messages || [])) {
8
- chars += typeof m.content === 'string' ? m.content.length : JSON.stringify(m.content || '').length;
9
- }
10
- return Math.ceil(chars / 4);
11
- }
12
-
13
- function extractSubagentModel(messages) {
14
- const first = messages?.[0];
15
- if (!first) return null;
16
- const text = typeof first.content === 'string' ? first.content :
17
- (Array.isArray(first.content) ? first.content.map(b => b.text || '').join('') : '');
18
- const m = SUBAGENT_RE.exec(text);
19
- return m ? m[1].trim() : null;
20
- }
21
-
22
- function parseProviderModel(str) {
23
- const idx = str.indexOf(',');
24
- if (idx === -1) return { providerName: null, modelName: str };
25
- return { providerName: str.slice(0, idx), modelName: str.slice(idx + 1) };
26
- }
27
-
28
- async function route(params, routerCfg, customRouterFn) {
29
- const { messages, system, taskType } = params;
30
-
31
- if (customRouterFn) {
32
- const custom = await customRouterFn(params, routerCfg);
33
- if (custom) return parseProviderModel(custom);
34
- }
35
-
36
- const subagent = extractSubagentModel(messages);
37
- if (subagent) return parseProviderModel(subagent);
38
-
39
- if (taskType === 'background' && routerCfg.background) return parseProviderModel(routerCfg.background);
40
- if (taskType === 'think' && routerCfg.think) return parseProviderModel(routerCfg.think);
41
- if (taskType === 'webSearch' && routerCfg.webSearch) return parseProviderModel(routerCfg.webSearch);
42
- if (taskType === 'image' && routerCfg.image) return parseProviderModel(routerCfg.image);
43
-
44
- const threshold = routerCfg.longContextThreshold || 60000;
45
- if (routerCfg.longContext && estimateTokens(messages, system) > threshold) return parseProviderModel(routerCfg.longContext);
46
-
47
- if (routerCfg.default) return parseProviderModel(routerCfg.default);
48
- return { providerName: null, modelName: null };
49
- }
50
-
51
- module.exports = { route, estimateTokens, parseProviderModel };
@@ -1,35 +0,0 @@
1
- const { TimeoutError, BridgeError } = require('./errors');
2
-
3
- async function* guardStream(iterable, opts = {}) {
4
- const timeoutMs = opts?.chunkTimeoutMs ?? 30000;
5
- const maxRepeats = opts?.maxRepeats ?? 100;
6
- let lastChunk = null;
7
- let repeatCount = 0;
8
- for await (const chunk of raceTimeout(iterable, timeoutMs)) {
9
- const key = JSON.stringify(chunk);
10
- if (key === lastChunk && key !== '{}' && key !== 'null') {
11
- repeatCount++;
12
- if (repeatCount >= maxRepeats) {
13
- throw new BridgeError(`Same chunk repeated ${maxRepeats} times`, { retryable: false });
14
- }
15
- } else {
16
- lastChunk = key;
17
- repeatCount = 1;
18
- }
19
- yield chunk;
20
- }
21
- }
22
-
23
- async function* raceTimeout(iterable, ms) {
24
- const iter = iterable[Symbol.asyncIterator]();
25
- while (true) {
26
- const result = await Promise.race([
27
- iter.next(),
28
- new Promise((_, reject) => setTimeout(() => reject(new TimeoutError(`Stream chunk timeout after ${ms}ms`)), ms))
29
- ]);
30
- if (result.done) return;
31
- yield result.value;
32
- }
33
- }
34
-
35
- module.exports = { guardStream };
@@ -1,93 +0,0 @@
1
- function removeCacheControl(obj) {
2
- if (!obj || typeof obj !== 'object') return obj;
3
- if (Array.isArray(obj)) return obj.map(removeCacheControl);
4
- const out = {};
5
- for (const [k, v] of Object.entries(obj)) {
6
- if (k === 'cache_control') continue;
7
- out[k] = removeCacheControl(v);
8
- }
9
- return out;
10
- }
11
-
12
- const BUILT_IN = {
13
- cleancache: {
14
- request(req) { return { ...req, messages: removeCacheControl(req.messages), system: removeCacheControl(req.system) }; }
15
- },
16
- deepseek: {
17
- request(req) {
18
- const r = removeCacheControl(req);
19
- if (r.system && typeof r.system !== 'string') {
20
- r.system = (Array.isArray(r.system) ? r.system : [r.system]).map(b => b.text || '').join('\n');
21
- }
22
- return r;
23
- }
24
- },
25
- openrouter: {
26
- options: {},
27
- request(req, opts) {
28
- const headers = { 'HTTP-Referer': 'https://github.com/AnEntrypoint/thebird', 'X-Title': 'thebird', ...(opts || {}).headers };
29
- if ((opts || {}).provider) req = { ...req, provider: (opts || {}).provider };
30
- return { ...req, _extraHeaders: { ...(req._extraHeaders || {}), ...headers } };
31
- }
32
- },
33
- maxtoken: {
34
- request(req, opts) { return { ...req, max_tokens: (opts || {}).max_tokens || req.max_tokens }; }
35
- },
36
- tooluse: {
37
- request(req) {
38
- if (req.tools && req.tools.length > 0) return { ...req, tool_choice: { type: 'required' } };
39
- return req;
40
- }
41
- },
42
- reasoning: {
43
- request(req) { return req; },
44
- response(res) {
45
- if (!res.choices) return res;
46
- return {
47
- ...res,
48
- choices: res.choices.map(c => {
49
- if (!c.message) return c;
50
- const msg = { ...c.message };
51
- if (msg.reasoning_content) { msg._reasoning = msg.reasoning_content; delete msg.reasoning_content; }
52
- return { ...c, message: msg };
53
- })
54
- };
55
- }
56
- },
57
- sampling: {
58
- request(req) {
59
- const r = { ...req };
60
- delete r.top_k;
61
- delete r.repetition_penalty;
62
- return r;
63
- }
64
- },
65
- groq: {
66
- request(req) {
67
- const r = { ...req };
68
- delete r.top_k;
69
- return r;
70
- }
71
- }
72
- };
73
-
74
- function resolveTransformers(useList, customMap) {
75
- if (!useList) return [];
76
- return useList.map(entry => {
77
- const name = Array.isArray(entry) ? entry[0] : entry;
78
- const opts = Array.isArray(entry) ? entry[1] : undefined;
79
- const t = (customMap && customMap[name]) || BUILT_IN[name];
80
- if (!t) { console.warn('[thebird] unknown transformer:', name); return null; }
81
- return { transformer: t, opts };
82
- }).filter(Boolean);
83
- }
84
-
85
- function applyRequestTransformers(req, transformers) {
86
- return transformers.reduce((r, { transformer, opts }) => transformer.request ? transformer.request(r, opts) : r, req);
87
- }
88
-
89
- function applyResponseTransformers(res, transformers) {
90
- return transformers.reduce((r, { transformer, opts }) => transformer.response ? transformer.response(r, opts) : r, res);
91
- }
92
-
93
- module.exports = { resolveTransformers, applyRequestTransformers, applyResponseTransformers, BUILT_IN };
@@ -1,4 +0,0 @@
1
- import tb from './thebird-browser-entry.js';
2
- export const streamGemini = tb.streamGemini;
3
- export const generateGemini = tb.generateGemini;
4
- export const streamOpenAI = tb.streamOpenAI;