@thinwrap/llm 1.0.0
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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/cjs/base/base.connector.d.ts +44 -0
- package/dist/cjs/base/base.connector.js +216 -0
- package/dist/cjs/facades/chat.facade.d.ts +17 -0
- package/dist/cjs/facades/chat.facade.js +63 -0
- package/dist/cjs/facades/embeddings.facade.d.ts +23 -0
- package/dist/cjs/facades/embeddings.facade.js +63 -0
- package/dist/cjs/index.d.ts +15 -0
- package/dist/cjs/index.js +35 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/providers/_shared/openai-compat.connector.d.ts +23 -0
- package/dist/cjs/providers/_shared/openai-compat.connector.js +320 -0
- package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
- package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.js +105 -0
- package/dist/cjs/providers/_shared/spec.d.ts +99 -0
- package/dist/cjs/providers/_shared/spec.js +84 -0
- package/dist/cjs/providers/anthropic/anthropic.config.d.ts +17 -0
- package/dist/cjs/providers/anthropic/anthropic.config.js +2 -0
- package/dist/cjs/providers/anthropic/anthropic.connector.d.ts +30 -0
- package/dist/cjs/providers/anthropic/anthropic.connector.js +427 -0
- package/dist/cjs/providers/anthropic/index.d.ts +2 -0
- package/dist/cjs/providers/anthropic/index.js +5 -0
- package/dist/cjs/providers/bedrock/bedrock.config.d.ts +18 -0
- package/dist/cjs/providers/bedrock/bedrock.config.js +2 -0
- package/dist/cjs/providers/bedrock/bedrock.connector.d.ts +61 -0
- package/dist/cjs/providers/bedrock/bedrock.connector.js +479 -0
- package/dist/cjs/providers/bedrock/index.d.ts +2 -0
- package/dist/cjs/providers/bedrock/index.js +5 -0
- package/dist/cjs/providers/gemini/gemini.config.d.ts +12 -0
- package/dist/cjs/providers/gemini/gemini.config.js +2 -0
- package/dist/cjs/providers/gemini/gemini.connector.d.ts +28 -0
- package/dist/cjs/providers/gemini/gemini.connector.js +434 -0
- package/dist/cjs/providers/gemini/index.d.ts +2 -0
- package/dist/cjs/providers/gemini/index.js +5 -0
- package/dist/cjs/types/chat.types.d.ts +115 -0
- package/dist/cjs/types/chat.types.js +2 -0
- package/dist/cjs/types/config-map.type.d.ts +29 -0
- package/dist/cjs/types/config-map.type.js +2 -0
- package/dist/cjs/types/embeddings.types.d.ts +29 -0
- package/dist/cjs/types/embeddings.types.js +2 -0
- package/dist/cjs/types/error.types.d.ts +20 -0
- package/dist/cjs/types/error.types.js +22 -0
- package/dist/cjs/types/index.d.ts +7 -0
- package/dist/cjs/types/index.js +23 -0
- package/dist/cjs/types/passthrough.type.d.ts +15 -0
- package/dist/cjs/types/passthrough.type.js +2 -0
- package/dist/cjs/types/provider-id.enum.d.ts +8 -0
- package/dist/cjs/types/provider-id.enum.js +29 -0
- package/dist/cjs/types/provider.interface.d.ts +7 -0
- package/dist/cjs/types/provider.interface.js +2 -0
- package/dist/esm/base/base.connector.d.ts +44 -0
- package/dist/esm/base/base.connector.js +209 -0
- package/dist/esm/facades/chat.facade.d.ts +17 -0
- package/dist/esm/facades/chat.facade.js +59 -0
- package/dist/esm/facades/embeddings.facade.d.ts +23 -0
- package/dist/esm/facades/embeddings.facade.js +59 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/providers/_shared/openai-compat.connector.d.ts +23 -0
- package/dist/esm/providers/_shared/openai-compat.connector.js +316 -0
- package/dist/esm/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
- package/dist/esm/providers/_shared/openai-compat.embeddings.connector.js +101 -0
- package/dist/esm/providers/_shared/spec.d.ts +99 -0
- package/dist/esm/providers/_shared/spec.js +81 -0
- package/dist/esm/providers/anthropic/anthropic.config.d.ts +17 -0
- package/dist/esm/providers/anthropic/anthropic.config.js +1 -0
- package/dist/esm/providers/anthropic/anthropic.connector.d.ts +30 -0
- package/dist/esm/providers/anthropic/anthropic.connector.js +423 -0
- package/dist/esm/providers/anthropic/index.d.ts +2 -0
- package/dist/esm/providers/anthropic/index.js +1 -0
- package/dist/esm/providers/bedrock/bedrock.config.d.ts +18 -0
- package/dist/esm/providers/bedrock/bedrock.config.js +1 -0
- package/dist/esm/providers/bedrock/bedrock.connector.d.ts +61 -0
- package/dist/esm/providers/bedrock/bedrock.connector.js +474 -0
- package/dist/esm/providers/bedrock/index.d.ts +2 -0
- package/dist/esm/providers/bedrock/index.js +1 -0
- package/dist/esm/providers/gemini/gemini.config.d.ts +12 -0
- package/dist/esm/providers/gemini/gemini.config.js +1 -0
- package/dist/esm/providers/gemini/gemini.connector.d.ts +28 -0
- package/dist/esm/providers/gemini/gemini.connector.js +430 -0
- package/dist/esm/providers/gemini/index.d.ts +2 -0
- package/dist/esm/providers/gemini/index.js +1 -0
- package/dist/esm/types/chat.types.d.ts +115 -0
- package/dist/esm/types/chat.types.js +1 -0
- package/dist/esm/types/config-map.type.d.ts +29 -0
- package/dist/esm/types/config-map.type.js +1 -0
- package/dist/esm/types/embeddings.types.d.ts +29 -0
- package/dist/esm/types/embeddings.types.js +1 -0
- package/dist/esm/types/error.types.d.ts +20 -0
- package/dist/esm/types/error.types.js +18 -0
- package/dist/esm/types/index.d.ts +7 -0
- package/dist/esm/types/index.js +7 -0
- package/dist/esm/types/passthrough.type.d.ts +15 -0
- package/dist/esm/types/passthrough.type.js +1 -0
- package/dist/esm/types/provider-id.enum.d.ts +8 -0
- package/dist/esm/types/provider-id.enum.js +26 -0
- package/dist/esm/types/provider.interface.d.ts +7 -0
- package/dist/esm/types/provider.interface.js +1 -0
- package/package.json +73 -0
- package/src/providers/anthropic/README.md +64 -0
- package/src/providers/azure-openai/README.md +62 -0
- package/src/providers/bedrock/README.md +71 -0
- package/src/providers/cloudflare/README.md +62 -0
- package/src/providers/deepinfra/README.md +61 -0
- package/src/providers/deepseek/README.md +50 -0
- package/src/providers/fireworks/README.md +62 -0
- package/src/providers/gemini/README.md +67 -0
- package/src/providers/groq/README.md +50 -0
- package/src/providers/lmstudio/README.md +62 -0
- package/src/providers/mistral/README.md +61 -0
- package/src/providers/ollama/README.md +62 -0
- package/src/providers/openai/README.md +62 -0
- package/src/providers/openrouter/README.md +62 -0
- package/src/providers/perplexity/README.md +50 -0
- package/src/providers/together/README.md +61 -0
- package/src/providers/vllm/README.md +62 -0
- package/src/providers/xai/README.md +50 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAICompatConnector = void 0;
|
|
4
|
+
const base_connector_1 = require("../../base/base.connector");
|
|
5
|
+
const types_1 = require("../../types");
|
|
6
|
+
/**
|
|
7
|
+
* The single connector shared by every first-class OpenAI-compatible provider.
|
|
8
|
+
* It builds the `/chat/completions` request from the normalized `ChatInput`,
|
|
9
|
+
* parses the response (and SSE stream) into the normalized shapes, and maps
|
|
10
|
+
* vendor errors to `ConnectorError`. Provider differences are expressed by the
|
|
11
|
+
* `OpenAICompatSpec`, not by subclassing.
|
|
12
|
+
*/
|
|
13
|
+
class OpenAICompatConnector extends base_connector_1.BaseConnector {
|
|
14
|
+
constructor(spec, config) {
|
|
15
|
+
super(config.fetch);
|
|
16
|
+
this.spec = spec;
|
|
17
|
+
this.id = spec.id;
|
|
18
|
+
this.config = config;
|
|
19
|
+
const baseUrl = config.baseUrl ?? spec.defaultBaseUrl;
|
|
20
|
+
if (!baseUrl) {
|
|
21
|
+
throw new types_1.ConnectorError({
|
|
22
|
+
message: `Provider '${spec.id}' requires an explicit \`baseUrl\` in config`,
|
|
23
|
+
statusCode: null,
|
|
24
|
+
providerCode: 'invalid_request',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
28
|
+
}
|
|
29
|
+
async complete(input) {
|
|
30
|
+
const { url, body, headers } = this.buildRequest(input, false);
|
|
31
|
+
const response = await this.sendPostJson(url, body, headers, input.signal);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errBody = await response.json().catch(() => null);
|
|
34
|
+
throw this.mapVendorError(response.status, errBody, response.headers);
|
|
35
|
+
}
|
|
36
|
+
const json = this.requireDecodedBody((await response.json().catch(() => null)), response.status);
|
|
37
|
+
return this.parseResult(json, input.model);
|
|
38
|
+
}
|
|
39
|
+
async *stream(input) {
|
|
40
|
+
const { url, body, headers } = this.buildRequest(input, true);
|
|
41
|
+
const response = await this.invokeFetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
Accept: 'text/event-stream',
|
|
46
|
+
...headers,
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
signal: input.signal,
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const errBody = await response.json().catch(() => null);
|
|
53
|
+
throw this.mapVendorError(response.status, errBody, response.headers);
|
|
54
|
+
}
|
|
55
|
+
for await (const chunk of (0, base_connector_1.parseSSEStream)(response)) {
|
|
56
|
+
for (const delta of this.mapStreamChunk(chunk))
|
|
57
|
+
yield delta;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
buildRequest(input, stream) {
|
|
61
|
+
const body = {
|
|
62
|
+
model: input.model,
|
|
63
|
+
messages: input.messages.map(mapMessage),
|
|
64
|
+
};
|
|
65
|
+
const toolsAllowed = this.spec.supportsTools !== false;
|
|
66
|
+
if (toolsAllowed && input.tools && input.tools.length > 0) {
|
|
67
|
+
body.tools = input.tools.map((t) => ({
|
|
68
|
+
type: 'function',
|
|
69
|
+
function: {
|
|
70
|
+
name: t.function.name,
|
|
71
|
+
...(t.function.description ? { description: t.function.description } : {}),
|
|
72
|
+
...(t.function.parameters ? { parameters: t.function.parameters } : {}),
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
if (toolsAllowed && input.toolChoice !== undefined)
|
|
77
|
+
body.tool_choice = input.toolChoice;
|
|
78
|
+
if (input.responseFormat)
|
|
79
|
+
body.response_format = mapResponseFormat(input.responseFormat);
|
|
80
|
+
if (input.temperature !== undefined)
|
|
81
|
+
body.temperature = input.temperature;
|
|
82
|
+
if (input.topP !== undefined)
|
|
83
|
+
body.top_p = input.topP;
|
|
84
|
+
if (input.maxOutputTokens !== undefined) {
|
|
85
|
+
body[this.spec.maxTokensField ?? 'max_tokens'] = input.maxOutputTokens;
|
|
86
|
+
}
|
|
87
|
+
if (input.stop !== undefined)
|
|
88
|
+
body.stop = input.stop;
|
|
89
|
+
if (input.reasoning)
|
|
90
|
+
body.reasoning_effort = input.reasoning.effort;
|
|
91
|
+
if (stream) {
|
|
92
|
+
body.stream = true;
|
|
93
|
+
if (!this.spec.strictParams)
|
|
94
|
+
body.stream_options = { include_usage: true };
|
|
95
|
+
}
|
|
96
|
+
// Passthrough body is merged last and overrides connector-built fields.
|
|
97
|
+
if (input._passthrough?.body)
|
|
98
|
+
Object.assign(body, input._passthrough.body);
|
|
99
|
+
const headers = {
|
|
100
|
+
...this.spec.buildAuthHeaders(this.config),
|
|
101
|
+
...this.config.headers,
|
|
102
|
+
...input._passthrough?.headers,
|
|
103
|
+
};
|
|
104
|
+
let url = `${this.baseUrl}/chat/completions`;
|
|
105
|
+
const query = input._passthrough?.query;
|
|
106
|
+
if (query && Object.keys(query).length > 0) {
|
|
107
|
+
url += `?${new URLSearchParams(query).toString()}`;
|
|
108
|
+
}
|
|
109
|
+
return { url, body, headers };
|
|
110
|
+
}
|
|
111
|
+
parseResult(json, requestedModel) {
|
|
112
|
+
const choice = json?.choices?.[0];
|
|
113
|
+
const msg = choice?.message;
|
|
114
|
+
const toolCalls = msg?.tool_calls?.map(mapToolCall);
|
|
115
|
+
return {
|
|
116
|
+
message: {
|
|
117
|
+
role: 'assistant',
|
|
118
|
+
content: msg?.content ?? null,
|
|
119
|
+
...(toolCalls && toolCalls.length > 0 ? { toolCalls } : {}),
|
|
120
|
+
},
|
|
121
|
+
finishReason: mapFinishReason(choice?.finish_reason),
|
|
122
|
+
usage: mapUsage(json?.usage),
|
|
123
|
+
model: json?.model ?? requestedModel,
|
|
124
|
+
raw: json,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
mapStreamChunk(chunk) {
|
|
128
|
+
// A provider/gateway failure emitted mid-stream arrives as an `{"error": …}`
|
|
129
|
+
// data frame (no `choices`). Surface it as a ConnectorError instead of
|
|
130
|
+
// silently truncating the stream into a finish-less "success".
|
|
131
|
+
if (chunk.error != null) {
|
|
132
|
+
throw this.mapVendorError(streamErrorStatus(chunk.error), chunk, new Headers());
|
|
133
|
+
}
|
|
134
|
+
const deltas = [];
|
|
135
|
+
const choice = chunk.choices?.[0];
|
|
136
|
+
const delta = choice?.delta;
|
|
137
|
+
if (typeof delta?.content === 'string' && delta.content.length > 0) {
|
|
138
|
+
deltas.push({ contentDelta: delta.content, raw: chunk });
|
|
139
|
+
}
|
|
140
|
+
// Parallel tool-call fragments can arrive in a single chunk — emit one
|
|
141
|
+
// toolCallDelta per entry (not just `tool_calls[0]`).
|
|
142
|
+
for (const tc of delta?.tool_calls ?? []) {
|
|
143
|
+
deltas.push({
|
|
144
|
+
toolCallDelta: {
|
|
145
|
+
index: tc.index ?? 0,
|
|
146
|
+
...(tc.id ? { id: tc.id } : {}),
|
|
147
|
+
...(tc.function?.name ? { functionName: tc.function.name } : {}),
|
|
148
|
+
...(tc.function?.arguments ? { argumentsDelta: tc.function.arguments } : {}),
|
|
149
|
+
},
|
|
150
|
+
raw: chunk,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const trailer = { raw: chunk };
|
|
154
|
+
let hasTrailer = false;
|
|
155
|
+
if (choice?.finish_reason) {
|
|
156
|
+
trailer.finishReason = mapFinishReason(choice.finish_reason);
|
|
157
|
+
hasTrailer = true;
|
|
158
|
+
}
|
|
159
|
+
const usageRaw = chunk.usage ??
|
|
160
|
+
(this.spec.streamUsagePath === 'x_groq' ? chunk.x_groq?.usage : undefined);
|
|
161
|
+
if (usageRaw) {
|
|
162
|
+
trailer.usage = mapUsage(usageRaw);
|
|
163
|
+
hasTrailer = true;
|
|
164
|
+
}
|
|
165
|
+
if (hasTrailer)
|
|
166
|
+
deltas.push(trailer);
|
|
167
|
+
return deltas;
|
|
168
|
+
}
|
|
169
|
+
mapVendorError(status, body, headers) {
|
|
170
|
+
const message = extractErrorMessage(body) ?? `HTTP ${status}`;
|
|
171
|
+
const providerCode = mapStatusToProviderCode(status, body);
|
|
172
|
+
const retryAfter = headers.get('retry-after');
|
|
173
|
+
const cause = { raw: body ?? null };
|
|
174
|
+
if (retryAfter != null) {
|
|
175
|
+
cause.retryAfter = retryAfter;
|
|
176
|
+
const secs = parseRetryAfterSeconds(retryAfter);
|
|
177
|
+
if (secs != null)
|
|
178
|
+
cause.retryAfterSeconds = secs;
|
|
179
|
+
}
|
|
180
|
+
return new types_1.ConnectorError({
|
|
181
|
+
message,
|
|
182
|
+
statusCode: status,
|
|
183
|
+
providerCode,
|
|
184
|
+
providerMessage: message,
|
|
185
|
+
cause,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.OpenAICompatConnector = OpenAICompatConnector;
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Module-private mappers
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
function mapMessage(m) {
|
|
194
|
+
const out = { role: m.role, content: mapContent(m.content) };
|
|
195
|
+
if (m.name)
|
|
196
|
+
out.name = m.name;
|
|
197
|
+
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
198
|
+
out.tool_calls = m.toolCalls.map((tc) => ({
|
|
199
|
+
id: tc.id,
|
|
200
|
+
type: 'function',
|
|
201
|
+
function: { name: tc.function.name, arguments: tc.function.arguments },
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
if (m.toolCallId)
|
|
205
|
+
out.tool_call_id = m.toolCallId;
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
function mapContent(content) {
|
|
209
|
+
if (content === null)
|
|
210
|
+
return null;
|
|
211
|
+
if (typeof content === 'string')
|
|
212
|
+
return content;
|
|
213
|
+
return content.map((p) => p.type === 'text'
|
|
214
|
+
? { type: 'text', text: p.text }
|
|
215
|
+
: { type: 'image_url', image_url: { url: `data:${p.mediaType};base64,${p.base64}` } });
|
|
216
|
+
}
|
|
217
|
+
function mapToolCall(tc) {
|
|
218
|
+
return {
|
|
219
|
+
id: tc.id ?? '',
|
|
220
|
+
type: 'function',
|
|
221
|
+
function: { name: tc.function?.name ?? '', arguments: tc.function?.arguments ?? '' },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function mapFinishReason(fr) {
|
|
225
|
+
switch (fr) {
|
|
226
|
+
case 'stop':
|
|
227
|
+
case 'eos': // Together AI reports natural end-of-sequence as "eos"
|
|
228
|
+
return 'stop';
|
|
229
|
+
case 'length':
|
|
230
|
+
return 'length';
|
|
231
|
+
case 'tool_calls':
|
|
232
|
+
case 'function_call':
|
|
233
|
+
return 'tool_calls';
|
|
234
|
+
case 'content_filter':
|
|
235
|
+
return 'content_filter';
|
|
236
|
+
default:
|
|
237
|
+
return 'unknown';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function mapUsage(u) {
|
|
241
|
+
const inputTokens = u?.prompt_tokens ?? 0;
|
|
242
|
+
const outputTokens = u?.completion_tokens ?? 0;
|
|
243
|
+
const totalTokens = u?.total_tokens ?? inputTokens + outputTokens;
|
|
244
|
+
return { inputTokens, outputTokens, totalTokens };
|
|
245
|
+
}
|
|
246
|
+
function mapResponseFormat(rf) {
|
|
247
|
+
if (rf.type === 'json_schema') {
|
|
248
|
+
return {
|
|
249
|
+
type: 'json_schema',
|
|
250
|
+
json_schema: { name: rf.jsonSchema.name, schema: rf.jsonSchema.schema },
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return { type: rf.type };
|
|
254
|
+
}
|
|
255
|
+
function extractErrorMessage(body) {
|
|
256
|
+
if (typeof body === 'string')
|
|
257
|
+
return body;
|
|
258
|
+
if (!body || typeof body !== 'object')
|
|
259
|
+
return null;
|
|
260
|
+
const b = body;
|
|
261
|
+
if (typeof b.error === 'string')
|
|
262
|
+
return b.error;
|
|
263
|
+
if (b.error && typeof b.error === 'object') {
|
|
264
|
+
const em = b.error.message;
|
|
265
|
+
if (typeof em === 'string')
|
|
266
|
+
return em;
|
|
267
|
+
}
|
|
268
|
+
if (typeof b.message === 'string')
|
|
269
|
+
return b.message;
|
|
270
|
+
const errs = b.errors;
|
|
271
|
+
if (Array.isArray(errs) && errs[0] && typeof errs[0] === 'object') {
|
|
272
|
+
const m = errs[0].message;
|
|
273
|
+
if (typeof m === 'string')
|
|
274
|
+
return m;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/** Best-effort HTTP status for a mid-stream `{"error": …}` frame (some gateways carry a numeric `code`). */
|
|
279
|
+
function streamErrorStatus(error) {
|
|
280
|
+
if (error && typeof error === 'object') {
|
|
281
|
+
const code = error.code;
|
|
282
|
+
if (typeof code === 'number' && code >= 400 && code <= 599)
|
|
283
|
+
return code;
|
|
284
|
+
}
|
|
285
|
+
return 500;
|
|
286
|
+
}
|
|
287
|
+
function mapStatusToProviderCode(status, body) {
|
|
288
|
+
if (status === 401 || status === 403)
|
|
289
|
+
return 'auth_failed';
|
|
290
|
+
if (status === 429)
|
|
291
|
+
return 'rate_limited';
|
|
292
|
+
if (status >= 500)
|
|
293
|
+
return 'provider_unavailable';
|
|
294
|
+
if (status === 400 || status === 404 || status === 422) {
|
|
295
|
+
const msg = (extractErrorMessage(body) ?? '').toLowerCase();
|
|
296
|
+
if (msg.includes('context length') || msg.includes('maximum context') || msg.includes('context_length')) {
|
|
297
|
+
return 'context_length_exceeded';
|
|
298
|
+
}
|
|
299
|
+
if (msg.includes('content_filter') ||
|
|
300
|
+
msg.includes('content filter') ||
|
|
301
|
+
msg.includes('content_policy') ||
|
|
302
|
+
msg.includes('content policy') ||
|
|
303
|
+
msg.includes('moderation')) {
|
|
304
|
+
return 'content_filtered';
|
|
305
|
+
}
|
|
306
|
+
return 'invalid_request';
|
|
307
|
+
}
|
|
308
|
+
return 'unknown';
|
|
309
|
+
}
|
|
310
|
+
function parseRetryAfterSeconds(header) {
|
|
311
|
+
const asNum = Number(header);
|
|
312
|
+
if (Number.isFinite(asNum))
|
|
313
|
+
return asNum;
|
|
314
|
+
const asDate = Date.parse(header);
|
|
315
|
+
if (!Number.isNaN(asDate)) {
|
|
316
|
+
const secs = Math.round((asDate - Date.now()) / 1000);
|
|
317
|
+
return secs >= 0 ? secs : 0;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseConnector } from '../../base/base.connector';
|
|
2
|
+
import type { EmbeddingsInput, EmbeddingsResult, IEmbeddingsConnector, OpenAICompatConfig } from '../../types';
|
|
3
|
+
import type { OpenAICompatSpec } from './spec';
|
|
4
|
+
/**
|
|
5
|
+
* The shared embeddings connector for OpenAI-float-shaped providers
|
|
6
|
+
* (`POST /embeddings`). Decoupled from chat (a separate operation surface).
|
|
7
|
+
* Requests `encoding_format: 'float'` and normalizes `data[].embedding` into
|
|
8
|
+
* `number[][]` in input order. Only wired for providers that expose a
|
|
9
|
+
* float-shaped embeddings endpoint (see `EMBEDDINGS_PROVIDER_IDS`).
|
|
10
|
+
*/
|
|
11
|
+
export declare class OpenAICompatEmbeddingsConnector extends BaseConnector implements IEmbeddingsConnector {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
private readonly baseUrl;
|
|
14
|
+
private readonly spec;
|
|
15
|
+
private readonly config;
|
|
16
|
+
constructor(spec: OpenAICompatSpec, config: OpenAICompatConfig);
|
|
17
|
+
create(input: EmbeddingsInput): Promise<EmbeddingsResult>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAICompatEmbeddingsConnector = void 0;
|
|
4
|
+
const base_connector_1 = require("../../base/base.connector");
|
|
5
|
+
const types_1 = require("../../types");
|
|
6
|
+
/**
|
|
7
|
+
* The shared embeddings connector for OpenAI-float-shaped providers
|
|
8
|
+
* (`POST /embeddings`). Decoupled from chat (a separate operation surface).
|
|
9
|
+
* Requests `encoding_format: 'float'` and normalizes `data[].embedding` into
|
|
10
|
+
* `number[][]` in input order. Only wired for providers that expose a
|
|
11
|
+
* float-shaped embeddings endpoint (see `EMBEDDINGS_PROVIDER_IDS`).
|
|
12
|
+
*/
|
|
13
|
+
class OpenAICompatEmbeddingsConnector extends base_connector_1.BaseConnector {
|
|
14
|
+
constructor(spec, config) {
|
|
15
|
+
super(config.fetch);
|
|
16
|
+
this.spec = spec;
|
|
17
|
+
this.id = spec.id;
|
|
18
|
+
this.config = config;
|
|
19
|
+
const baseUrl = config.baseUrl ?? spec.defaultBaseUrl;
|
|
20
|
+
if (!baseUrl) {
|
|
21
|
+
throw new types_1.ConnectorError({
|
|
22
|
+
message: `Provider '${spec.id}' requires an explicit \`baseUrl\` in config`,
|
|
23
|
+
statusCode: null,
|
|
24
|
+
providerCode: 'invalid_request',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
28
|
+
}
|
|
29
|
+
async create(input) {
|
|
30
|
+
const body = {
|
|
31
|
+
model: input.model,
|
|
32
|
+
input: input.input,
|
|
33
|
+
encoding_format: 'float',
|
|
34
|
+
};
|
|
35
|
+
if (input.dimensions !== undefined)
|
|
36
|
+
body.dimensions = input.dimensions;
|
|
37
|
+
if (input._passthrough?.body)
|
|
38
|
+
Object.assign(body, input._passthrough.body);
|
|
39
|
+
const headers = {
|
|
40
|
+
...this.spec.buildAuthHeaders(this.config),
|
|
41
|
+
...this.config.headers,
|
|
42
|
+
...input._passthrough?.headers,
|
|
43
|
+
};
|
|
44
|
+
const url = (0, base_connector_1.appendPassthroughQuery)(`${this.baseUrl}/embeddings`, input._passthrough?.query);
|
|
45
|
+
const response = await this.sendPostJson(url, body, headers, input.signal);
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const errBody = await response.json().catch(() => null);
|
|
48
|
+
throw mapVendorError(response.status, errBody, response.headers);
|
|
49
|
+
}
|
|
50
|
+
const json = this.requireDecodedBody((await response.json().catch(() => null)), response.status);
|
|
51
|
+
const rows = [...(json?.data ?? [])].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
52
|
+
const inputTokens = json?.usage?.prompt_tokens ?? 0;
|
|
53
|
+
return {
|
|
54
|
+
embeddings: rows.map((d) => d.embedding ?? []),
|
|
55
|
+
usage: { inputTokens, totalTokens: json?.usage?.total_tokens ?? inputTokens },
|
|
56
|
+
model: json?.model ?? input.model,
|
|
57
|
+
raw: json,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.OpenAICompatEmbeddingsConnector = OpenAICompatEmbeddingsConnector;
|
|
62
|
+
function mapVendorError(status, body, headers) {
|
|
63
|
+
const message = extractErrorMessage(body) ?? `HTTP ${status}`;
|
|
64
|
+
const cause = { raw: body ?? null };
|
|
65
|
+
const retryAfter = headers.get('retry-after');
|
|
66
|
+
if (retryAfter != null) {
|
|
67
|
+
cause.retryAfter = retryAfter;
|
|
68
|
+
const secs = Number(retryAfter);
|
|
69
|
+
if (Number.isFinite(secs))
|
|
70
|
+
cause.retryAfterSeconds = secs;
|
|
71
|
+
}
|
|
72
|
+
return new types_1.ConnectorError({
|
|
73
|
+
message,
|
|
74
|
+
statusCode: status,
|
|
75
|
+
providerCode: mapStatusToProviderCode(status),
|
|
76
|
+
providerMessage: message,
|
|
77
|
+
cause,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function extractErrorMessage(body) {
|
|
81
|
+
if (typeof body === 'string')
|
|
82
|
+
return body;
|
|
83
|
+
if (!body || typeof body !== 'object')
|
|
84
|
+
return null;
|
|
85
|
+
const b = body;
|
|
86
|
+
if (b.error && typeof b.error === 'object') {
|
|
87
|
+
const em = b.error.message;
|
|
88
|
+
if (typeof em === 'string')
|
|
89
|
+
return em;
|
|
90
|
+
}
|
|
91
|
+
if (typeof b.message === 'string')
|
|
92
|
+
return b.message;
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function mapStatusToProviderCode(status) {
|
|
96
|
+
if (status === 401 || status === 403)
|
|
97
|
+
return 'auth_failed';
|
|
98
|
+
if (status === 429)
|
|
99
|
+
return 'rate_limited';
|
|
100
|
+
if (status >= 500)
|
|
101
|
+
return 'provider_unavailable';
|
|
102
|
+
if (status === 400 || status === 404 || status === 422)
|
|
103
|
+
return 'invalid_request';
|
|
104
|
+
return 'unknown';
|
|
105
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { OpenAICompatConfig } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Per-provider parameterization of the one shared OpenAI-compatible connector.
|
|
4
|
+
* Base URLs are current-as-of-2026 defaults and are the exact fact each
|
|
5
|
+
* per-connector README pins; treat as overridable via `config.baseUrl`.
|
|
6
|
+
*/
|
|
7
|
+
export interface OpenAICompatSpec {
|
|
8
|
+
id: string;
|
|
9
|
+
/** Default base URL (no trailing slash). Undefined ⇒ consumer must supply `config.baseUrl`. */
|
|
10
|
+
defaultBaseUrl?: string;
|
|
11
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
12
|
+
/** Providers with no tool support on the primary chat surface (Perplexity Sonar). */
|
|
13
|
+
supportsTools?: boolean;
|
|
14
|
+
/** Vendor field name for max output tokens (default `max_tokens`). */
|
|
15
|
+
maxTokensField?: string;
|
|
16
|
+
/** Groq relocates streaming usage under `x_groq.usage` on the final chunk (baseline-exception). */
|
|
17
|
+
streamUsagePath?: 'x_groq';
|
|
18
|
+
/** Mistral rejects unknown params with HTTP 422 — skip OpenAI-only extras like `stream_options`. */
|
|
19
|
+
strictParams?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const SPECS: {
|
|
22
|
+
openai: {
|
|
23
|
+
id: string;
|
|
24
|
+
defaultBaseUrl: string;
|
|
25
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
'azure-openai': {
|
|
28
|
+
id: string;
|
|
29
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
30
|
+
};
|
|
31
|
+
openrouter: {
|
|
32
|
+
id: string;
|
|
33
|
+
defaultBaseUrl: string;
|
|
34
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
groq: {
|
|
37
|
+
id: string;
|
|
38
|
+
defaultBaseUrl: string;
|
|
39
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
40
|
+
streamUsagePath: "x_groq";
|
|
41
|
+
};
|
|
42
|
+
together: {
|
|
43
|
+
id: string;
|
|
44
|
+
defaultBaseUrl: string;
|
|
45
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
46
|
+
};
|
|
47
|
+
fireworks: {
|
|
48
|
+
id: string;
|
|
49
|
+
defaultBaseUrl: string;
|
|
50
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
51
|
+
};
|
|
52
|
+
deepseek: {
|
|
53
|
+
id: string;
|
|
54
|
+
defaultBaseUrl: string;
|
|
55
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
56
|
+
};
|
|
57
|
+
xai: {
|
|
58
|
+
id: string;
|
|
59
|
+
defaultBaseUrl: string;
|
|
60
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
61
|
+
};
|
|
62
|
+
mistral: {
|
|
63
|
+
id: string;
|
|
64
|
+
defaultBaseUrl: string;
|
|
65
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
66
|
+
strictParams: true;
|
|
67
|
+
};
|
|
68
|
+
perplexity: {
|
|
69
|
+
id: string;
|
|
70
|
+
defaultBaseUrl: string;
|
|
71
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
72
|
+
supportsTools: false;
|
|
73
|
+
};
|
|
74
|
+
deepinfra: {
|
|
75
|
+
id: string;
|
|
76
|
+
defaultBaseUrl: string;
|
|
77
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
78
|
+
};
|
|
79
|
+
cloudflare: {
|
|
80
|
+
id: string;
|
|
81
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
82
|
+
};
|
|
83
|
+
vllm: {
|
|
84
|
+
id: string;
|
|
85
|
+
defaultBaseUrl: string;
|
|
86
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
87
|
+
};
|
|
88
|
+
ollama: {
|
|
89
|
+
id: string;
|
|
90
|
+
defaultBaseUrl: string;
|
|
91
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
92
|
+
};
|
|
93
|
+
lmstudio: {
|
|
94
|
+
id: string;
|
|
95
|
+
defaultBaseUrl: string;
|
|
96
|
+
buildAuthHeaders: (config: OpenAICompatConfig) => Record<string, string>;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
export type SpecId = keyof typeof SPECS;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SPECS = void 0;
|
|
4
|
+
const bearer = (config) => config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {};
|
|
5
|
+
exports.SPECS = {
|
|
6
|
+
openai: {
|
|
7
|
+
id: 'openai',
|
|
8
|
+
defaultBaseUrl: 'https://api.openai.com/v1',
|
|
9
|
+
buildAuthHeaders: bearer,
|
|
10
|
+
},
|
|
11
|
+
'azure-openai': {
|
|
12
|
+
id: 'azure-openai',
|
|
13
|
+
// no default: consumer supplies https://<resource>.openai.azure.com/openai/v1
|
|
14
|
+
buildAuthHeaders: (config) => config.apiKey ? { 'api-key': config.apiKey } : {},
|
|
15
|
+
},
|
|
16
|
+
openrouter: {
|
|
17
|
+
id: 'openrouter',
|
|
18
|
+
defaultBaseUrl: 'https://openrouter.ai/api/v1',
|
|
19
|
+
buildAuthHeaders: bearer,
|
|
20
|
+
},
|
|
21
|
+
groq: {
|
|
22
|
+
id: 'groq',
|
|
23
|
+
defaultBaseUrl: 'https://api.groq.com/openai/v1',
|
|
24
|
+
buildAuthHeaders: bearer,
|
|
25
|
+
streamUsagePath: 'x_groq',
|
|
26
|
+
},
|
|
27
|
+
together: {
|
|
28
|
+
id: 'together',
|
|
29
|
+
defaultBaseUrl: 'https://api.together.ai/v1',
|
|
30
|
+
buildAuthHeaders: bearer,
|
|
31
|
+
},
|
|
32
|
+
fireworks: {
|
|
33
|
+
id: 'fireworks',
|
|
34
|
+
defaultBaseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
35
|
+
buildAuthHeaders: bearer,
|
|
36
|
+
},
|
|
37
|
+
deepseek: {
|
|
38
|
+
id: 'deepseek',
|
|
39
|
+
defaultBaseUrl: 'https://api.deepseek.com/v1',
|
|
40
|
+
buildAuthHeaders: bearer,
|
|
41
|
+
},
|
|
42
|
+
xai: {
|
|
43
|
+
id: 'xai',
|
|
44
|
+
defaultBaseUrl: 'https://api.x.ai/v1',
|
|
45
|
+
buildAuthHeaders: bearer,
|
|
46
|
+
},
|
|
47
|
+
mistral: {
|
|
48
|
+
id: 'mistral',
|
|
49
|
+
defaultBaseUrl: 'https://api.mistral.ai/v1',
|
|
50
|
+
buildAuthHeaders: bearer,
|
|
51
|
+
strictParams: true,
|
|
52
|
+
},
|
|
53
|
+
perplexity: {
|
|
54
|
+
id: 'perplexity',
|
|
55
|
+
defaultBaseUrl: 'https://api.perplexity.ai',
|
|
56
|
+
buildAuthHeaders: bearer,
|
|
57
|
+
supportsTools: false,
|
|
58
|
+
},
|
|
59
|
+
deepinfra: {
|
|
60
|
+
id: 'deepinfra',
|
|
61
|
+
defaultBaseUrl: 'https://api.deepinfra.com/v1/openai',
|
|
62
|
+
buildAuthHeaders: bearer,
|
|
63
|
+
},
|
|
64
|
+
cloudflare: {
|
|
65
|
+
id: 'cloudflare',
|
|
66
|
+
// no default: consumer supplies https://api.cloudflare.com/client/v4/accounts/<id>/ai/v1
|
|
67
|
+
buildAuthHeaders: bearer,
|
|
68
|
+
},
|
|
69
|
+
vllm: {
|
|
70
|
+
id: 'vllm',
|
|
71
|
+
defaultBaseUrl: 'http://localhost:8000/v1',
|
|
72
|
+
buildAuthHeaders: bearer,
|
|
73
|
+
},
|
|
74
|
+
ollama: {
|
|
75
|
+
id: 'ollama',
|
|
76
|
+
defaultBaseUrl: 'http://localhost:11434/v1',
|
|
77
|
+
buildAuthHeaders: bearer,
|
|
78
|
+
},
|
|
79
|
+
lmstudio: {
|
|
80
|
+
id: 'lmstudio',
|
|
81
|
+
defaultBaseUrl: 'http://localhost:1234/v1',
|
|
82
|
+
buildAuthHeaders: bearer,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface AnthropicConfig {
|
|
2
|
+
/** Anthropic API key — sent as the `x-api-key` header (not Bearer). */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Override the base URL. Default `https://api.anthropic.com/v1`. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** `anthropic-version` header value. Default `2023-06-01`. */
|
|
7
|
+
anthropicVersion?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Anthropic requires `max_tokens` on every request. Used when
|
|
10
|
+
* `ChatInput.maxOutputTokens` is omitted. Default 4096.
|
|
11
|
+
*/
|
|
12
|
+
defaultMaxTokens?: number;
|
|
13
|
+
/** Bring-your-own fetch. Defaults to `globalThis.fetch`. */
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
/** Extra headers merged onto every request. */
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseConnector } from '../../base/base.connector';
|
|
2
|
+
import type { ChatInput, ChatResult, ChatStreamDelta, IChatConnector } from '../../types';
|
|
3
|
+
import type { AnthropicConfig } from './anthropic.config';
|
|
4
|
+
/**
|
|
5
|
+
* Native adapter for the Anthropic **Messages API** (`POST /v1/messages`).
|
|
6
|
+
* Anthropic is structurally different from OpenAI Chat Completions — different
|
|
7
|
+
* auth, a top-level `system`, typed content blocks, tool results carried inside
|
|
8
|
+
* a user turn, `stop_sequences`, and Anthropic-shaped SSE events — so this is a
|
|
9
|
+
* full translation layer, not an OpenAI passthrough. It emits the identical
|
|
10
|
+
* normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other
|
|
11
|
+
* connector, so it is drop-in swappable via the `Chat` facade.
|
|
12
|
+
*
|
|
13
|
+
* Not normalized here (per D2/D4, surfaced via `raw` or `_passthrough`, never
|
|
14
|
+
* emulated): `responseFormat` (Anthropic has no `response_format` — use
|
|
15
|
+
* tool-based structured output via `_passthrough`); reasoning CoT output
|
|
16
|
+
* (`thinking` blocks land in `raw`); prompt caching (`cache_control` via
|
|
17
|
+
* `_passthrough`, cached-token counts in `raw`).
|
|
18
|
+
*/
|
|
19
|
+
export declare class AnthropicConnector extends BaseConnector implements IChatConnector {
|
|
20
|
+
readonly id = "anthropic";
|
|
21
|
+
private readonly baseUrl;
|
|
22
|
+
private readonly config;
|
|
23
|
+
constructor(config: AnthropicConfig);
|
|
24
|
+
complete(input: ChatInput): Promise<ChatResult>;
|
|
25
|
+
stream(input: ChatInput): AsyncGenerator<ChatStreamDelta>;
|
|
26
|
+
private authHeaders;
|
|
27
|
+
private buildRequest;
|
|
28
|
+
private parseResult;
|
|
29
|
+
private mapVendorError;
|
|
30
|
+
}
|