thebird 1.2.71 → 1.2.73
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/.github/workflows/auto-declaudeify.yml +44 -0
- package/index.js +6 -79
- package/lib/errors.js +79 -5
- package/lib/providers/openai.js +14 -7
- package/lib/router-stream.js +83 -0
- package/lib/stream-guard.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Auto-Declaudeify
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ['**']
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
declaudeify:
|
|
9
|
+
if: contains(github.event.head_commit.author.name, 'Claude') || contains(github.event.head_commit.author.email, 'claude')
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
|
|
17
|
+
- name: Check for Claude commits
|
|
18
|
+
id: check
|
|
19
|
+
run: |
|
|
20
|
+
CLAUDE_COMMITS=$(git log --all --author="Claude" --oneline | wc -l)
|
|
21
|
+
echo "count=$CLAUDE_COMMITS" >> $GITHUB_OUTPUT
|
|
22
|
+
if [ $CLAUDE_COMMITS -gt 0 ]; then
|
|
23
|
+
echo "Found $CLAUDE_COMMITS Claude commits, will filter"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
- name: Filter Claude from history
|
|
27
|
+
if: steps.check.outputs.count > 0
|
|
28
|
+
run: |
|
|
29
|
+
git filter-branch --force --env-filter \
|
|
30
|
+
'if [ "$GIT_AUTHOR_NAME" = "Claude" ]; then \
|
|
31
|
+
export GIT_AUTHOR_NAME="lanmower"; \
|
|
32
|
+
export GIT_AUTHOR_EMAIL="lanmower@lanmower.com"; \
|
|
33
|
+
fi; \
|
|
34
|
+
if [ "$GIT_COMMITTER_NAME" = "Claude" ]; then \
|
|
35
|
+
export GIT_COMMITTER_NAME="lanmower"; \
|
|
36
|
+
export GIT_COMMITTER_EMAIL="lanmower@lanmower.com"; \
|
|
37
|
+
fi' \
|
|
38
|
+
--tag-name-filter cat -- --all
|
|
39
|
+
|
|
40
|
+
- name: Force push filtered history
|
|
41
|
+
if: steps.check.outputs.count > 0
|
|
42
|
+
run: git push --all --force
|
|
43
|
+
env:
|
|
44
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
const { getClient } = require('./lib/client');
|
|
2
2
|
const { GeminiError, withRetry } = require('./lib/errors');
|
|
3
3
|
const { convertMessages, convertTools, cleanSchema, extractModelId, buildConfig } = require('./lib/convert');
|
|
4
|
-
const {
|
|
5
|
-
const { route } = require('./lib/router');
|
|
6
|
-
const { resolveTransformers, applyRequestTransformers } = require('./lib/transformers');
|
|
7
|
-
const openaiProv = require('./lib/providers/openai');
|
|
4
|
+
const { guardStream } = require('./lib/stream-guard');
|
|
8
5
|
|
|
9
6
|
function streamGemini({ model, system, messages, tools, onStepFinish, apiKey,
|
|
10
7
|
temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
@@ -14,7 +11,7 @@ function streamGemini({ model, system, messages, tools, onStepFinish, apiKey,
|
|
|
14
11
|
};
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
async function* createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities }) {
|
|
14
|
+
async function* createFullStream({ model, system, messages, tools, onStepFinish, apiKey, temperature, maxOutputTokens, topP, topK, safetySettings, responseModalities, streamGuard }) {
|
|
18
15
|
const client = getClient(apiKey);
|
|
19
16
|
const modelId = extractModelId(model);
|
|
20
17
|
let contents = convertMessages(messages);
|
|
@@ -24,7 +21,7 @@ async function* createFullStream({ model, system, messages, tools, onStepFinish,
|
|
|
24
21
|
try {
|
|
25
22
|
const stream = await withRetry(() => client.models.generateContentStream({ model: modelId, contents, config }));
|
|
26
23
|
const allParts = [];
|
|
27
|
-
for await (const chunk of stream) {
|
|
24
|
+
for await (const chunk of guardStream(stream, streamGuard)) {
|
|
28
25
|
for (const candidate of (chunk.candidates || [])) {
|
|
29
26
|
for (const part of (candidate.content?.parts || [])) {
|
|
30
27
|
allParts.push(part);
|
|
@@ -98,79 +95,9 @@ async function generateGemini({ model, system, messages, tools, apiKey, temperat
|
|
|
98
95
|
}
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
return p.name === 'gemini' || (p.api_base_url || '').includes('generativelanguage.googleapis.com');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function findProvider(providers, providerName, modelName) {
|
|
106
|
-
if (providerName) return providers.find(p => p.name === providerName);
|
|
107
|
-
if (modelName) return providers.find(p => (p.models || []).includes(modelName));
|
|
108
|
-
return providers[0];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function buildOpenAIUrl(base) {
|
|
112
|
-
const clean = (base || '').replace(/\/$/g, '');
|
|
113
|
-
return clean.includes('/completions') ? clean : clean + '/chat/completions';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function resolveForProvider(provider, model, customMap) {
|
|
117
|
-
const useList = provider.transformer?.[model]?.use || provider.transformer?.use || [];
|
|
118
|
-
return resolveTransformers(useList, customMap);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function* routerStream(params, resolver) {
|
|
122
|
-
const { provider, actualModel, transformers } = await resolver(params);
|
|
123
|
-
if (isGeminiProvider(provider)) {
|
|
124
|
-
yield* createFullStream({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
|
|
125
|
-
} else {
|
|
126
|
-
const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
|
|
127
|
-
const oaiTools = openaiProv.convertTools(params.tools);
|
|
128
|
-
let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
|
|
129
|
-
if (oaiTools) req.tools = oaiTools;
|
|
130
|
-
req = applyRequestTransformers(req, transformers);
|
|
131
|
-
yield* openaiProv.streamOpenAI({ url: buildOpenAIUrl(provider.api_base_url), apiKey: provider.api_key, headers: req._extraHeaders, body: req, tools: params.tools, onStepFinish: params.onStepFinish });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function createRouter(config) {
|
|
136
|
-
const providers = config.Providers || config.providers || [];
|
|
137
|
-
const routerCfg = config.Router || {};
|
|
138
|
-
async function resolve(params) {
|
|
139
|
-
const { providerName, modelName } = await route(params, routerCfg, config.customRouter);
|
|
140
|
-
const provider = findProvider(providers, providerName, modelName) || providers[0];
|
|
141
|
-
if (!provider) throw new Error('[thebird] no provider configured');
|
|
142
|
-
const actualModel = modelName || (provider.models || [])[0] || extractModelId(params.model) || 'gemini-2.0-flash';
|
|
143
|
-
const transformers = resolveForProvider(provider, actualModel, config._transformers);
|
|
144
|
-
return { provider, actualModel, transformers };
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
stream(params) { return { fullStream: routerStream(params, resolve), warnings: Promise.resolve([]) }; },
|
|
148
|
-
async generate(params) {
|
|
149
|
-
const { provider, actualModel, transformers } = await resolve(params);
|
|
150
|
-
if (isGeminiProvider(provider)) return generateGemini({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
|
|
151
|
-
const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
|
|
152
|
-
const oaiTools = openaiProv.convertTools(params.tools);
|
|
153
|
-
let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
|
|
154
|
-
if (oaiTools) req.tools = oaiTools;
|
|
155
|
-
req = applyRequestTransformers(req, transformers);
|
|
156
|
-
return openaiProv.generateOpenAI({ url: buildOpenAIUrl(provider.api_base_url), apiKey: provider.api_key, headers: req._extraHeaders, body: req, tools: params.tools });
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function streamRouter(params) {
|
|
162
|
-
const config = loadConfig(params.configPath);
|
|
163
|
-
if (!(config.Providers || config.providers)?.length) return streamGemini(params);
|
|
164
|
-
return createRouter(config).stream(params);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function generateRouter(params) {
|
|
168
|
-
const config = loadConfig(params.configPath);
|
|
169
|
-
if (!(config.Providers || config.providers)?.length) return generateGemini(params);
|
|
170
|
-
return createRouter(config).generate(params);
|
|
171
|
-
}
|
|
172
|
-
|
|
98
|
+
const { streamRouter, generateRouter, createRouter } = require('./lib/router-stream');
|
|
173
99
|
const { cloudGenerate, streamCloud, cloudStream } = require('./lib/cloud-generate');
|
|
174
100
|
const { ensureAuth, login: oauthLogin } = require('./lib/oauth');
|
|
101
|
+
const { TimeoutError } = require('./lib/stream-guard');
|
|
175
102
|
|
|
176
|
-
module.exports = { streamGemini, generateGemini, streamRouter, generateRouter, createRouter, convertMessages, convertTools, cleanSchema, GeminiError, cloudGenerate, streamCloud, cloudStream, ensureAuth, oauthLogin };
|
|
103
|
+
module.exports = { streamGemini, createFullStream, generateGemini, streamRouter, generateRouter, createRouter, convertMessages, convertTools, cleanSchema, GeminiError, TimeoutError, cloudGenerate, streamCloud, cloudStream, ensureAuth, oauthLogin };
|
package/lib/errors.js
CHANGED
|
@@ -1,15 +1,73 @@
|
|
|
1
|
-
class
|
|
2
|
-
constructor(message, { status, code, retryable = false } = {}) {
|
|
1
|
+
class BridgeError extends Error {
|
|
2
|
+
constructor(message, { status, code, retryable = false, provider, headers } = {}) {
|
|
3
3
|
super(message);
|
|
4
|
-
this.name = '
|
|
4
|
+
this.name = 'BridgeError';
|
|
5
5
|
this.status = status;
|
|
6
6
|
this.code = code;
|
|
7
7
|
this.retryable = retryable;
|
|
8
|
+
this.provider = provider;
|
|
9
|
+
this.headers = headers;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
class AuthError extends BridgeError {
|
|
14
|
+
constructor(message, opts = {}) {
|
|
15
|
+
super(message, { ...opts, retryable: false });
|
|
16
|
+
this.name = 'AuthError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class RateLimitError extends BridgeError {
|
|
21
|
+
constructor(message, opts = {}) {
|
|
22
|
+
super(message, { ...opts, retryable: true });
|
|
23
|
+
this.name = 'RateLimitError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class TimeoutError extends BridgeError {
|
|
28
|
+
constructor(message, opts = {}) {
|
|
29
|
+
super(message, { ...opts, retryable: true });
|
|
30
|
+
this.name = 'TimeoutError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class ContextWindowError extends BridgeError {
|
|
35
|
+
constructor(message, opts = {}) {
|
|
36
|
+
super(message, { ...opts, retryable: false });
|
|
37
|
+
this.name = 'ContextWindowError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ContentPolicyError extends BridgeError {
|
|
42
|
+
constructor(message, opts = {}) {
|
|
43
|
+
super(message, { ...opts, retryable: false });
|
|
44
|
+
this.name = 'ContentPolicyError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class ProviderError extends BridgeError {
|
|
49
|
+
constructor(message, opts = {}) {
|
|
50
|
+
super(message, opts);
|
|
51
|
+
this.name = 'ProviderError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const GeminiError = BridgeError;
|
|
56
|
+
|
|
57
|
+
function classifyError(status, message, provider) {
|
|
58
|
+
const opts = { status, provider };
|
|
59
|
+
const msg = message || '';
|
|
60
|
+
if (status === 401 || status === 403) return new AuthError(msg, opts);
|
|
61
|
+
if (status === 429) return new RateLimitError(msg, opts);
|
|
62
|
+
if (status === 408 || /timeout/i.test(msg)) return new TimeoutError(msg, opts);
|
|
63
|
+
if (status === 413 || /context.?length|token.?limit|too.?long/i.test(msg)) return new ContextWindowError(msg, opts);
|
|
64
|
+
if (status === 451 || /safety|blocked|content.?policy|harmful/i.test(msg)) return new ContentPolicyError(msg, opts);
|
|
65
|
+
if (typeof status === 'number' && status >= 500) return new ProviderError(msg, { ...opts, retryable: true });
|
|
66
|
+
return new BridgeError(msg, { ...opts, retryable: false });
|
|
67
|
+
}
|
|
68
|
+
|
|
11
69
|
function isRetryable(err) {
|
|
12
|
-
if (err instanceof
|
|
70
|
+
if (err instanceof BridgeError) return err.retryable;
|
|
13
71
|
const status = err?.status ?? err?.code;
|
|
14
72
|
if (status === 429) return true;
|
|
15
73
|
if (typeof status === 'number' && status >= 500) return true;
|
|
@@ -17,7 +75,19 @@ function isRetryable(err) {
|
|
|
17
75
|
return /quota|rate.?limit|overloaded|unavailable/i.test(msg);
|
|
18
76
|
}
|
|
19
77
|
|
|
78
|
+
function parseRetryAfterHeader(err) {
|
|
79
|
+
const raw = err?.headers?.get?.('retry-after') ?? err?.retryAfter;
|
|
80
|
+
if (raw == null) return null;
|
|
81
|
+
const secs = Number(raw);
|
|
82
|
+
if (!isNaN(secs) && secs >= 0) return secs * 1000;
|
|
83
|
+
const date = Date.parse(raw);
|
|
84
|
+
if (!isNaN(date)) return Math.max(0, date - Date.now());
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
20
88
|
function parseRetryDelay(err) {
|
|
89
|
+
const headerDelay = parseRetryAfterHeader(err);
|
|
90
|
+
if (headerDelay != null) return headerDelay;
|
|
21
91
|
try {
|
|
22
92
|
const body = typeof err.message === 'string' ? JSON.parse(err.message) : err.message;
|
|
23
93
|
const details = body?.error?.details || [];
|
|
@@ -46,4 +116,8 @@ async function withRetry(fn, maxRetries = 3) {
|
|
|
46
116
|
throw lastErr;
|
|
47
117
|
}
|
|
48
118
|
|
|
49
|
-
module.exports = {
|
|
119
|
+
module.exports = {
|
|
120
|
+
BridgeError, GeminiError, AuthError, RateLimitError,
|
|
121
|
+
TimeoutError, ContextWindowError, ContentPolicyError,
|
|
122
|
+
ProviderError, classifyError, isRetryable, withRetry
|
|
123
|
+
};
|
package/lib/providers/openai.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { GeminiError } = require('../errors');
|
|
2
|
+
const { guardStream } = require('../stream-guard');
|
|
2
3
|
|
|
3
4
|
function convertMessages(messages, system) {
|
|
4
5
|
const result = [];
|
|
@@ -40,22 +41,28 @@ async function callOpenAI({ url, apiKey, headers, body }) {
|
|
|
40
41
|
const res = await fetch(url, { method: 'POST',
|
|
41
42
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, ...(headers || {}) },
|
|
42
43
|
body: JSON.stringify(body) });
|
|
43
|
-
if (!res.ok) { const t = await res.text(); throw new GeminiError(t, { status: res.status, retryable: res.status === 429 || res.status >= 500 }); }
|
|
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 }); }
|
|
44
45
|
return res;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
async function*
|
|
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 }) {
|
|
48
58
|
while (true) {
|
|
49
59
|
yield { type: 'start-step' };
|
|
50
60
|
const res = await callOpenAI({ url, apiKey, headers, body: { ...body, stream: true } });
|
|
51
61
|
const reader = res.body.getReader();
|
|
52
|
-
const dec = new TextDecoder();
|
|
53
62
|
let buf = '', toolCallsMap = {};
|
|
54
63
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (done) break;
|
|
58
|
-
buf += dec.decode(value, { stream: true });
|
|
64
|
+
for await (const text of guardStream(readerIterable(reader), streamGuard)) {
|
|
65
|
+
buf += text;
|
|
59
66
|
const lines = buf.split('\n');
|
|
60
67
|
buf = lines.pop();
|
|
61
68
|
for (const line of lines) {
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
|
|
7
|
+
function isGeminiProvider(p) {
|
|
8
|
+
return p.name === 'gemini' || (p.api_base_url || '').includes('generativelanguage.googleapis.com');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function findProvider(providers, providerName, modelName) {
|
|
12
|
+
if (providerName) return providers.find(p => p.name === providerName);
|
|
13
|
+
if (modelName) return providers.find(p => (p.models || []).includes(modelName));
|
|
14
|
+
return providers[0];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildOpenAIUrl(base) {
|
|
18
|
+
const clean = (base || '').replace(/\/$/g, '');
|
|
19
|
+
return clean.includes('/completions') ? clean : clean + '/chat/completions';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveForProvider(provider, model, customMap) {
|
|
23
|
+
const useList = provider.transformer?.[model]?.use || provider.transformer?.use || [];
|
|
24
|
+
return resolveTransformers(useList, customMap);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function* routerStream(params, resolver) {
|
|
28
|
+
const { createFullStream } = require('../index');
|
|
29
|
+
const { provider, actualModel, transformers } = await resolver(params);
|
|
30
|
+
if (isGeminiProvider(provider)) {
|
|
31
|
+
yield* createFullStream({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
|
|
32
|
+
} else {
|
|
33
|
+
const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
|
|
34
|
+
const oaiTools = openaiProv.convertTools(params.tools);
|
|
35
|
+
let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
|
|
36
|
+
if (oaiTools) req.tools = oaiTools;
|
|
37
|
+
req = applyRequestTransformers(req, transformers);
|
|
38
|
+
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 });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createRouter(config) {
|
|
43
|
+
const { generateGemini } = require('../index');
|
|
44
|
+
const providers = config.Providers || config.providers || [];
|
|
45
|
+
const routerCfg = config.Router || {};
|
|
46
|
+
async function resolve(params) {
|
|
47
|
+
const { providerName, modelName } = await route(params, routerCfg, config.customRouter);
|
|
48
|
+
const provider = findProvider(providers, providerName, modelName) || providers[0];
|
|
49
|
+
if (!provider) throw new Error('[thebird] no provider configured');
|
|
50
|
+
const actualModel = modelName || (provider.models || [])[0] || extractModelId(params.model) || 'gemini-2.0-flash';
|
|
51
|
+
const transformers = resolveForProvider(provider, actualModel, config._transformers);
|
|
52
|
+
return { provider, actualModel, transformers };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
stream(params) { return { fullStream: routerStream(params, resolve), warnings: Promise.resolve([]) }; },
|
|
56
|
+
async generate(params) {
|
|
57
|
+
const { provider, actualModel, transformers } = await resolve(params);
|
|
58
|
+
if (isGeminiProvider(provider)) return generateGemini({ ...params, model: actualModel, apiKey: provider.api_key || params.apiKey });
|
|
59
|
+
const oaiMsgs = openaiProv.convertMessages(params.messages, params.system);
|
|
60
|
+
const oaiTools = openaiProv.convertTools(params.tools);
|
|
61
|
+
let req = { messages: oaiMsgs, model: actualModel, max_tokens: params.maxOutputTokens || 8192, temperature: params.temperature ?? 0.5 };
|
|
62
|
+
if (oaiTools) req.tools = oaiTools;
|
|
63
|
+
req = applyRequestTransformers(req, transformers);
|
|
64
|
+
return openaiProv.generateOpenAI({ url: buildOpenAIUrl(provider.api_base_url), apiKey: provider.api_key, headers: req._extraHeaders, body: req, tools: params.tools });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function streamRouter(params) {
|
|
70
|
+
const { streamGemini } = require('../index');
|
|
71
|
+
const config = loadConfig(params.configPath);
|
|
72
|
+
if (!(config.Providers || config.providers)?.length) return streamGemini(params);
|
|
73
|
+
return createRouter(config).stream(params);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function generateRouter(params) {
|
|
77
|
+
const { generateGemini } = require('../index');
|
|
78
|
+
const config = loadConfig(params.configPath);
|
|
79
|
+
if (!(config.Providers || config.providers)?.length) return generateGemini(params);
|
|
80
|
+
return createRouter(config).generate(params);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { routerStream, createRouter, streamRouter, generateRouter, findProvider, buildOpenAIUrl, resolveForProvider, isGeminiProvider };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const { GeminiError } = require('./errors');
|
|
2
|
+
|
|
3
|
+
class TimeoutError extends GeminiError {
|
|
4
|
+
constructor(ms) {
|
|
5
|
+
super(`Stream chunk timeout after ${ms}ms`, { retryable: true });
|
|
6
|
+
this.name = 'TimeoutError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function* guardStream(iterable, opts = {}) {
|
|
11
|
+
const timeoutMs = opts.chunkTimeoutMs ?? 30000;
|
|
12
|
+
const maxRepeats = opts.maxRepeats ?? 100;
|
|
13
|
+
let lastChunk = null;
|
|
14
|
+
let repeatCount = 0;
|
|
15
|
+
for await (const chunk of raceTimeout(iterable, timeoutMs)) {
|
|
16
|
+
const key = JSON.stringify(chunk);
|
|
17
|
+
if (key === lastChunk && key !== '{}' && key !== 'null') {
|
|
18
|
+
repeatCount++;
|
|
19
|
+
if (repeatCount >= maxRepeats) {
|
|
20
|
+
throw new GeminiError(`Same chunk repeated ${maxRepeats} times`, { retryable: false });
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
lastChunk = key;
|
|
24
|
+
repeatCount = 1;
|
|
25
|
+
}
|
|
26
|
+
yield chunk;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function* raceTimeout(iterable, ms) {
|
|
31
|
+
const iter = iterable[Symbol.asyncIterator]();
|
|
32
|
+
while (true) {
|
|
33
|
+
const result = await Promise.race([
|
|
34
|
+
iter.next(),
|
|
35
|
+
new Promise((_, reject) => setTimeout(() => reject(new TimeoutError(ms)), ms))
|
|
36
|
+
]);
|
|
37
|
+
if (result.done) return;
|
|
38
|
+
yield result.value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { guardStream, TimeoutError };
|
package/package.json
CHANGED