@morlay/dsh-llm-openai-compatible 0.0.1 → 0.0.2
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 +0 -9
- package/cordis.patch.yml +3 -3
- package/dist/index.d.mts +109 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +895 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +19 -18
- package/src/adapter.ts +426 -0
- package/src/index.ts +497 -0
- package/src/serialize.ts +336 -0
- package/src/translate.ts +195 -0
- package/lib/adapter.d.mts +0 -137
- package/lib/adapter.d.mts.map +0 -1
- package/lib/adapter.mjs +0 -295
- package/lib/adapter.mjs.map +0 -1
- package/lib/index.d.mts +0 -89
- package/lib/index.d.mts.map +0 -1
- package/lib/index.mjs +0 -293
- package/lib/index.mjs.map +0 -1
- package/lib/serialize.d.mts +0 -68
- package/lib/serialize.d.mts.map +0 -1
- package/lib/serialize.mjs +0 -276
- package/lib/serialize.mjs.map +0 -1
- package/lib/translate.d.mts +0 -19
- package/lib/translate.d.mts.map +0 -1
- package/lib/translate.mjs +0 -209
- package/lib/translate.mjs.map +0 -1
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTEXT_WINDOW_EXCEEDED_CODE,
|
|
3
|
+
QUOTA_EXCEEDED_CODE,
|
|
4
|
+
LlmAdapter,
|
|
5
|
+
LlmError,
|
|
6
|
+
ProviderRequestId,
|
|
7
|
+
ReasoningEffortId,
|
|
8
|
+
attributionHeaders,
|
|
9
|
+
contentHasImage,
|
|
10
|
+
isContextWindowExceededError,
|
|
11
|
+
isQuotaExceededError,
|
|
12
|
+
} from "@deepseek-ai/dsh-llm";
|
|
13
|
+
import type {
|
|
14
|
+
GenerateOptions,
|
|
15
|
+
LlmModelInfo,
|
|
16
|
+
LlmProviderInfo,
|
|
17
|
+
LlmResolvedModelInfo,
|
|
18
|
+
ModelModality,
|
|
19
|
+
ResolvedRetryPolicy,
|
|
20
|
+
StreamChunk,
|
|
21
|
+
} from "@deepseek-ai/dsh-llm";
|
|
22
|
+
import type { AttachmentStore } from "@deepseek-ai/dsh-attachment";
|
|
23
|
+
import type { CredentialRef } from "@deepseek-ai/dsh-credentials";
|
|
24
|
+
import { deadline, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
25
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
26
|
+
import type { LanguageModelV4, LanguageModelV4Usage } from "@ai-sdk/provider";
|
|
27
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
28
|
+
import { serializeCallOptions, serializeCallOptionsWithImages } from "./serialize.ts";
|
|
29
|
+
import { translate } from "./translate.ts";
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_CONTEXT_WINDOW = 262_144;
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_MAX_TOKENS = 32_768;
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
38
|
+
|
|
39
|
+
export const STREAM_IDLE_TIMEOUT_CODE = "LLM_STREAM_IDLE_TIMEOUT";
|
|
40
|
+
|
|
41
|
+
export const REQUEST_TIMEOUT_CODE = "LLM_REQUEST_TIMEOUT";
|
|
42
|
+
|
|
43
|
+
export const PROVIDER_OPTIONS_KEY = "openai-compatible";
|
|
44
|
+
|
|
45
|
+
export type ReasoningEffort = "off" | "low" | "high" | "max";
|
|
46
|
+
|
|
47
|
+
export interface ResolvedModelProfile {
|
|
48
|
+
id: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
contextWindow?: number;
|
|
52
|
+
maxTokens?: number;
|
|
53
|
+
inputModalities: readonly ModelModality[];
|
|
54
|
+
|
|
55
|
+
reasoningEfforts?: false | Partial<Record<ReasoningEffort, string | null>>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ResolvedProviderProfile {
|
|
59
|
+
provider: string;
|
|
60
|
+
displayName: string;
|
|
61
|
+
|
|
62
|
+
apiKeyEnv?: CredentialRef;
|
|
63
|
+
|
|
64
|
+
baseURL: string;
|
|
65
|
+
headers?: Readonly<Record<string, string>>;
|
|
66
|
+
// === sampling defaults (request-level values win) ===
|
|
67
|
+
temperature?: number;
|
|
68
|
+
topP?: number;
|
|
69
|
+
topK?: number;
|
|
70
|
+
presencePenalty?: number;
|
|
71
|
+
frequencyPenalty?: number;
|
|
72
|
+
seed?: number;
|
|
73
|
+
|
|
74
|
+
reasoning?: ReasoningEffort;
|
|
75
|
+
// === model catalog ===
|
|
76
|
+
models: readonly ResolvedModelProfile[];
|
|
77
|
+
defaultContextWindow: number;
|
|
78
|
+
defaultMaxTokens: number;
|
|
79
|
+
// === transport ===
|
|
80
|
+
maxRequestImageBytes: number;
|
|
81
|
+
streamIdleTimeoutMs: number;
|
|
82
|
+
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
retryPolicy: ResolvedRetryPolicy;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface OpenAICompatibleAdapterOptions {
|
|
88
|
+
profiles: () => ReadonlyMap<string, ResolvedProviderProfile>;
|
|
89
|
+
|
|
90
|
+
resolveApiKey: (
|
|
91
|
+
provider: string,
|
|
92
|
+
profile: ResolvedProviderProfile,
|
|
93
|
+
) => Promise<string | undefined>;
|
|
94
|
+
|
|
95
|
+
resolveUserId: () => string;
|
|
96
|
+
|
|
97
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface WireUsageLike {
|
|
101
|
+
prompt_tokens?: number | null | undefined;
|
|
102
|
+
completion_tokens?: number | null | undefined;
|
|
103
|
+
prompt_tokens_details?: { cached_tokens?: number | null | undefined } | null | undefined;
|
|
104
|
+
|
|
105
|
+
prompt_cache_hit_tokens?: number | null | undefined;
|
|
106
|
+
completion_tokens_details?: { reasoning_tokens?: number | null | undefined } | null | undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function convertUsage(usage: WireUsageLike | null | undefined): LanguageModelV4Usage {
|
|
110
|
+
if (usage == null) {
|
|
111
|
+
return {
|
|
112
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: void 0, cacheWrite: void 0 },
|
|
113
|
+
outputTokens: { total: 0, text: void 0, reasoning: void 0 },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const promptTokens = usage.prompt_tokens ?? 0;
|
|
117
|
+
const completionTokens = usage.completion_tokens ?? 0;
|
|
118
|
+
const cacheRead =
|
|
119
|
+
usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens ?? 0;
|
|
120
|
+
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
121
|
+
return {
|
|
122
|
+
inputTokens: {
|
|
123
|
+
total: promptTokens,
|
|
124
|
+
noCache: Math.max(0, promptTokens - cacheRead),
|
|
125
|
+
cacheRead,
|
|
126
|
+
cacheWrite: void 0,
|
|
127
|
+
},
|
|
128
|
+
outputTokens: {
|
|
129
|
+
total: completionTokens,
|
|
130
|
+
text: Math.max(0, completionTokens - reasoningTokens),
|
|
131
|
+
reasoning: reasoningTokens,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function modelInfo(profile: ResolvedProviderProfile, model: ResolvedModelProfile): LlmModelInfo {
|
|
137
|
+
return {
|
|
138
|
+
provider: profile.provider,
|
|
139
|
+
id: model.id,
|
|
140
|
+
name: model.name ?? model.id,
|
|
141
|
+
...(model.description === void 0 ? {} : { description: model.description }),
|
|
142
|
+
inputModalities: [...model.inputModalities],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reasoningInfo(
|
|
147
|
+
model: ResolvedModelProfile | undefined,
|
|
148
|
+
defaultEffort: ReasoningEffort | undefined,
|
|
149
|
+
): Pick<LlmResolvedModelInfo, "reasoning"> {
|
|
150
|
+
const declaration = model?.reasoningEfforts;
|
|
151
|
+
if (declaration === void 0 || declaration === false) return {};
|
|
152
|
+
const entries = Object.entries(declaration) as [ReasoningEffort, string | null | undefined][];
|
|
153
|
+
const efforts = entries.map(([id]) => ({
|
|
154
|
+
id: ReasoningEffortId(id),
|
|
155
|
+
name: `${id.charAt(0).toUpperCase()}${id.slice(1)}`,
|
|
156
|
+
}));
|
|
157
|
+
return {
|
|
158
|
+
reasoning: {
|
|
159
|
+
efforts,
|
|
160
|
+
// A configured default the model does not declare is silently dropped
|
|
161
|
+
// here (describing a model must never throw); the request path still
|
|
162
|
+
// refuses it, which is where a bad deployment default belongs.
|
|
163
|
+
...(defaultEffort !== void 0 && declaration[defaultEffort] !== void 0
|
|
164
|
+
? { defaultEffort: ReasoningEffortId(defaultEffort) }
|
|
165
|
+
: {}),
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function httpErrorCode(
|
|
171
|
+
status: number,
|
|
172
|
+
error?: { code?: unknown; type?: unknown; message?: unknown },
|
|
173
|
+
): string {
|
|
174
|
+
if (status === 401 || status === 403) return "AUTH";
|
|
175
|
+
if (status === 413) return "INVALID_REQUEST";
|
|
176
|
+
const detail = [error?.code, error?.type, error?.message].filter(Boolean).join(" ");
|
|
177
|
+
if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE;
|
|
178
|
+
if (status === 429) return "RATE_LIMIT";
|
|
179
|
+
if (status === 400) {
|
|
180
|
+
if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE;
|
|
181
|
+
return "INVALID_REQUEST";
|
|
182
|
+
}
|
|
183
|
+
if (status >= 500) return "SERVER";
|
|
184
|
+
return `HTTP_${status}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function providerErrorBody(
|
|
188
|
+
error: APICallError,
|
|
189
|
+
): { code?: unknown; type?: unknown; message?: unknown } | undefined {
|
|
190
|
+
if (error.responseBody === void 0) return void 0;
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(error.responseBody) as {
|
|
193
|
+
error?: { code?: unknown; type?: unknown; message?: unknown };
|
|
194
|
+
};
|
|
195
|
+
return parsed.error;
|
|
196
|
+
} catch {
|
|
197
|
+
return void 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function requestId(headers: Record<string, string> | undefined): ProviderRequestId | undefined {
|
|
202
|
+
if (headers === void 0) return void 0;
|
|
203
|
+
const value = headers["x-request-id"] ?? headers["x-openai-compatible-request-id"];
|
|
204
|
+
return value === void 0 || value.length === 0 ? void 0 : ProviderRequestId(value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export class OpenAICompatibleAdapter extends LlmAdapter {
|
|
208
|
+
private readonly config: OpenAICompatibleAdapterOptions;
|
|
209
|
+
private readonly sdkProviders = new Map<ResolvedProviderProfile, Map<string, LanguageModelV4>>();
|
|
210
|
+
|
|
211
|
+
constructor(config: OpenAICompatibleAdapterOptions) {
|
|
212
|
+
super();
|
|
213
|
+
this.config = config;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private profileOf(provider: string): ResolvedProviderProfile {
|
|
217
|
+
const profile = this.config.profiles().get(provider);
|
|
218
|
+
if (profile === void 0)
|
|
219
|
+
throw new LlmError(
|
|
220
|
+
`OpenAI-compatible adapter does not own provider "${provider}"`,
|
|
221
|
+
"NO_ADAPTER",
|
|
222
|
+
);
|
|
223
|
+
return profile;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private modelOf(
|
|
227
|
+
profile: ResolvedProviderProfile,
|
|
228
|
+
model: string,
|
|
229
|
+
): ResolvedModelProfile | undefined {
|
|
230
|
+
return profile.models.find((entry) => entry.id === model);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private sdkModel(profile: ResolvedProviderProfile, modelId: string): LanguageModelV4 {
|
|
234
|
+
let byModel = this.sdkProviders.get(profile);
|
|
235
|
+
if (byModel === void 0) {
|
|
236
|
+
byModel = new Map();
|
|
237
|
+
this.sdkProviders.set(profile, byModel);
|
|
238
|
+
}
|
|
239
|
+
let model = byModel.get(modelId);
|
|
240
|
+
if (model === void 0) {
|
|
241
|
+
const provider = createOpenAICompatible({
|
|
242
|
+
name: PROVIDER_OPTIONS_KEY,
|
|
243
|
+
baseURL: profile.baseURL,
|
|
244
|
+
headers: { ...profile.headers, ...attributionHeaders() },
|
|
245
|
+
includeUsage: true,
|
|
246
|
+
convertUsage,
|
|
247
|
+
});
|
|
248
|
+
model = provider.chatModel(modelId);
|
|
249
|
+
byModel.set(modelId, model);
|
|
250
|
+
}
|
|
251
|
+
return model;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
providerInfo(provider: string): LlmProviderInfo {
|
|
255
|
+
return {
|
|
256
|
+
id: provider,
|
|
257
|
+
name: this.config.profiles().get(provider)?.displayName ?? provider,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined {
|
|
262
|
+
return this.config.profiles().get(provider)?.retryPolicy;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
listModels(provider: string): Promise<readonly LlmModelInfo[]> {
|
|
266
|
+
const profile = this.profileOf(provider);
|
|
267
|
+
return Promise.resolve(profile.models.map((model) => modelInfo(profile, model)));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
resolveModel(
|
|
271
|
+
provider: string,
|
|
272
|
+
model: string,
|
|
273
|
+
_signal?: AbortSignal,
|
|
274
|
+
): Promise<LlmResolvedModelInfo> {
|
|
275
|
+
const profile = this.profileOf(provider);
|
|
276
|
+
const configured = this.modelOf(profile, model);
|
|
277
|
+
const contextWindow = configured?.contextWindow ?? profile.defaultContextWindow;
|
|
278
|
+
const maxTokens = configured?.maxTokens ?? profile.defaultMaxTokens;
|
|
279
|
+
return Promise.resolve({
|
|
280
|
+
...(configured === void 0
|
|
281
|
+
? { provider, id: model, name: model, inputModalities: ["text" as const] }
|
|
282
|
+
: modelInfo(profile, configured)),
|
|
283
|
+
context: { contextWindow },
|
|
284
|
+
...(maxTokens !== void 0 ? { defaultMaxTokens: maxTokens } : {}),
|
|
285
|
+
...reasoningInfo(configured, profile.reasoning),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async *stream(options: GenerateOptions): AsyncGenerator<StreamChunk> {
|
|
290
|
+
const profile = this.profileOf(options.provider);
|
|
291
|
+
const model = this.modelOf(profile, options.model);
|
|
292
|
+
const hasImages = options.messages.some((message) => contentHasImage(message.content));
|
|
293
|
+
let attachments: AttachmentStore | undefined;
|
|
294
|
+
if (hasImages) {
|
|
295
|
+
if (model?.inputModalities.includes("image") !== true) {
|
|
296
|
+
throw new LlmError(
|
|
297
|
+
`OpenAI-compatible model "${options.model}" does not accept image input.`,
|
|
298
|
+
"UNSUPPORTED_CONTENT",
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
attachments = this.config.resolveAttachments?.();
|
|
302
|
+
if (attachments === void 0)
|
|
303
|
+
throw new LlmError(
|
|
304
|
+
"OpenAI-compatible image conversion requires the durable attachment service.",
|
|
305
|
+
"UNSUPPORTED_CONTENT",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
const apiKey = await this.config.resolveApiKey(options.provider, profile);
|
|
309
|
+
const userId = this.config.resolveUserId();
|
|
310
|
+
const consumer = new AbortController();
|
|
311
|
+
const upstream =
|
|
312
|
+
options.signal === void 0
|
|
313
|
+
? consumer.signal
|
|
314
|
+
: AbortSignal.any([options.signal, consumer.signal]);
|
|
315
|
+
const overall =
|
|
316
|
+
profile.timeoutMs === void 0
|
|
317
|
+
? void 0
|
|
318
|
+
: deadline(upstream, profile.timeoutMs, REQUEST_TIMEOUT_CODE);
|
|
319
|
+
const watchdog = idleWatchdog(
|
|
320
|
+
overall?.signal ?? upstream,
|
|
321
|
+
profile.streamIdleTimeoutMs,
|
|
322
|
+
STREAM_IDLE_TIMEOUT_CODE,
|
|
323
|
+
);
|
|
324
|
+
try {
|
|
325
|
+
const callOptions =
|
|
326
|
+
attachments === void 0
|
|
327
|
+
? await serializeCallOptions(options, profile, model)
|
|
328
|
+
: await serializeCallOptionsWithImages(options, profile, model, {
|
|
329
|
+
attachments,
|
|
330
|
+
maxRequestImageBytes: profile.maxRequestImageBytes,
|
|
331
|
+
signal: watchdog.signal,
|
|
332
|
+
});
|
|
333
|
+
const sdkModel = this.sdkModel(profile, options.model);
|
|
334
|
+
let result;
|
|
335
|
+
try {
|
|
336
|
+
result = await sdkModel.doStream({
|
|
337
|
+
...callOptions,
|
|
338
|
+
abortSignal: watchdog.signal,
|
|
339
|
+
headers: {
|
|
340
|
+
...(apiKey === void 0 ? {} : { authorization: `Bearer ${apiKey}` }),
|
|
341
|
+
"x-openai-compatible-harness-user-id": String(userId),
|
|
342
|
+
...(options.sessionId !== void 0
|
|
343
|
+
? { "x-openai-compatible-harness-session-id": String(options.sessionId) }
|
|
344
|
+
: {}),
|
|
345
|
+
...(options.purpose === "compaction"
|
|
346
|
+
? { "x-openai-compatible-harness-compact": "1" }
|
|
347
|
+
: {}),
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw this.normalizeTransportError(error, profile);
|
|
352
|
+
}
|
|
353
|
+
const iterator = translate(result.stream)[Symbol.asyncIterator]();
|
|
354
|
+
let exhausted = false;
|
|
355
|
+
try {
|
|
356
|
+
while (true) {
|
|
357
|
+
const next = await watchdog.next(iterator);
|
|
358
|
+
if (next.done) {
|
|
359
|
+
exhausted = true;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
yield next.value;
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (timeoutOf(watchdog.signal, STREAM_IDLE_TIMEOUT_CODE) !== void 0) {
|
|
366
|
+
throw new LlmError(
|
|
367
|
+
`OpenAI-compatible stream idle timeout after ${profile.streamIdleTimeoutMs}ms`,
|
|
368
|
+
"TIMEOUT",
|
|
369
|
+
{ cause: error },
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (
|
|
373
|
+
profile.timeoutMs !== void 0 &&
|
|
374
|
+
timeoutOf(watchdog.signal, REQUEST_TIMEOUT_CODE) !== void 0
|
|
375
|
+
) {
|
|
376
|
+
throw new LlmError(
|
|
377
|
+
`OpenAI-compatible request timeout after ${profile.timeoutMs}ms`,
|
|
378
|
+
"TIMEOUT",
|
|
379
|
+
{ cause: error },
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (options.signal?.aborted)
|
|
383
|
+
throw new LlmError("OpenAI-compatible request aborted by caller", "ABORTED", {
|
|
384
|
+
cause: error,
|
|
385
|
+
});
|
|
386
|
+
if (error instanceof LlmError) throw error;
|
|
387
|
+
throw this.normalizeTransportError(error, profile);
|
|
388
|
+
} finally {
|
|
389
|
+
consumer.abort("OpenAI-compatible stream consumer stopped");
|
|
390
|
+
if (!exhausted) {
|
|
391
|
+
try {
|
|
392
|
+
await iterator.return(void 0);
|
|
393
|
+
} catch {
|
|
394
|
+
// The transport already aborted; teardown is best-effort.
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} finally {
|
|
399
|
+
watchdog[Symbol.dispose]();
|
|
400
|
+
overall?.[Symbol.dispose]();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private normalizeTransportError(error: unknown, profile: ResolvedProviderProfile): LlmError {
|
|
405
|
+
if (error instanceof LlmError) return error;
|
|
406
|
+
if (APICallError.isInstance(error)) {
|
|
407
|
+
const providerError = providerErrorBody(error);
|
|
408
|
+
const message =
|
|
409
|
+
typeof providerError?.message === "string" ? providerError.message : error.message;
|
|
410
|
+
const id = requestId(error.responseHeaders);
|
|
411
|
+
return new LlmError(message, httpErrorCode(error.statusCode ?? 0, providerError), {
|
|
412
|
+
...(error.statusCode === void 0 ? {} : { status: error.statusCode }),
|
|
413
|
+
...(id === void 0 ? {} : { requestId: id }),
|
|
414
|
+
cause: error,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (error instanceof Error) {
|
|
418
|
+
return new LlmError(
|
|
419
|
+
`OpenAI-compatible API request to ${profile.baseURL} failed`,
|
|
420
|
+
"TRANSPORT",
|
|
421
|
+
{ cause: error },
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
return new LlmError(`OpenAI-compatible API request to ${profile.baseURL} failed`, "TRANSPORT");
|
|
425
|
+
}
|
|
426
|
+
}
|