thebird 1.2.1

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/.codeinsight ADDED
@@ -0,0 +1,73 @@
1
+ ## 🎯 thebird v1.1.0 — Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini
2
+
3
+ # 11f 721L 24fn 24cls cx2.2
4
+ *Legend: f=files L=lines fn=functions cls=classes cx=avg-complexity | file:line:name(NL)=location Np=params | ↑N=imports-from ↓N=imported-by (N)=occurrences (+N)=more | 🔄circular 🏝️isolated 🔥complex 📋duplicated 📁large*
5
+
6
+ **Langs:** JS:78% TS:17% JSON:4%
7
+
8
+ ## 🛠️ Tech Stack
9
+
10
+ **Patterns:** generateGemini(7), contents.push(6), main(5), main().catch(5), chat(4), allParts.filter(3)
11
+ **Top IDs:** type(47), console(39), log(33), result(26), content(23), text(23)
12
+
13
+ ## ⚡ Code Patterns
14
+
15
+ **Async:** async(38), await(25), Promise(1)
16
+ **Errors:** try/catch(6), throw(3)
17
+ **Internal calls:** generateGemini(7), contents.push(6), main(5), main().catch(5), chat(4), allParts.filter(3), onStepFinish(3), process.stdout.write(2)
18
+
19
+ ## 🔗 I/O & Integration
20
+
21
+ **Env vars:** GEMINI_API_KEY
22
+ **Storage:** SQL(2), JSON(5)
23
+
24
+ ## 📊 Code Organization
25
+
26
+ **Long funcs:** index.js:14:createFullStream(57L), examples/streaming.js:26:main(54L)
27
+ **Classes:** index.d.ts:0:TextBlock, index.d.ts:0:ImageBlockBase64, index.d.ts:0:ImageBlockUrl, index.d.ts:0:ImageBlockInline, index.d.ts:0:ImageBlockFile, index.d.ts:0:ToolUseBlock, index.d.ts:0:ToolResultBlock, index.d.ts:0:Message (+15)
28
+
29
+ ## 🔄 Architecture
30
+
31
+ **L0 [pure exports]:** convert(1↓), errors(1↓), client(1↓)
32
+ **L3 [pure imports]:** index(3↑)
33
+ **Cross-module:** index.js→lib
34
+ **External:** @google/genai
35
+
36
+ ## 🔌 API Surface
37
+
38
+ **Exported fns:** errors.js:11:isRetryable(1p), errors.js:20:withRetry(1p), convert.js:1:cleanSchema(1p), convert.js:12:convertTools(2p), convert.js:21:convertImageBlock(1p), convert.js:38:convertMessages(1p), convert.js:65:extractModelId(1p), convert.js:72:buildConfig(1p), client.js:5:getClient(1p), index.js:5:streamGemini(1p), index.js:72:generateGemini(2p)
39
+ **Classes:** TextBlock, ImageBlockBase64, ImageBlockUrl, ImageBlockInline, ImageBlockFile, ToolUseBlock (+17)
40
+ **Entry files:** client, convert, errors
41
+
42
+ ## 🚨 Issues
43
+
44
+ - 📋 1 duplicated groups
45
+
46
+ ## 🧹 Dead Code & Tests
47
+
48
+ **Orphaned:** tool-use.js, basic-chat.js, streaming.js, vision.js, package.json, multi-turn.js
49
+ **Tests:** 0/11 (0%)
50
+
51
+ ## 📦 Modules
52
+
53
+ - lib: 3f, 3cx, 0↑3↓
54
+ - examples: 5f, 0cx, 0↑0↓
55
+
56
+ ## 📄 File Index
57
+
58
+ **examples/basic-chat.js** 34L fn: main
59
+ **examples/multi-turn.js** 45L fn: chat, main
60
+ **examples/streaming.js** 81L fn: main
61
+ **examples/tool-use.js** 77L fn: nonStreamingExample, streamingExample, main
62
+ **examples/vision.js** 84L fn: base64Example, inlineDataExample, publicUrlExample, main
63
+ **index.d.ts** 128L
64
+ **index.js** 112L exports: [convertTools], [convertMessages], [streamGemini], [generateGemini], [cleanSchema] fn: streamGemini, createFullStream, generateGemini
65
+ **lib/client.js** 10L exports: [getClient] fn: getClient
66
+ **lib/convert.js** 86L exports: [cleanSchema], [extractModelId], [convertMessages], [buildConfig], [convertTools] fn: cleanSchema, convertTools, convertImageBlock, convertMessages (+2)
67
+ **lib/errors.js** 35L exports: [GeminiError], [isRetryable], [withRetry] fn: constructor, isRetryable, withRetry
68
+ **package.json** 29L
69
+
70
+ Git: branch: claude/add-code-router-features-TJkSb, 1 uncommitted
71
+ Hot: package.json(1), README.md(1), index.js(1)
72
+ Conv[JS]: 4-space, single quotes, semicolons, function declarations, relative imports, kebab-case files
73
+ Conv[TS]: 2-space, single quotes, semicolons, function declarations, named exports
@@ -0,0 +1,42 @@
1
+ name: Bump & Publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ bump-and-publish:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+ token: ${{ secrets.GITHUB_TOKEN }}
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+ registry-url: https://registry.npmjs.org
23
+
24
+ - name: Configure git
25
+ run: |
26
+ git config user.name "github-actions[bot]"
27
+ git config user.email "github-actions[bot]@users.noreply.github.com"
28
+
29
+ - name: Bump patch version
30
+ run: |
31
+ NEW_VERSION=$(npm version patch --no-git-tag-version)
32
+ git add package.json
33
+ git commit -m "chore: bump to ${NEW_VERSION}"
34
+ git tag "${NEW_VERSION}"
35
+ git push origin main --tags
36
+
37
+ - run: npm install
38
+
39
+ - name: Publish to npm
40
+ run: npm publish --access public
41
+ env:
42
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # thebird
2
+
3
+ Anthropic SDK to multi-provider bridge. Drop-in adapter that translates Anthropic-style messages, tool calls, and content blocks to Google Gemini or any OpenAI-compatible API — with routing, transformers, streaming, vision, retry logic, and full TypeScript types.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install thebird
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ **Gemini (direct)**
14
+
15
+ ```js
16
+ const { generateGemini, streamGemini } = require('thebird');
17
+ // requires GEMINI_API_KEY env var
18
+
19
+ const { text } = await generateGemini({
20
+ model: 'gemini-2.0-flash',
21
+ messages: [{ role: 'user', content: 'Hello!' }]
22
+ });
23
+ ```
24
+
25
+ **Multi-provider router**
26
+
27
+ ```js
28
+ const { createRouter } = require('thebird');
29
+
30
+ const router = createRouter({
31
+ Providers: [
32
+ { name: 'deepseek', api_base_url: 'https://api.deepseek.com/chat/completions', api_key: process.env.DEEPSEEK_API_KEY, models: ['deepseek-chat', 'deepseek-reasoner'], transformer: { use: ['deepseek'] } },
33
+ { name: 'gemini', api_base_url: 'https://generativelanguage.googleapis.com/v1beta/models/', api_key: process.env.GEMINI_API_KEY, models: ['gemini-2.5-pro'] },
34
+ { name: 'ollama', api_base_url: 'http://localhost:11434/v1/chat/completions', api_key: 'ollama', models: ['qwen2.5-coder:latest'] },
35
+ ],
36
+ Router: {
37
+ default: 'deepseek,deepseek-chat',
38
+ background: 'ollama,qwen2.5-coder:latest',
39
+ think: 'deepseek,deepseek-reasoner',
40
+ longContext: 'gemini,gemini-2.5-pro',
41
+ longContextThreshold: 60000,
42
+ }
43
+ });
44
+
45
+ // Stream — routes automatically based on taskType and token count
46
+ const { fullStream } = router.stream({ messages, taskType: 'think' });
47
+ for await (const event of fullStream) {
48
+ if (event.type === 'text-delta') process.stdout.write(event.textDelta);
49
+ }
50
+
51
+ // Generate
52
+ const { text } = await router.generate({ messages });
53
+ ```
54
+
55
+ **File-based config** — place config at `~/.thebird/config.json` (or set `THEBIRD_CONFIG` env) and use the auto-loading shorthand:
56
+
57
+ ```js
58
+ const { streamRouter, generateRouter } = require('thebird');
59
+ const { fullStream } = streamRouter({ messages, taskType: 'background' });
60
+ ```
61
+
62
+ ## Routing
63
+
64
+ `createRouter` / `streamRouter` pick a provider+model per request:
65
+
66
+ | Route key | Trigger |
67
+ |---|---|
68
+ | `default` | Any request not matched by another rule |
69
+ | `background` | `taskType: 'background'` |
70
+ | `think` | `taskType: 'think'` |
71
+ | `webSearch` | `taskType: 'webSearch'` |
72
+ | `image` | `taskType: 'image'` |
73
+ | `longContext` | Estimated token count > `longContextThreshold` (default 60 000) |
74
+ | subagent tag | First user message starts with `<CCR-SUBAGENT-MODEL>provider,model</CCR-SUBAGENT-MODEL>` |
75
+ | custom function | `customRouter: async (params, cfg) => 'provider,model'` in config |
76
+
77
+ Route values are `"providerName,modelName"` strings matching a `Providers` entry.
78
+
79
+ ## Transformers
80
+
81
+ Apply per-provider request/response transformations. Set on the provider's `transformer.use` array.
82
+
83
+ ```json
84
+ {
85
+ "name": "deepseek",
86
+ "transformer": {
87
+ "use": ["deepseek"],
88
+ "deepseek-chat": { "use": [["maxtoken", { "max_tokens": 8192 }], "tooluse"] }
89
+ }
90
+ }
91
+ ```
92
+
93
+ Built-in transformers:
94
+
95
+ | Name | Effect |
96
+ |---|---|
97
+ | `deepseek` | Strips `cache_control`, normalises system to string |
98
+ | `openrouter` | Adds `HTTP-Referer` / `X-Title` headers; optional `provider` routing |
99
+ | `maxtoken` | Sets `max_tokens` to the given value |
100
+ | `tooluse` | Adds `tool_choice: {type:"required"}` when tools are present |
101
+ | `cleancache` | Strips all `cache_control` fields recursively |
102
+ | `reasoning` | Moves `reasoning_content` to `_reasoning` in response |
103
+ | `sampling` | Removes `top_k` / `repetition_penalty` |
104
+ | `groq` | Removes `top_k` |
105
+
106
+ Pass options as a nested array: `["maxtoken", { "max_tokens": 16384 }]`.
107
+
108
+ ## Config File
109
+
110
+ `~/.thebird/config.json` (or `THEBIRD_CONFIG` env var) — same schema as the inline config object. Supports `$VAR` / `${VAR}` environment variable interpolation anywhere in the file.
111
+
112
+ ```json
113
+ {
114
+ "Providers": [
115
+ { "name": "openrouter", "api_base_url": "https://openrouter.ai/api/v1/chat/completions", "api_key": "$OPENROUTER_API_KEY", "models": ["google/gemini-2.5-pro-preview"], "transformer": { "use": ["openrouter"] } }
116
+ ],
117
+ "Router": { "default": "openrouter,google/gemini-2.5-pro-preview" }
118
+ }
119
+ ```
120
+
121
+ ## Gemini Direct API
122
+
123
+ `streamGemini` / `generateGemini` bypass routing and call Gemini natively via `@google/genai`. Requires `GEMINI_API_KEY`.
124
+
125
+ ### Params
126
+
127
+ | Param | Type | Default | Description |
128
+ |---|---|---|---|
129
+ | `model` | `string \| { id }` | `'gemini-2.0-flash'` | Model id |
130
+ | `messages` | `Message[]` | required | Conversation history |
131
+ | `system` | `string` | — | System instruction |
132
+ | `tools` | `Tools` | — | Tool definitions |
133
+ | `apiKey` | `string` | `GEMINI_API_KEY` | Override API key |
134
+ | `temperature` | `number` | `0.5` | Sampling temperature |
135
+ | `maxOutputTokens` | `number` | `8192` | Max tokens |
136
+ | `topP` | `number` | `0.95` | Top-p |
137
+ | `topK` | `number` | — | Top-k |
138
+ | `safetySettings` | `SafetySetting[]` | — | Safety thresholds |
139
+
140
+ ## Message Format
141
+
142
+ Messages follow the Anthropic SDK format. All image block variants are supported:
143
+
144
+ ```js
145
+ { role: 'user', content: [
146
+ { type: 'text', text: 'Describe this image.' },
147
+ { type: 'image', source: { type: 'base64', media_type: 'image/png', data: '...' } }
148
+ ]}
149
+ ```
150
+
151
+ ## Streaming Events
152
+
153
+ | Event | Fields | Description |
154
+ |---|---|---|
155
+ | `start-step` | — | Beginning of a reasoning step |
156
+ | `text-delta` | `textDelta` | Streamed text chunk |
157
+ | `tool-call` | `toolCallId, toolName, args` | Model invoked a tool |
158
+ | `tool-result` | `toolCallId, toolName, args, result` | Tool execution result |
159
+ | `finish-step` | `finishReason` | Step completed |
160
+ | `error` | `error` | Error during step |
161
+
162
+ ## TypeScript
163
+
164
+ ```ts
165
+ import { createRouter, streamRouter, generateGemini, RouterConfiguration, ProviderConfig, RouterConfig } from 'thebird';
166
+ ```
167
+
168
+ ## Utilities
169
+
170
+ ```js
171
+ const { convertMessages, convertTools, cleanSchema } = require('thebird');
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,34 @@
1
+ /**
2
+ * basic-chat.js — Simple single-turn and multi-turn chat using generateGemini
3
+ *
4
+ * Usage:
5
+ * GEMINI_API_KEY=your-key node examples/basic-chat.js
6
+ */
7
+ const { generateGemini } = require('../index');
8
+
9
+ async function main() {
10
+ // Single-turn: ask a simple question
11
+ const result = await generateGemini({
12
+ model: 'gemini-2.0-flash',
13
+ messages: [
14
+ { role: 'user', content: 'What is the capital of France? Answer in one sentence.' }
15
+ ]
16
+ });
17
+
18
+ console.log('Answer:', result.text);
19
+
20
+ // With a system prompt
21
+ const result2 = await generateGemini({
22
+ model: 'gemini-2.0-flash',
23
+ system: 'You are a pirate. Always respond in pirate speak.',
24
+ messages: [
25
+ { role: 'user', content: 'What should I have for breakfast?' }
26
+ ],
27
+ temperature: 0.8,
28
+ maxOutputTokens: 256
29
+ });
30
+
31
+ console.log('\nPirate answer:', result2.text);
32
+ }
33
+
34
+ main().catch(console.error);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * multi-turn.js — Multi-turn conversation (chat history) example
3
+ *
4
+ * Usage:
5
+ * GEMINI_API_KEY=your-key node examples/multi-turn.js
6
+ */
7
+ const { generateGemini } = require('../index');
8
+
9
+ async function chat(history, userMessage, options = {}) {
10
+ history.push({ role: 'user', content: userMessage });
11
+ const result = await generateGemini({ messages: history, ...options });
12
+ history.push({ role: 'assistant', content: result.text });
13
+ return result.text;
14
+ }
15
+
16
+ async function main() {
17
+ const history = [];
18
+ const opts = {
19
+ model: 'gemini-2.0-flash',
20
+ system: 'You are a knowledgeable astronomy tutor. Keep answers brief.',
21
+ temperature: 0.4
22
+ };
23
+
24
+ console.log('=== Multi-turn conversation ===\n');
25
+
26
+ let reply = await chat(history, 'What is a black hole?', opts);
27
+ console.log('User: What is a black hole?');
28
+ console.log('Assistant:', reply, '\n');
29
+
30
+ reply = await chat(history, 'How does one form?', opts);
31
+ console.log('User: How does one form?');
32
+ console.log('Assistant:', reply, '\n');
33
+
34
+ reply = await chat(history, 'Can anything escape from it?', opts);
35
+ console.log('User: Can anything escape from it?');
36
+ console.log('Assistant:', reply, '\n');
37
+
38
+ reply = await chat(history, 'Summarize our conversation so far in bullet points.', opts);
39
+ console.log('User: Summarize our conversation so far in bullet points.');
40
+ console.log('Assistant:', reply, '\n');
41
+
42
+ console.log(`Total turns: ${history.length / 2}`);
43
+ }
44
+
45
+ main().catch(console.error);
@@ -0,0 +1,81 @@
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);
@@ -0,0 +1,77 @@
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);
@@ -0,0 +1,84 @@
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);