thebird 1.2.100 → 1.2.102
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/.gm/lastskill +1 -0
- package/CLAUDE.md +25 -8
- package/README.md +21 -233
- package/index.js +1 -104
- package/package.json +2 -3
- package/server.js +1 -1
- package/examples/basic-chat.js +0 -34
- package/examples/multi-turn.js +0 -45
- package/examples/sdk-validate.js +0 -31
- package/examples/streaming.js +0 -81
- package/examples/tool-use.js +0 -77
- package/examples/vision.js +0 -84
- package/index.d.ts +0 -126
- package/lib/capabilities.js +0 -50
- package/lib/circuit-breaker.js +0 -36
- package/lib/client.js +0 -10
- package/lib/cloud-generate.js +0 -119
- package/lib/config.js +0 -24
- package/lib/convert.js +0 -87
- package/lib/errors.js +0 -140
- package/lib/oauth.js +0 -133
- package/lib/providers/acp.js +0 -88
- package/lib/providers/openai.js +0 -134
- package/lib/router-stream.js +0 -95
- package/lib/router.js +0 -51
- package/lib/stream-guard.js +0 -35
- package/lib/transformers.js +0 -93
- package/thebird-browser-entry-esm.js +0 -4
- package/thebird-browser-entry.js +0 -196
package/examples/streaming.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* streaming.js — Streaming with all event types demonstrated
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* GEMINI_API_KEY=your-key node examples/streaming.js
|
|
6
|
-
*/
|
|
7
|
-
const { streamGemini } = require('../index');
|
|
8
|
-
|
|
9
|
-
const tools = {
|
|
10
|
-
get_time: {
|
|
11
|
-
description: 'Get the current time in a timezone.',
|
|
12
|
-
parameters: {
|
|
13
|
-
type: 'object',
|
|
14
|
-
properties: {
|
|
15
|
-
timezone: { type: 'string', description: 'IANA timezone name, e.g. America/New_York' }
|
|
16
|
-
},
|
|
17
|
-
required: ['timezone']
|
|
18
|
-
},
|
|
19
|
-
execute: async ({ timezone }) => {
|
|
20
|
-
const now = new Date().toLocaleString('en-US', { timeZone: timezone });
|
|
21
|
-
return { timezone, time: now };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
async function main() {
|
|
27
|
-
console.log('Streaming all event types:\n');
|
|
28
|
-
|
|
29
|
-
const { fullStream } = streamGemini({
|
|
30
|
-
model: 'gemini-2.0-flash',
|
|
31
|
-
system: 'You are a helpful assistant. Be concise.',
|
|
32
|
-
messages: [
|
|
33
|
-
{ role: 'user', content: "What time is it in Tokyo and New York right now?" }
|
|
34
|
-
],
|
|
35
|
-
tools,
|
|
36
|
-
temperature: 0.3,
|
|
37
|
-
maxOutputTokens: 512,
|
|
38
|
-
onStepFinish: async () => {
|
|
39
|
-
console.log('\n[step finished]');
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const stats = { steps: 0, toolCalls: 0, chars: 0 };
|
|
44
|
-
|
|
45
|
-
for await (const event of fullStream) {
|
|
46
|
-
switch (event.type) {
|
|
47
|
-
case 'start-step':
|
|
48
|
-
stats.steps++;
|
|
49
|
-
console.log(`\n[start-step #${stats.steps}]`);
|
|
50
|
-
break;
|
|
51
|
-
|
|
52
|
-
case 'text-delta':
|
|
53
|
-
stats.chars += event.textDelta.length;
|
|
54
|
-
process.stdout.write(event.textDelta);
|
|
55
|
-
break;
|
|
56
|
-
|
|
57
|
-
case 'tool-call':
|
|
58
|
-
stats.toolCalls++;
|
|
59
|
-
console.log(`\n[tool-call] id=${event.toolCallId} name=${event.toolName}`);
|
|
60
|
-
console.log(' args:', JSON.stringify(event.args));
|
|
61
|
-
break;
|
|
62
|
-
|
|
63
|
-
case 'tool-result':
|
|
64
|
-
console.log(`[tool-result] id=${event.toolCallId} name=${event.toolName}`);
|
|
65
|
-
console.log(' result:', JSON.stringify(event.result));
|
|
66
|
-
break;
|
|
67
|
-
|
|
68
|
-
case 'finish-step':
|
|
69
|
-
console.log(`\n[finish-step] reason=${event.finishReason}`);
|
|
70
|
-
break;
|
|
71
|
-
|
|
72
|
-
case 'error':
|
|
73
|
-
console.error('\n[error]', event.error.message);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log(`\n\nStats: ${stats.steps} steps, ${stats.toolCalls} tool calls, ${stats.chars} chars`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
main().catch(console.error);
|
package/examples/tool-use.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tool-use.js — Tool/function calling with generateGemini and streamGemini
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* GEMINI_API_KEY=your-key node examples/tool-use.js
|
|
6
|
-
*/
|
|
7
|
-
const { generateGemini, streamGemini } = require('../index');
|
|
8
|
-
|
|
9
|
-
const tools = {
|
|
10
|
-
get_weather: {
|
|
11
|
-
description: 'Get the current weather for a given city.',
|
|
12
|
-
parameters: {
|
|
13
|
-
type: 'object',
|
|
14
|
-
properties: {
|
|
15
|
-
city: { type: 'string', description: 'The city name' },
|
|
16
|
-
unit: { type: 'string', enum: ['celsius', 'fahrenheit'], description: 'Temperature unit' }
|
|
17
|
-
},
|
|
18
|
-
required: ['city']
|
|
19
|
-
},
|
|
20
|
-
execute: async ({ city, unit = 'celsius' }) => {
|
|
21
|
-
// Simulated weather data
|
|
22
|
-
return { city, temperature: 22, unit, condition: 'Sunny' };
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
calculate: {
|
|
26
|
-
description: 'Evaluate a simple math expression.',
|
|
27
|
-
parameters: {
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
expression: { type: 'string', description: 'Math expression to evaluate, e.g. "2 + 2"' }
|
|
31
|
-
},
|
|
32
|
-
required: ['expression']
|
|
33
|
-
},
|
|
34
|
-
execute: async ({ expression }) => {
|
|
35
|
-
try {
|
|
36
|
-
// eslint-disable-next-line no-new-func
|
|
37
|
-
const result = Function('"use strict"; return (' + expression + ')')();
|
|
38
|
-
return { result };
|
|
39
|
-
} catch {
|
|
40
|
-
return { error: 'Invalid expression' };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
async function nonStreamingExample() {
|
|
47
|
-
console.log('=== Non-streaming tool use ===');
|
|
48
|
-
const result = await generateGemini({
|
|
49
|
-
model: 'gemini-2.0-flash',
|
|
50
|
-
messages: [{ role: 'user', content: "What's the weather in Tokyo and what is 17 * 43?" }],
|
|
51
|
-
tools
|
|
52
|
-
});
|
|
53
|
-
console.log('Final answer:', result.text);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function streamingExample() {
|
|
57
|
-
console.log('\n=== Streaming tool use ===');
|
|
58
|
-
const { fullStream } = streamGemini({
|
|
59
|
-
model: 'gemini-2.0-flash',
|
|
60
|
-
messages: [{ role: 'user', content: 'What is 100 / 4? Use the calculator.' }],
|
|
61
|
-
tools
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
for await (const event of fullStream) {
|
|
65
|
-
if (event.type === 'tool-call') console.log(`[tool-call] ${event.toolName}(${JSON.stringify(event.args)})`);
|
|
66
|
-
if (event.type === 'tool-result') console.log(`[tool-result] ${JSON.stringify(event.result)}`);
|
|
67
|
-
if (event.type === 'text-delta') process.stdout.write(event.textDelta);
|
|
68
|
-
if (event.type === 'finish-step') console.log(`\n[finish] reason=${event.finishReason}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function main() {
|
|
73
|
-
await nonStreamingExample();
|
|
74
|
-
await streamingExample();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
main().catch(console.error);
|
package/examples/vision.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vision.js — Image/vision understanding examples
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates three ways to pass images:
|
|
5
|
-
* 1. Base64 inline data (Anthropic SDK style)
|
|
6
|
-
* 2. Gemini inlineData style
|
|
7
|
-
* 3. Public URL via fileData
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* GEMINI_API_KEY=your-key node examples/vision.js
|
|
11
|
-
*/
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const { generateGemini } = require('../index');
|
|
15
|
-
|
|
16
|
-
async function base64Example() {
|
|
17
|
-
console.log('=== Base64 image (Anthropic style) ===');
|
|
18
|
-
// Read a local image and encode as base64
|
|
19
|
-
// const imageBuffer = fs.readFileSync(path.join(__dirname, 'sample.jpg'));
|
|
20
|
-
// const base64 = imageBuffer.toString('base64');
|
|
21
|
-
|
|
22
|
-
// For demo purposes, use a tiny 1x1 transparent PNG
|
|
23
|
-
const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
24
|
-
|
|
25
|
-
const result = await generateGemini({
|
|
26
|
-
model: 'gemini-2.0-flash',
|
|
27
|
-
messages: [{
|
|
28
|
-
role: 'user',
|
|
29
|
-
content: [
|
|
30
|
-
{
|
|
31
|
-
type: 'image',
|
|
32
|
-
source: { type: 'base64', media_type: 'image/png', data: base64 }
|
|
33
|
-
},
|
|
34
|
-
{ type: 'text', text: 'Describe this image in one sentence.' }
|
|
35
|
-
]
|
|
36
|
-
}]
|
|
37
|
-
});
|
|
38
|
-
console.log('Response:', result.text);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function inlineDataExample() {
|
|
42
|
-
console.log('\n=== Gemini inlineData style ===');
|
|
43
|
-
const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
44
|
-
|
|
45
|
-
const result = await generateGemini({
|
|
46
|
-
model: 'gemini-2.0-flash',
|
|
47
|
-
messages: [{
|
|
48
|
-
role: 'user',
|
|
49
|
-
content: [
|
|
50
|
-
{ inlineData: { mimeType: 'image/png', data: base64 } },
|
|
51
|
-
{ type: 'text', text: 'What color is this image?' }
|
|
52
|
-
]
|
|
53
|
-
}]
|
|
54
|
-
});
|
|
55
|
-
console.log('Response:', result.text);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function publicUrlExample() {
|
|
59
|
-
console.log('\n=== Public URL via fileData ===');
|
|
60
|
-
const result = await generateGemini({
|
|
61
|
-
model: 'gemini-2.0-flash',
|
|
62
|
-
messages: [{
|
|
63
|
-
role: 'user',
|
|
64
|
-
content: [
|
|
65
|
-
{
|
|
66
|
-
fileData: {
|
|
67
|
-
mimeType: 'image/jpeg',
|
|
68
|
-
fileUri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/240px-PNG_transparency_demonstration_1.png'
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
{ type: 'text', text: 'What do you see in this image?' }
|
|
72
|
-
]
|
|
73
|
-
}]
|
|
74
|
-
});
|
|
75
|
-
console.log('Response:', result.text);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function main() {
|
|
79
|
-
await base64Example();
|
|
80
|
-
await inlineDataExample();
|
|
81
|
-
// await publicUrlExample(); // Uncomment to test with public URLs
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
main().catch(console.error);
|
package/index.d.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
export interface TextBlock { type: 'text'; text: string }
|
|
2
|
-
export interface ImageBlockBase64 { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
|
|
3
|
-
export interface ImageBlockUrl { type: 'image'; source: { type: 'url'; url: string; media_type?: string } }
|
|
4
|
-
export interface ImageBlockInline { inlineData: { mimeType: string; data: string } }
|
|
5
|
-
export interface ImageBlockFile { fileData: { mimeType: string; fileUri: string } }
|
|
6
|
-
export type ImageBlock = ImageBlockBase64 | ImageBlockUrl | ImageBlockInline | ImageBlockFile;
|
|
7
|
-
export interface ToolUseBlock { type: 'tool_use'; name: string; input: Record<string, unknown> }
|
|
8
|
-
export interface ToolResultBlock { type: 'tool_result'; name: string; content: string | Record<string, unknown> }
|
|
9
|
-
export type ContentBlock = TextBlock | ImageBlock | ToolUseBlock | ToolResultBlock;
|
|
10
|
-
export interface Message { role: 'user' | 'assistant'; content: string | ContentBlock[] }
|
|
11
|
-
export interface ToolDefinition {
|
|
12
|
-
description?: string;
|
|
13
|
-
parameters?: Record<string, unknown>;
|
|
14
|
-
execute?: (args: Record<string, unknown>, ctx?: { toolCallId: string }) => Promise<unknown>;
|
|
15
|
-
}
|
|
16
|
-
export type Tools = Record<string, ToolDefinition>;
|
|
17
|
-
export interface SafetySetting { category: string; threshold: string }
|
|
18
|
-
export interface GenerationParams {
|
|
19
|
-
model?: string | { modelId?: string; id?: string };
|
|
20
|
-
system?: string;
|
|
21
|
-
messages: Message[];
|
|
22
|
-
tools?: Tools;
|
|
23
|
-
apiKey?: string;
|
|
24
|
-
temperature?: number;
|
|
25
|
-
maxOutputTokens?: number;
|
|
26
|
-
topP?: number;
|
|
27
|
-
topK?: number;
|
|
28
|
-
safetySettings?: SafetySetting[];
|
|
29
|
-
configPath?: string;
|
|
30
|
-
taskType?: 'background' | 'think' | 'webSearch' | 'image';
|
|
31
|
-
}
|
|
32
|
-
export interface StartStepEvent { type: 'start-step' }
|
|
33
|
-
export interface TextDeltaEvent { type: 'text-delta'; textDelta: string }
|
|
34
|
-
export interface ToolCallEvent { type: 'tool-call'; toolCallId: string; toolName: string; args: Record<string, unknown> }
|
|
35
|
-
export interface ToolResultEvent { type: 'tool-result'; toolCallId: string; toolName: string; args: Record<string, unknown>; result: unknown }
|
|
36
|
-
export interface FinishStepEvent { type: 'finish-step'; finishReason: 'stop' | 'tool-calls' | 'error' }
|
|
37
|
-
export interface ErrorEvent { type: 'error'; error: Error }
|
|
38
|
-
export type StreamEvent = StartStepEvent | TextDeltaEvent | ToolCallEvent | ToolResultEvent | FinishStepEvent | ErrorEvent;
|
|
39
|
-
export interface StreamResult { fullStream: AsyncIterable<StreamEvent>; warnings: Promise<unknown[]> }
|
|
40
|
-
export interface StreamParams extends GenerationParams { onStepFinish?: () => Promise<void> | void; streamGuard?: StreamGuardOptions }
|
|
41
|
-
export function streamGemini(params: StreamParams): StreamResult;
|
|
42
|
-
export interface GenerateResult { text: string; parts: unknown[]; response: unknown }
|
|
43
|
-
export function generateGemini(params: GenerationParams): Promise<GenerateResult>;
|
|
44
|
-
|
|
45
|
-
export type TransformerEntry = string | [string, Record<string, unknown>];
|
|
46
|
-
export interface TransformerConfig {
|
|
47
|
-
use?: TransformerEntry[];
|
|
48
|
-
[modelName: string]: { use?: TransformerEntry[] } | TransformerEntry[] | undefined;
|
|
49
|
-
}
|
|
50
|
-
export interface ProviderConfig {
|
|
51
|
-
name: string;
|
|
52
|
-
api_base_url: string;
|
|
53
|
-
api_key: string;
|
|
54
|
-
models?: string[];
|
|
55
|
-
transformer?: TransformerConfig;
|
|
56
|
-
capabilities?: Partial<CapabilitySet>;
|
|
57
|
-
}
|
|
58
|
-
export interface RouterConfig {
|
|
59
|
-
default?: string;
|
|
60
|
-
background?: string;
|
|
61
|
-
think?: string;
|
|
62
|
-
longContext?: string;
|
|
63
|
-
longContextThreshold?: number;
|
|
64
|
-
webSearch?: string;
|
|
65
|
-
image?: string;
|
|
66
|
-
}
|
|
67
|
-
export interface RouterConfiguration {
|
|
68
|
-
Providers?: ProviderConfig[];
|
|
69
|
-
providers?: ProviderConfig[];
|
|
70
|
-
Router?: RouterConfig;
|
|
71
|
-
customRouter?: (params: GenerationParams, config: RouterConfig) => Promise<string | null>;
|
|
72
|
-
configPath?: string;
|
|
73
|
-
circuitBreaker?: CircuitBreakerOptions;
|
|
74
|
-
}
|
|
75
|
-
export interface RouterInstance {
|
|
76
|
-
breaker: { isOpen(name: string): boolean; recordFailure(name: string): void; recordSuccess(name: string): void };
|
|
77
|
-
stream(params: StreamParams): StreamResult;
|
|
78
|
-
generate(params: GenerationParams): Promise<GenerateResult | { text: string; response: unknown }>;
|
|
79
|
-
}
|
|
80
|
-
export function createRouter(config: RouterConfiguration): RouterInstance;
|
|
81
|
-
export function streamRouter(params: StreamParams & RouterConfiguration): StreamResult;
|
|
82
|
-
export function generateRouter(params: GenerationParams & RouterConfiguration): Promise<GenerateResult | { text: string; response: unknown }>;
|
|
83
|
-
|
|
84
|
-
export interface GeminiPart {
|
|
85
|
-
text?: string;
|
|
86
|
-
functionCall?: { name: string; args: Record<string, unknown> };
|
|
87
|
-
functionResponse?: { name: string; response: unknown };
|
|
88
|
-
inlineData?: { mimeType: string; data: string };
|
|
89
|
-
fileData?: { mimeType: string; fileUri: string };
|
|
90
|
-
}
|
|
91
|
-
export interface GeminiContent { role: 'user' | 'model'; parts: GeminiPart[] }
|
|
92
|
-
export function convertMessages(messages: Message[]): GeminiContent[];
|
|
93
|
-
export function convertTools(tools: Tools): Array<{ name: string; description: string; parameters: Record<string, unknown> }>;
|
|
94
|
-
export function cleanSchema(schema: unknown): unknown;
|
|
95
|
-
export interface StreamGuardOptions {
|
|
96
|
-
chunkTimeoutMs?: number;
|
|
97
|
-
maxRepeats?: number;
|
|
98
|
-
}
|
|
99
|
-
export interface CapabilitySet {
|
|
100
|
-
streaming: boolean;
|
|
101
|
-
toolUse: boolean;
|
|
102
|
-
vision: boolean;
|
|
103
|
-
systemMessage: boolean;
|
|
104
|
-
jsonMode: boolean;
|
|
105
|
-
}
|
|
106
|
-
export interface CircuitBreakerOptions {
|
|
107
|
-
maxFailures?: number;
|
|
108
|
-
cooldownMs?: number;
|
|
109
|
-
}
|
|
110
|
-
export class BridgeError extends Error {
|
|
111
|
-
name: string;
|
|
112
|
-
status?: number;
|
|
113
|
-
code?: string | number;
|
|
114
|
-
retryable: boolean;
|
|
115
|
-
provider?: string;
|
|
116
|
-
constructor(message: string, options?: { status?: number; code?: string | number; retryable?: boolean; provider?: string; headers?: unknown });
|
|
117
|
-
}
|
|
118
|
-
export class AuthError extends BridgeError {}
|
|
119
|
-
export class RateLimitError extends BridgeError {}
|
|
120
|
-
export class TimeoutError extends BridgeError {}
|
|
121
|
-
export class ContextWindowError extends BridgeError {}
|
|
122
|
-
export class ContentPolicyError extends BridgeError {}
|
|
123
|
-
export class ProviderError extends BridgeError {}
|
|
124
|
-
export const GeminiError: typeof BridgeError;
|
|
125
|
-
export function classifyError(status: number, message: string, provider?: string): BridgeError;
|
|
126
|
-
export function redactKeys(str: string): string;
|
package/lib/capabilities.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const DEFAULTS = {
|
|
2
|
-
streaming: true,
|
|
3
|
-
toolUse: true,
|
|
4
|
-
vision: true,
|
|
5
|
-
systemMessage: true,
|
|
6
|
-
jsonMode: false
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function getCapabilities(provider) {
|
|
10
|
-
return { ...DEFAULTS, ...(provider.capabilities || {}) };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function stripImageBlocks(messages) {
|
|
14
|
-
return messages.map(msg => {
|
|
15
|
-
if (!Array.isArray(msg.content)) return msg;
|
|
16
|
-
const filtered = msg.content.filter(b => b.type !== 'image' && b.type !== 'image_url');
|
|
17
|
-
if (filtered.length === 0) return { ...msg, content: [{ type: 'text', text: '[image removed - unsupported by provider]' }] };
|
|
18
|
-
return { ...msg, content: filtered };
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function prependSystemAsUser(messages, system) {
|
|
23
|
-
if (!system) return { messages, system: undefined };
|
|
24
|
-
const text = Array.isArray(system) ? system.map(b => b.text || '').join('\n') : system;
|
|
25
|
-
const sysMsg = { role: 'user', content: [{ type: 'text', text }] };
|
|
26
|
-
return { messages: [sysMsg, ...messages], system: undefined };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function stripUnsupported(params, caps) {
|
|
30
|
-
const warnings = [];
|
|
31
|
-
const result = { ...params };
|
|
32
|
-
if (!caps.toolUse && result.tools) {
|
|
33
|
-
delete result.tools;
|
|
34
|
-
delete result.tool_choice;
|
|
35
|
-
warnings.push('toolUse not supported — tools removed');
|
|
36
|
-
}
|
|
37
|
-
if (!caps.vision && result.messages) {
|
|
38
|
-
result.messages = stripImageBlocks(result.messages);
|
|
39
|
-
warnings.push('vision not supported — image blocks removed');
|
|
40
|
-
}
|
|
41
|
-
if (!caps.systemMessage && result.system) {
|
|
42
|
-
const { messages, system } = prependSystemAsUser(result.messages || [], result.system);
|
|
43
|
-
result.messages = messages;
|
|
44
|
-
result.system = system;
|
|
45
|
-
warnings.push('systemMessage not supported — prepended as user message');
|
|
46
|
-
}
|
|
47
|
-
return { params: result, warnings };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = { getCapabilities, stripUnsupported, DEFAULTS };
|
package/lib/circuit-breaker.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
function createCircuitBreaker(opts = {}) {
|
|
2
|
-
const maxFailures = opts.maxFailures || 5;
|
|
3
|
-
const cooldownMs = opts.cooldownMs || 60000;
|
|
4
|
-
const state = new Map();
|
|
5
|
-
|
|
6
|
-
function getState(name) {
|
|
7
|
-
if (!state.has(name)) state.set(name, { failures: 0, openedAt: 0 });
|
|
8
|
-
return state.get(name);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function isOpen(name) {
|
|
12
|
-
const s = getState(name);
|
|
13
|
-
if (s.failures < maxFailures) return false;
|
|
14
|
-
if (Date.now() - s.openedAt >= cooldownMs) {
|
|
15
|
-
s.failures = maxFailures;
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function recordFailure(name) {
|
|
22
|
-
const s = getState(name);
|
|
23
|
-
s.failures++;
|
|
24
|
-
if (s.failures >= maxFailures) s.openedAt = Date.now();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function recordSuccess(name) {
|
|
28
|
-
const s = getState(name);
|
|
29
|
-
s.failures = 0;
|
|
30
|
-
s.openedAt = 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return { isOpen, recordFailure, recordSuccess };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
module.exports = { createCircuitBreaker };
|
package/lib/client.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
const { GoogleGenAI } = require('@google/genai');
|
|
2
|
-
|
|
3
|
-
let _client = null;
|
|
4
|
-
|
|
5
|
-
function getClient(apiKey) {
|
|
6
|
-
if (!_client || apiKey) _client = new GoogleGenAI({ apiKey: apiKey || process.env.GEMINI_API_KEY });
|
|
7
|
-
return _client;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
module.exports = { getClient };
|
package/lib/cloud-generate.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
const { convertMessages, convertTools, cleanSchema, extractModelId, buildConfig } = require('./convert');
|
|
2
|
-
const { ensureAuth, CODE_ASSIST_BASE, CODE_ASSIST_HEADERS } = require('./oauth');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
function buildUserAgent(model) {
|
|
6
|
-
return `gemini-cli/0.30.0 (node; ${process.platform}) model/${model || 'unknown'}`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async function cloudGenerate({ model, system, messages, tools, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities, authPort }) {
|
|
10
|
-
const tokens = await ensureAuth(authPort);
|
|
11
|
-
const modelId = extractModelId(model);
|
|
12
|
-
const contents = convertMessages(messages);
|
|
13
|
-
const { config } = buildConfig({ system, tools, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities });
|
|
14
|
-
|
|
15
|
-
const request = { contents };
|
|
16
|
-
if (config.systemInstruction) request.systemInstruction = { parts: [{ text: config.systemInstruction }] };
|
|
17
|
-
if (config.tools) request.tools = config.tools;
|
|
18
|
-
const genConfig = {};
|
|
19
|
-
if (config.maxOutputTokens) genConfig.maxOutputTokens = config.maxOutputTokens;
|
|
20
|
-
if (config.temperature != null) genConfig.temperature = config.temperature;
|
|
21
|
-
if (config.topP != null) genConfig.topP = config.topP;
|
|
22
|
-
if (config.topK != null) genConfig.topK = config.topK;
|
|
23
|
-
if (config.responseModalities) genConfig.responseModalities = config.responseModalities;
|
|
24
|
-
if (Object.keys(genConfig).length) request.generationConfig = genConfig;
|
|
25
|
-
|
|
26
|
-
const envelope = { project: tokens.projectId, model: modelId, user_prompt_id: crypto.randomUUID(), request };
|
|
27
|
-
|
|
28
|
-
const res = await fetch(`${CODE_ASSIST_BASE}:generateContent`, {
|
|
29
|
-
method: 'POST',
|
|
30
|
-
headers: {
|
|
31
|
-
'Content-Type': 'application/json',
|
|
32
|
-
Authorization: `Bearer ${tokens.accessToken}`,
|
|
33
|
-
'User-Agent': buildUserAgent(modelId),
|
|
34
|
-
'x-activity-request-id': crypto.randomUUID(),
|
|
35
|
-
...CODE_ASSIST_HEADERS
|
|
36
|
-
},
|
|
37
|
-
body: JSON.stringify(envelope)
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (!res.ok) throw new Error(`Cloud generate failed (${res.status}): ${await res.text()}`);
|
|
41
|
-
const data = await res.json();
|
|
42
|
-
const inner = data.response || data;
|
|
43
|
-
const candidate = inner.candidates?.[0];
|
|
44
|
-
if (!candidate) throw new Error('No candidates returned');
|
|
45
|
-
const allParts = candidate.content?.parts || [];
|
|
46
|
-
const text = allParts.filter(p => p.text && !p.thought).map(p => p.text).join('');
|
|
47
|
-
return { text, parts: allParts, response: inner };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function* cloudStream({ model, system, messages, tools, onStepFinish, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities, authPort }) {
|
|
51
|
-
const tokens = await ensureAuth(authPort);
|
|
52
|
-
const modelId = extractModelId(model);
|
|
53
|
-
const contents = convertMessages(messages);
|
|
54
|
-
const { config } = buildConfig({ system, tools, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities });
|
|
55
|
-
|
|
56
|
-
const request = { contents };
|
|
57
|
-
if (config.systemInstruction) request.systemInstruction = { parts: [{ text: config.systemInstruction }] };
|
|
58
|
-
if (config.tools) request.tools = config.tools;
|
|
59
|
-
const genConfig = {};
|
|
60
|
-
if (config.maxOutputTokens) genConfig.maxOutputTokens = config.maxOutputTokens;
|
|
61
|
-
if (config.temperature != null) genConfig.temperature = config.temperature;
|
|
62
|
-
if (config.topP != null) genConfig.topP = config.topP;
|
|
63
|
-
if (config.topK != null) genConfig.topK = config.topK;
|
|
64
|
-
if (config.responseModalities) genConfig.responseModalities = config.responseModalities;
|
|
65
|
-
if (Object.keys(genConfig).length) request.generationConfig = genConfig;
|
|
66
|
-
|
|
67
|
-
const envelope = { project: tokens.projectId, model: modelId, user_prompt_id: crypto.randomUUID(), request };
|
|
68
|
-
|
|
69
|
-
const res = await fetch(`${CODE_ASSIST_BASE}:streamGenerateContent?alt=sse`, {
|
|
70
|
-
method: 'POST',
|
|
71
|
-
headers: {
|
|
72
|
-
'Content-Type': 'application/json',
|
|
73
|
-
Authorization: `Bearer ${tokens.accessToken}`,
|
|
74
|
-
'User-Agent': buildUserAgent(modelId),
|
|
75
|
-
'x-activity-request-id': crypto.randomUUID(),
|
|
76
|
-
Accept: 'text/event-stream',
|
|
77
|
-
...CODE_ASSIST_HEADERS
|
|
78
|
-
},
|
|
79
|
-
body: JSON.stringify(envelope)
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (!res.ok) throw new Error(`Cloud stream failed (${res.status}): ${await res.text()}`);
|
|
83
|
-
|
|
84
|
-
yield { type: 'start-step' };
|
|
85
|
-
const reader = res.body.getReader();
|
|
86
|
-
const decoder = new TextDecoder();
|
|
87
|
-
let buffer = '';
|
|
88
|
-
|
|
89
|
-
while (true) {
|
|
90
|
-
const { done, value } = await reader.read();
|
|
91
|
-
if (done) break;
|
|
92
|
-
buffer += decoder.decode(value, { stream: true });
|
|
93
|
-
const lines = buffer.split('\n');
|
|
94
|
-
buffer = lines.pop() || '';
|
|
95
|
-
for (const line of lines) {
|
|
96
|
-
const trimmed = line.trim();
|
|
97
|
-
if (!trimmed.startsWith('data:')) continue;
|
|
98
|
-
const json = trimmed.slice(5).trim();
|
|
99
|
-
if (!json || json === '[DONE]') continue;
|
|
100
|
-
try {
|
|
101
|
-
const parsed = JSON.parse(json);
|
|
102
|
-
const inner = parsed.response || parsed;
|
|
103
|
-
const parts = inner.candidates?.[0]?.content?.parts || [];
|
|
104
|
-
for (const part of parts) {
|
|
105
|
-
if (part.text && !part.thought) yield { type: 'text-delta', textDelta: part.text };
|
|
106
|
-
if (part.inlineData) yield { type: 'image-data', inlineData: part.inlineData };
|
|
107
|
-
}
|
|
108
|
-
} catch {}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
yield { type: 'finish-step', finishReason: 'stop' };
|
|
112
|
-
if (onStepFinish) await onStepFinish();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function streamCloud(params) {
|
|
116
|
-
return { fullStream: cloudStream(params), warnings: Promise.resolve([]) };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
module.exports = { cloudGenerate, cloudStream, streamCloud };
|
package/lib/config.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
function interpolateEnv(val) {
|
|
6
|
-
if (typeof val === 'string') return val.replace(/\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)/g, (_, a, b) => process.env[a || b] || '');
|
|
7
|
-
if (Array.isArray(val)) return val.map(interpolateEnv);
|
|
8
|
-
if (val && typeof val === 'object') {
|
|
9
|
-
const out = {};
|
|
10
|
-
for (const [k, v] of Object.entries(val)) out[k] = interpolateEnv(v);
|
|
11
|
-
return out;
|
|
12
|
-
}
|
|
13
|
-
return val;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function loadConfig(configPath) {
|
|
17
|
-
const fp = configPath || process.env.THEBIRD_CONFIG || path.join(os.homedir(), '.thebird', 'config.json');
|
|
18
|
-
try {
|
|
19
|
-
const raw = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
20
|
-
return interpolateEnv(raw);
|
|
21
|
-
} catch { return {}; }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = { loadConfig, interpolateEnv };
|