@amux.ai/llm-bridge 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,909 @@
1
+ 'use strict';
2
+
3
+ // src/adapter/registry.ts
4
+ var AdapterRegistry = class {
5
+ adapters = /* @__PURE__ */ new Map();
6
+ /**
7
+ * Register an adapter
8
+ */
9
+ register(adapter) {
10
+ if (this.adapters.has(adapter.name)) {
11
+ throw new Error(`Adapter "${adapter.name}" is already registered`);
12
+ }
13
+ this.adapters.set(adapter.name, adapter);
14
+ }
15
+ /**
16
+ * Unregister an adapter
17
+ */
18
+ unregister(name) {
19
+ return this.adapters.delete(name);
20
+ }
21
+ /**
22
+ * Get an adapter by name
23
+ */
24
+ get(name) {
25
+ return this.adapters.get(name);
26
+ }
27
+ /**
28
+ * Check if an adapter is registered
29
+ */
30
+ has(name) {
31
+ return this.adapters.has(name);
32
+ }
33
+ /**
34
+ * List all registered adapters
35
+ */
36
+ list() {
37
+ return Array.from(this.adapters.values()).map(
38
+ (adapter) => adapter.getInfo()
39
+ );
40
+ }
41
+ /**
42
+ * Clear all adapters
43
+ */
44
+ clear() {
45
+ this.adapters.clear();
46
+ }
47
+ };
48
+ var globalRegistry = new AdapterRegistry();
49
+
50
+ // src/utils/sse-parser.ts
51
+ var SSELineParser = class {
52
+ buffer = "";
53
+ /**
54
+ * Process a chunk of SSE data and extract complete lines
55
+ * @param chunk - The data chunk to process
56
+ * @returns Array of complete lines
57
+ */
58
+ processChunk(chunk) {
59
+ this.buffer += chunk;
60
+ return this.extractLines();
61
+ }
62
+ /**
63
+ * Extract all complete lines from the buffer
64
+ * Incomplete lines remain in the buffer
65
+ * @private
66
+ */
67
+ extractLines() {
68
+ const lines = [];
69
+ let position = 0;
70
+ while (position < this.buffer.length) {
71
+ const newlineIndex = this.buffer.indexOf("\n", position);
72
+ if (newlineIndex === -1) {
73
+ this.buffer = this.buffer.slice(position);
74
+ break;
75
+ }
76
+ const line = this.buffer.slice(position, newlineIndex);
77
+ lines.push(line);
78
+ position = newlineIndex + 1;
79
+ }
80
+ if (position >= this.buffer.length) {
81
+ this.buffer = "";
82
+ }
83
+ return lines;
84
+ }
85
+ /**
86
+ * Get any remaining data in the buffer and clear it
87
+ * Call this when the stream ends to get the last incomplete line
88
+ */
89
+ flush() {
90
+ if (this.buffer.length === 0) {
91
+ return [];
92
+ }
93
+ const lines = this.buffer.split("\n").filter((line) => line.length > 0);
94
+ this.buffer = "";
95
+ return lines;
96
+ }
97
+ /**
98
+ * Check if buffer has any remaining data
99
+ */
100
+ hasRemaining() {
101
+ return this.buffer.length > 0;
102
+ }
103
+ /**
104
+ * Clear the buffer
105
+ */
106
+ clear() {
107
+ this.buffer = "";
108
+ }
109
+ };
110
+
111
+ // src/errors/index.ts
112
+ var LLMBridgeError = class extends Error {
113
+ code;
114
+ retryable;
115
+ details;
116
+ constructor(message, code, retryable = false, details) {
117
+ super(message);
118
+ this.name = "LLMBridgeError";
119
+ this.code = code;
120
+ this.retryable = retryable;
121
+ this.details = details;
122
+ Error.captureStackTrace(this, this.constructor);
123
+ }
124
+ };
125
+ var APIError = class extends LLMBridgeError {
126
+ status;
127
+ provider;
128
+ data;
129
+ response;
130
+ constructor(message, status, provider, data, response) {
131
+ super(message, "API_ERROR", status >= 500, data);
132
+ this.name = "APIError";
133
+ this.status = status;
134
+ this.provider = provider;
135
+ this.data = data;
136
+ this.response = response;
137
+ }
138
+ };
139
+ var NetworkError = class extends LLMBridgeError {
140
+ cause;
141
+ constructor(message, cause) {
142
+ super(message, "NETWORK_ERROR", true, cause);
143
+ this.name = "NetworkError";
144
+ this.cause = cause;
145
+ }
146
+ };
147
+ var TimeoutError = class extends LLMBridgeError {
148
+ timeout;
149
+ constructor(message, timeout) {
150
+ super(message, "TIMEOUT_ERROR", true, { timeout });
151
+ this.name = "TimeoutError";
152
+ this.timeout = timeout;
153
+ }
154
+ };
155
+ var ValidationError = class extends LLMBridgeError {
156
+ errors;
157
+ constructor(message, errors) {
158
+ super(message, "VALIDATION_ERROR", false, { errors });
159
+ this.name = "ValidationError";
160
+ this.errors = errors;
161
+ }
162
+ };
163
+ var AdapterError = class extends LLMBridgeError {
164
+ adapterName;
165
+ constructor(message, adapterName, details) {
166
+ super(message, "ADAPTER_ERROR", false, details);
167
+ this.name = "AdapterError";
168
+ this.adapterName = adapterName;
169
+ }
170
+ };
171
+ var BridgeError = class extends LLMBridgeError {
172
+ constructor(message, details) {
173
+ super(message, "BRIDGE_ERROR", false, details);
174
+ this.name = "BridgeError";
175
+ }
176
+ };
177
+
178
+ // src/bridge/http-client.ts
179
+ var HTTPClient = class {
180
+ defaultHeaders;
181
+ defaultTimeout;
182
+ maxRetries;
183
+ provider;
184
+ maxResponseSize;
185
+ constructor(options) {
186
+ this.defaultHeaders = options?.headers ?? {};
187
+ this.defaultTimeout = options?.timeout ?? 6e4;
188
+ this.maxRetries = options?.maxRetries ?? 3;
189
+ this.provider = options?.provider ?? "unknown";
190
+ this.maxResponseSize = options?.maxResponseSize ?? 100 * 1024 * 1024;
191
+ }
192
+ /**
193
+ * Determine if a status code should be retried
194
+ * @private
195
+ */
196
+ shouldRetry(status) {
197
+ const retryableStatuses = [408, 429, 500, 502, 503, 504];
198
+ return retryableStatuses.includes(status);
199
+ }
200
+ /**
201
+ * Calculate backoff delay with jitter
202
+ * @private
203
+ */
204
+ getBackoffDelay(attempt, retryAfter) {
205
+ if (retryAfter !== void 0 && retryAfter > 0) {
206
+ return retryAfter * 1e3;
207
+ }
208
+ const baseDelay = Math.pow(2, attempt) * 1e3;
209
+ const jitter = Math.random() * baseDelay * 0.1;
210
+ return baseDelay + jitter;
211
+ }
212
+ /**
213
+ * Make an HTTP request with retry logic
214
+ */
215
+ async request(options, retries = 0) {
216
+ const controller = new AbortController();
217
+ const timeoutId = setTimeout(() => {
218
+ controller.abort();
219
+ }, options.timeout ?? this.defaultTimeout);
220
+ try {
221
+ const response = await fetch(options.url, {
222
+ method: options.method,
223
+ headers: {
224
+ "Content-Type": "application/json",
225
+ ...this.defaultHeaders,
226
+ ...options.headers
227
+ },
228
+ body: options.body ? JSON.stringify(options.body) : void 0,
229
+ signal: options.signal ?? controller.signal
230
+ });
231
+ clearTimeout(timeoutId);
232
+ const contentLength = response.headers.get("content-length");
233
+ if (contentLength) {
234
+ const size = parseInt(contentLength, 10);
235
+ if (size > this.maxResponseSize) {
236
+ throw new NetworkError(
237
+ `Response size (${size} bytes) exceeds maximum allowed size (${this.maxResponseSize} bytes)`
238
+ );
239
+ }
240
+ }
241
+ if (!response.ok) {
242
+ const errorData = await response.json().catch(() => ({}));
243
+ const headers = Object.fromEntries(response.headers.entries());
244
+ throw new APIError(
245
+ `HTTP ${response.status}: ${response.statusText}`,
246
+ response.status,
247
+ this.provider,
248
+ errorData,
249
+ { headers }
250
+ );
251
+ }
252
+ const data = await response.json();
253
+ return {
254
+ status: response.status,
255
+ statusText: response.statusText,
256
+ headers: Object.fromEntries(response.headers.entries()),
257
+ data
258
+ };
259
+ } catch (error) {
260
+ clearTimeout(timeoutId);
261
+ if (error instanceof APIError) {
262
+ if (this.shouldRetry(error.status) && retries < this.maxRetries) {
263
+ const retryAfter = error.response?.headers?.["retry-after"];
264
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
265
+ const delay = this.getBackoffDelay(retries, retryAfterSeconds);
266
+ await new Promise((resolve) => setTimeout(resolve, delay));
267
+ return this.request(options, retries + 1);
268
+ }
269
+ throw error;
270
+ }
271
+ if (error instanceof Error && error.name === "AbortError") {
272
+ const timeoutError = new TimeoutError(
273
+ "Request timeout",
274
+ options.timeout ?? this.defaultTimeout
275
+ );
276
+ if (retries < this.maxRetries) {
277
+ const delay = this.getBackoffDelay(retries);
278
+ await new Promise((resolve) => setTimeout(resolve, delay));
279
+ return this.request(options, retries + 1);
280
+ }
281
+ throw timeoutError;
282
+ }
283
+ const networkError = new NetworkError("Network request failed", error);
284
+ if (retries < this.maxRetries) {
285
+ const delay = this.getBackoffDelay(retries);
286
+ await new Promise((resolve) => setTimeout(resolve, delay));
287
+ return this.request(options, retries + 1);
288
+ }
289
+ throw networkError;
290
+ }
291
+ }
292
+ /**
293
+ * Make a streaming HTTP request
294
+ */
295
+ async *requestStream(options) {
296
+ const controller = new AbortController();
297
+ const timeoutId = setTimeout(() => {
298
+ controller.abort();
299
+ }, options.timeout ?? this.defaultTimeout);
300
+ try {
301
+ const headers = {
302
+ "Content-Type": "application/json",
303
+ Accept: "text/event-stream",
304
+ ...this.defaultHeaders,
305
+ ...options.headers
306
+ };
307
+ const response = await fetch(options.url, {
308
+ method: options.method,
309
+ headers,
310
+ body: options.body ? JSON.stringify(options.body) : void 0,
311
+ signal: options.signal ?? controller.signal
312
+ });
313
+ clearTimeout(timeoutId);
314
+ if (!response.ok) {
315
+ const errorData = await response.json().catch(() => ({}));
316
+ const headers2 = Object.fromEntries(response.headers.entries());
317
+ throw new APIError(
318
+ `HTTP ${response.status}: ${response.statusText}`,
319
+ response.status,
320
+ this.provider,
321
+ errorData,
322
+ { headers: headers2 }
323
+ );
324
+ }
325
+ if (!response.body) {
326
+ throw new NetworkError("Response body is null");
327
+ }
328
+ const reader = response.body.getReader();
329
+ const decoder = new TextDecoder();
330
+ let totalBytes = 0;
331
+ try {
332
+ while (true) {
333
+ const { done, value } = await reader.read();
334
+ if (done) break;
335
+ totalBytes += value.length;
336
+ if (totalBytes > this.maxResponseSize) {
337
+ throw new NetworkError(
338
+ `Streaming response size (${totalBytes} bytes) exceeds maximum allowed size (${this.maxResponseSize} bytes)`
339
+ );
340
+ }
341
+ const chunk = decoder.decode(value, { stream: true });
342
+ yield chunk;
343
+ }
344
+ } finally {
345
+ reader.releaseLock();
346
+ }
347
+ } catch (error) {
348
+ clearTimeout(timeoutId);
349
+ if (error instanceof APIError) {
350
+ throw error;
351
+ }
352
+ if (error instanceof Error && error.name === "AbortError") {
353
+ throw new TimeoutError(
354
+ "Request timeout",
355
+ options.timeout ?? this.defaultTimeout
356
+ );
357
+ }
358
+ if (error instanceof NetworkError) {
359
+ throw error;
360
+ }
361
+ throw new NetworkError("Network request failed", error);
362
+ }
363
+ }
364
+ };
365
+
366
+ // src/bridge/bridge.ts
367
+ var Bridge = class {
368
+ inboundAdapter;
369
+ outboundAdapter;
370
+ config;
371
+ httpClient;
372
+ // Lifecycle hooks
373
+ hooks;
374
+ // Model mapping configuration
375
+ targetModel;
376
+ modelMapper;
377
+ modelMapping;
378
+ constructor(options) {
379
+ this.inboundAdapter = options.inbound;
380
+ this.outboundAdapter = options.outbound;
381
+ this.config = options.config;
382
+ this.hooks = options.hooks;
383
+ this.targetModel = options.targetModel;
384
+ this.modelMapper = options.modelMapper;
385
+ this.modelMapping = options.modelMapping;
386
+ const authHeaderName = this.config.authHeaderName ?? "Authorization";
387
+ const authHeaderPrefix = this.config.authHeaderPrefix ?? "Bearer";
388
+ const authHeaderValue = authHeaderPrefix ? `${authHeaderPrefix} ${this.config.apiKey}` : this.config.apiKey;
389
+ this.httpClient = new HTTPClient({
390
+ headers: {
391
+ [authHeaderName]: authHeaderValue,
392
+ ...this.config.headers
393
+ },
394
+ timeout: this.config.timeout,
395
+ maxRetries: this.config.maxRetries,
396
+ provider: this.outboundAdapter.name
397
+ });
398
+ }
399
+ /**
400
+ * Map model name from inbound to outbound
401
+ * Priority: targetModel > modelMapper > modelMapping > original model
402
+ */
403
+ mapModel(inboundModel) {
404
+ if (!inboundModel) return void 0;
405
+ if (this.targetModel) {
406
+ return this.targetModel;
407
+ }
408
+ if (this.modelMapper) {
409
+ return this.modelMapper(inboundModel);
410
+ }
411
+ if (this.modelMapping && this.modelMapping[inboundModel]) {
412
+ return this.modelMapping[inboundModel];
413
+ }
414
+ return inboundModel;
415
+ }
416
+ /**
417
+ * Validate that the IR request features are supported by outbound adapter
418
+ * @private
419
+ */
420
+ validateCapabilities(ir) {
421
+ const cap = this.outboundAdapter.capabilities;
422
+ if (ir.tools && ir.tools.length > 0 && !cap.tools) {
423
+ throw new Error(
424
+ `Outbound adapter '${this.outboundAdapter.name}' does not support tools`
425
+ );
426
+ }
427
+ if (!cap.vision) {
428
+ const hasVisionContent = ir.messages.some((msg) => {
429
+ if (typeof msg.content === "string") return false;
430
+ return msg.content.some((part) => part.type === "image");
431
+ });
432
+ if (hasVisionContent) {
433
+ throw new Error(
434
+ `Outbound adapter '${this.outboundAdapter.name}' does not support vision`
435
+ );
436
+ }
437
+ }
438
+ if (ir.generation?.thinking && !cap.reasoning) {
439
+ throw new Error(
440
+ `Outbound adapter '${this.outboundAdapter.name}' does not support reasoning/thinking`
441
+ );
442
+ }
443
+ }
444
+ /**
445
+ * Send a chat request
446
+ */
447
+ async chat(request) {
448
+ try {
449
+ const ir = this.inboundAdapter.inbound.parseRequest(request);
450
+ if (ir.model) {
451
+ ir.model = this.mapModel(ir.model);
452
+ }
453
+ if (this.hooks?.onRequest) {
454
+ await this.hooks.onRequest(ir);
455
+ }
456
+ if (this.inboundAdapter.validateRequest) {
457
+ const validation = this.inboundAdapter.validateRequest(ir);
458
+ if (!validation.valid) {
459
+ throw new Error(
460
+ `Invalid request: ${validation.errors?.join(", ") ?? "Unknown error"}`
461
+ );
462
+ }
463
+ }
464
+ this.validateCapabilities(ir);
465
+ const providerRequest = this.outboundAdapter.outbound.buildRequest(ir);
466
+ const baseURL = this.config.baseURL ?? this.getDefaultBaseURL();
467
+ const endpoint = this.getEndpoint(ir.model);
468
+ const response = await this.httpClient.request({
469
+ method: "POST",
470
+ url: `${baseURL}${endpoint}`,
471
+ body: providerRequest
472
+ });
473
+ const responseIR = this.outboundAdapter.inbound.parseResponse?.(
474
+ response.data
475
+ );
476
+ if (!responseIR) {
477
+ throw new Error("Outbound adapter does not support response parsing");
478
+ }
479
+ if (this.hooks?.onResponse) {
480
+ await this.hooks.onResponse(responseIR);
481
+ }
482
+ const finalResponse = this.inboundAdapter.outbound.buildResponse?.(responseIR);
483
+ if (!finalResponse) {
484
+ return responseIR;
485
+ }
486
+ return finalResponse;
487
+ } catch (error) {
488
+ if (this.hooks?.onError && error instanceof Error) {
489
+ try {
490
+ const errorIR = this.outboundAdapter.inbound.parseError?.(error) ?? {
491
+ message: error.message,
492
+ code: "UNKNOWN_ERROR",
493
+ type: "unknown"
494
+ };
495
+ await this.hooks.onError(errorIR);
496
+ } catch (hookError) {
497
+ console.warn("Error in onError hook:", hookError);
498
+ }
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ /**
504
+ * Send a chat request (raw IR response)
505
+ * Returns raw IR response for custom processing
506
+ */
507
+ async chatRaw(request) {
508
+ const ir = this.inboundAdapter.inbound.parseRequest(request);
509
+ if (ir.model) {
510
+ ir.model = this.mapModel(ir.model);
511
+ }
512
+ if (this.inboundAdapter.validateRequest) {
513
+ const validation = this.inboundAdapter.validateRequest(ir);
514
+ if (!validation.valid) {
515
+ throw new Error(
516
+ `Invalid request: ${validation.errors?.join(", ") ?? "Unknown error"}`
517
+ );
518
+ }
519
+ }
520
+ const providerRequest = this.outboundAdapter.outbound.buildRequest(ir);
521
+ const baseURL = this.config.baseURL ?? this.getDefaultBaseURL();
522
+ const endpoint = this.getEndpoint(ir.model);
523
+ const response = await this.httpClient.request({
524
+ method: "POST",
525
+ url: `${baseURL}${endpoint}`,
526
+ body: providerRequest
527
+ });
528
+ const responseIR = this.outboundAdapter.inbound.parseResponse?.(
529
+ response.data
530
+ );
531
+ if (!responseIR) {
532
+ throw new Error("Outbound adapter does not support response parsing");
533
+ }
534
+ return responseIR;
535
+ }
536
+ /**
537
+ * Send a streaming chat request
538
+ * Returns SSE events in inbound adapter's format
539
+ */
540
+ async *chatStream(request) {
541
+ const streamBuilder = this.inboundAdapter.outbound.createStreamBuilder?.();
542
+ if (!streamBuilder) {
543
+ for await (const event of this.chatStreamRaw(request)) {
544
+ yield { event: "data", data: event };
545
+ }
546
+ return;
547
+ }
548
+ for await (const event of this.chatStreamRaw(request)) {
549
+ const sseEvents = streamBuilder.process(event);
550
+ for (const sse of sseEvents) {
551
+ yield sse;
552
+ }
553
+ }
554
+ if (streamBuilder.finalize) {
555
+ const finalEvents = streamBuilder.finalize();
556
+ for (const sse of finalEvents) {
557
+ if (sse.data === "[DONE]") {
558
+ continue;
559
+ }
560
+ yield sse;
561
+ }
562
+ }
563
+ }
564
+ /**
565
+ * Send a streaming chat request (raw IR events)
566
+ * Returns raw IR stream events for custom processing
567
+ */
568
+ async *chatStreamRaw(request) {
569
+ try {
570
+ const ir = this.inboundAdapter.inbound.parseRequest(request);
571
+ if (ir.model) {
572
+ ir.model = this.mapModel(ir.model);
573
+ }
574
+ ir.stream = true;
575
+ if (this.hooks?.onRequest) {
576
+ await this.hooks.onRequest(ir);
577
+ }
578
+ if (!this.outboundAdapter.capabilities.streaming) {
579
+ throw new Error(
580
+ `Outbound adapter '${this.outboundAdapter.name}' does not support streaming`
581
+ );
582
+ }
583
+ this.validateCapabilities(ir);
584
+ const providerRequest = this.outboundAdapter.outbound.buildRequest(ir);
585
+ const baseURL = this.config.baseURL ?? this.getDefaultBaseURL();
586
+ const endpoint = this.getEndpoint(ir.model);
587
+ const streamHandler = this.outboundAdapter.inbound.parseStream;
588
+ if (!streamHandler) {
589
+ throw new Error("Outbound adapter does not support streaming");
590
+ }
591
+ const sseParser = new SSELineParser();
592
+ for await (const chunk of this.httpClient.requestStream({
593
+ method: "POST",
594
+ url: `${baseURL}${endpoint}`,
595
+ body: providerRequest
596
+ })) {
597
+ const lines = sseParser.processChunk(chunk);
598
+ for await (const event of this.processSSELines(lines, streamHandler)) {
599
+ yield event;
600
+ }
601
+ }
602
+ if (sseParser.hasRemaining()) {
603
+ const lines = sseParser.flush();
604
+ for await (const event of this.processSSELines(lines, streamHandler)) {
605
+ yield event;
606
+ }
607
+ }
608
+ } catch (error) {
609
+ if (this.hooks?.onError && error instanceof Error) {
610
+ try {
611
+ const errorIR = this.outboundAdapter.inbound.parseError?.(error) ?? {
612
+ message: error.message,
613
+ code: "UNKNOWN_ERROR",
614
+ type: "unknown"
615
+ };
616
+ await this.hooks.onError(errorIR);
617
+ } catch (hookError) {
618
+ console.warn("Error in onError hook:", hookError);
619
+ }
620
+ }
621
+ throw error;
622
+ }
623
+ }
624
+ /**
625
+ * Process SSE lines and yield stream events
626
+ * @private
627
+ */
628
+ async *processSSELines(lines, streamHandler) {
629
+ for (const line of lines) {
630
+ if (line.startsWith("data: ")) {
631
+ const data = line.slice(6).trim();
632
+ if (data === "[DONE]") continue;
633
+ try {
634
+ const parsed = JSON.parse(data);
635
+ const events = streamHandler(parsed);
636
+ if (events) {
637
+ const eventArray = Array.isArray(events) ? events : [events];
638
+ for (const event of eventArray) {
639
+ if (this.hooks?.onStreamEvent) {
640
+ await this.hooks.onStreamEvent(event);
641
+ }
642
+ yield event;
643
+ }
644
+ }
645
+ } catch {
646
+ continue;
647
+ }
648
+ }
649
+ }
650
+ }
651
+ /**
652
+ * List available models from the provider
653
+ */
654
+ async listModels() {
655
+ const baseURL = this.config.baseURL ?? this.getDefaultBaseURL();
656
+ const modelsPath = this.getModelsPath();
657
+ const response = await this.httpClient.request({
658
+ method: "GET",
659
+ url: `${baseURL}${modelsPath}`
660
+ });
661
+ return response.data;
662
+ }
663
+ /**
664
+ * Check compatibility between adapters
665
+ */
666
+ checkCompatibility() {
667
+ const issues = [];
668
+ const warnings = [];
669
+ const inCap = this.inboundAdapter.capabilities;
670
+ const outCap = this.outboundAdapter.capabilities;
671
+ if (inCap.tools && !outCap.tools) {
672
+ issues.push(
673
+ "Inbound adapter supports tools but outbound adapter does not"
674
+ );
675
+ }
676
+ if (inCap.vision && !outCap.vision) {
677
+ warnings.push(
678
+ "Inbound adapter supports vision but outbound adapter does not"
679
+ );
680
+ }
681
+ if (inCap.streaming && !outCap.streaming) {
682
+ warnings.push(
683
+ "Inbound adapter supports streaming but outbound adapter does not"
684
+ );
685
+ }
686
+ if (inCap.reasoning && !outCap.reasoning) {
687
+ warnings.push(
688
+ "Inbound adapter supports reasoning but outbound adapter does not - reasoning content may be lost"
689
+ );
690
+ }
691
+ return {
692
+ compatible: issues.length === 0,
693
+ issues: issues.length > 0 ? issues : void 0,
694
+ warnings: warnings.length > 0 ? warnings : void 0
695
+ };
696
+ }
697
+ /**
698
+ * Get adapter information
699
+ */
700
+ getAdapters() {
701
+ return {
702
+ inbound: {
703
+ name: this.inboundAdapter.name,
704
+ version: this.inboundAdapter.version
705
+ },
706
+ outbound: {
707
+ name: this.outboundAdapter.name,
708
+ version: this.outboundAdapter.version
709
+ }
710
+ };
711
+ }
712
+ /**
713
+ * Get default base URL from outbound adapter
714
+ * Note: config.baseURL is checked before calling this method
715
+ */
716
+ getDefaultBaseURL() {
717
+ const endpoint = this.outboundAdapter.getInfo().endpoint;
718
+ return endpoint?.baseUrl ?? "";
719
+ }
720
+ /**
721
+ * Get chat endpoint path
722
+ * Supports dynamic model replacement for endpoints like /v1beta/models/{model}:generateContent
723
+ * Priority: config.chatPath > adapter.endpoint.chatPath
724
+ */
725
+ getEndpoint(model) {
726
+ const endpoint = this.outboundAdapter.getInfo().endpoint;
727
+ let chatPath = this.config.chatPath ?? endpoint?.chatPath;
728
+ if (!chatPath) {
729
+ throw new Error(
730
+ `No chatPath configured. Either provide chatPath in config or ensure adapter '${this.outboundAdapter.name}' defines endpoint.chatPath`
731
+ );
732
+ }
733
+ if (model && chatPath.includes("{model}")) {
734
+ chatPath = chatPath.replace("{model}", model);
735
+ }
736
+ return chatPath;
737
+ }
738
+ /**
739
+ * Get models endpoint path
740
+ * Priority: config.modelsPath > adapter.endpoint.modelsPath
741
+ */
742
+ getModelsPath() {
743
+ const endpoint = this.outboundAdapter.getInfo().endpoint;
744
+ const modelsPath = this.config.modelsPath ?? endpoint?.modelsPath;
745
+ if (!modelsPath) {
746
+ throw new Error(
747
+ `No modelsPath configured. Either provide modelsPath in config or ensure adapter '${this.outboundAdapter.name}' defines endpoint.modelsPath`
748
+ );
749
+ }
750
+ return modelsPath;
751
+ }
752
+ };
753
+
754
+ // src/bridge/factory.ts
755
+ function createBridge(options) {
756
+ return new Bridge(options);
757
+ }
758
+
759
+ // src/utils/error-parser.ts
760
+ var STANDARD_ERROR_TYPE_MAP = {
761
+ invalid_request_error: "validation",
762
+ authentication_error: "authentication",
763
+ permission_error: "permission",
764
+ not_found_error: "not_found",
765
+ rate_limit_error: "rate_limit",
766
+ api_error: "api",
767
+ server_error: "server",
768
+ insufficient_quota: "rate_limit",
769
+ // Common in some providers
770
+ invalid_api_key: "authentication"
771
+ // Zhipu-specific
772
+ };
773
+ var STANDARD_ERROR_CODE_MAP = {
774
+ InvalidParameter: "validation",
775
+ InvalidApiKey: "authentication",
776
+ AccessDenied: "permission",
777
+ ModelNotFound: "not_found",
778
+ Throttling: "rate_limit",
779
+ InternalError: "server"
780
+ };
781
+ var STANDARD_FINISH_REASON_MAP = {
782
+ stop: "stop",
783
+ length: "length",
784
+ tool_calls: "tool_calls",
785
+ content_filter: "content_filter",
786
+ function_call: "tool_calls",
787
+ // Legacy OpenAI
788
+ insufficient_system_resource: "error",
789
+ // DeepSeek-specific
790
+ sensitive: "content_filter"
791
+ // Zhipu-specific
792
+ };
793
+ function mapFinishReason(reason, customMappings, defaultReason = "stop") {
794
+ if (!reason) return defaultReason;
795
+ const reasonMap = customMappings ? { ...STANDARD_FINISH_REASON_MAP, ...customMappings } : STANDARD_FINISH_REASON_MAP;
796
+ return reasonMap[reason] ?? defaultReason;
797
+ }
798
+ function mapErrorType(type, code, customTypeMappings, customCodeMappings) {
799
+ if (code) {
800
+ const codeMap = customCodeMappings ? { ...STANDARD_ERROR_CODE_MAP, ...customCodeMappings } : STANDARD_ERROR_CODE_MAP;
801
+ if (codeMap[code]) return codeMap[code];
802
+ }
803
+ if (type) {
804
+ const typeMap = customTypeMappings ? { ...STANDARD_ERROR_TYPE_MAP, ...customTypeMappings } : STANDARD_ERROR_TYPE_MAP;
805
+ return typeMap[type] ?? "unknown";
806
+ }
807
+ return "unknown";
808
+ }
809
+ function parseOpenAICompatibleError(error, customTypeMappings, customCodeMappings) {
810
+ if (error && typeof error === "object" && "error" in error) {
811
+ const err = error.error;
812
+ return {
813
+ type: mapErrorType(err.type, err.code, customTypeMappings, customCodeMappings),
814
+ message: err.message,
815
+ code: err.code,
816
+ raw: error
817
+ };
818
+ }
819
+ return {
820
+ type: "unknown",
821
+ message: String(error),
822
+ raw: error
823
+ };
824
+ }
825
+
826
+ // src/utils/content-helpers.ts
827
+ function contentToString(content) {
828
+ if (typeof content === "string") {
829
+ return content || null;
830
+ }
831
+ if (!content || content.length === 0) {
832
+ return null;
833
+ }
834
+ return content.filter((part) => part.type === "text").map((part) => part.type === "text" ? part.text : "").join("") || null;
835
+ }
836
+ function isTextOnlyContent(content) {
837
+ if (typeof content === "string") {
838
+ return true;
839
+ }
840
+ return content.every((part) => part.type === "text");
841
+ }
842
+ function extractTextFromContent(content) {
843
+ if (typeof content === "string") {
844
+ return [content];
845
+ }
846
+ return content.filter((part) => part.type === "text").map((part) => part.type === "text" ? part.text : "");
847
+ }
848
+ function hasImageContent(content) {
849
+ if (typeof content === "string") {
850
+ return false;
851
+ }
852
+ return content.some((part) => part.type === "image");
853
+ }
854
+
855
+ // src/utils/usage-parser.ts
856
+ function parseOpenAIUsage(usage) {
857
+ if (!usage) return void 0;
858
+ return {
859
+ promptTokens: usage.prompt_tokens,
860
+ completionTokens: usage.completion_tokens,
861
+ totalTokens: usage.total_tokens,
862
+ details: usage.completion_tokens_details?.reasoning_tokens || usage.prompt_cache_hit_tokens ? {
863
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
864
+ cachedTokens: usage.prompt_cache_hit_tokens
865
+ } : void 0
866
+ };
867
+ }
868
+ function buildOpenAIUsage(usage, includeReasoningTokens = true, includeCacheTokens = false) {
869
+ if (!usage) return void 0;
870
+ const result = {
871
+ prompt_tokens: usage.promptTokens,
872
+ completion_tokens: usage.completionTokens,
873
+ total_tokens: usage.totalTokens
874
+ };
875
+ if (includeReasoningTokens && usage.details?.reasoningTokens) {
876
+ result.completion_tokens_details = {
877
+ reasoning_tokens: usage.details.reasoningTokens
878
+ };
879
+ }
880
+ if (includeCacheTokens && usage.details?.cachedTokens) {
881
+ result.prompt_cache_hit_tokens = usage.details.cachedTokens;
882
+ }
883
+ return result;
884
+ }
885
+
886
+ exports.APIError = APIError;
887
+ exports.AdapterError = AdapterError;
888
+ exports.AdapterRegistry = AdapterRegistry;
889
+ exports.Bridge = Bridge;
890
+ exports.BridgeError = BridgeError;
891
+ exports.HTTPClient = HTTPClient;
892
+ exports.LLMBridgeError = LLMBridgeError;
893
+ exports.NetworkError = NetworkError;
894
+ exports.SSELineParser = SSELineParser;
895
+ exports.TimeoutError = TimeoutError;
896
+ exports.ValidationError = ValidationError;
897
+ exports.buildOpenAIUsage = buildOpenAIUsage;
898
+ exports.contentToString = contentToString;
899
+ exports.createBridge = createBridge;
900
+ exports.extractTextFromContent = extractTextFromContent;
901
+ exports.globalRegistry = globalRegistry;
902
+ exports.hasImageContent = hasImageContent;
903
+ exports.isTextOnlyContent = isTextOnlyContent;
904
+ exports.mapErrorType = mapErrorType;
905
+ exports.mapFinishReason = mapFinishReason;
906
+ exports.parseOpenAICompatibleError = parseOpenAICompatibleError;
907
+ exports.parseOpenAIUsage = parseOpenAIUsage;
908
+ //# sourceMappingURL=index.cjs.map
909
+ //# sourceMappingURL=index.cjs.map