@animalabs/membrane 0.1.3 → 0.1.5
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/membrane.d.ts.map +1 -1
- package/dist/membrane.js +25 -13
- package/dist/membrane.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +17 -15
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +16 -13
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +16 -13
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +16 -13
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/types/errors.d.ts +10 -10
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +20 -10
- package/dist/types/errors.js.map +1 -1
- package/dist/types/provider.d.ts +2 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/membrane.ts +33 -17
- package/src/providers/anthropic.ts +30 -28
- package/src/providers/openai-compatible.ts +41 -37
- package/src/providers/openai.ts +44 -40
- package/src/providers/openrouter.ts +46 -42
- package/src/types/errors.ts +20 -10
- package/src/types/provider.ts +4 -1
|
@@ -137,12 +137,12 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
137
137
|
options?: ProviderRequestOptions
|
|
138
138
|
): Promise<ProviderResponse> {
|
|
139
139
|
const openRouterRequest = this.buildRequest(request);
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
try {
|
|
142
142
|
const response = await this.makeRequest(openRouterRequest, options);
|
|
143
|
-
return this.parseResponse(response, request.model);
|
|
143
|
+
return this.parseResponse(response, request.model, openRouterRequest);
|
|
144
144
|
} catch (error) {
|
|
145
|
-
throw this.handleError(error);
|
|
145
|
+
throw this.handleError(error, openRouterRequest);
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -155,7 +155,7 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
155
155
|
openRouterRequest.stream = true;
|
|
156
156
|
// Request usage data in stream for cache metrics
|
|
157
157
|
openRouterRequest.stream_options = { include_usage: true };
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
try {
|
|
160
160
|
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
161
161
|
method: 'POST',
|
|
@@ -163,43 +163,43 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
163
163
|
body: JSON.stringify(openRouterRequest),
|
|
164
164
|
signal: options?.signal,
|
|
165
165
|
});
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
if (!response.ok) {
|
|
168
168
|
const errorText = await response.text();
|
|
169
169
|
throw new Error(`OpenRouter error: ${response.status} ${errorText}`);
|
|
170
170
|
}
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
const reader = response.body?.getReader();
|
|
173
173
|
if (!reader) {
|
|
174
174
|
throw new Error('No response body');
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
const decoder = new TextDecoder();
|
|
178
178
|
let accumulated = '';
|
|
179
179
|
let finishReason = 'stop';
|
|
180
180
|
let toolCalls: OpenRouterToolCall[] = [];
|
|
181
181
|
let streamUsage: OpenRouterResponse['usage'] | undefined;
|
|
182
|
-
|
|
182
|
+
|
|
183
183
|
while (true) {
|
|
184
184
|
const { done, value } = await reader.read();
|
|
185
185
|
if (done) break;
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
const chunk = decoder.decode(value, { stream: true });
|
|
188
188
|
const lines = chunk.split('\n').filter(line => line.startsWith('data: '));
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
for (const line of lines) {
|
|
191
191
|
const data = line.slice(6);
|
|
192
192
|
if (data === '[DONE]') continue;
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
try {
|
|
195
195
|
const parsed = JSON.parse(data);
|
|
196
196
|
const delta = parsed.choices?.[0]?.delta;
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
if (delta?.content) {
|
|
199
199
|
accumulated += delta.content;
|
|
200
200
|
callbacks.onChunk(delta.content);
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
// Handle streaming tool calls
|
|
204
204
|
if (delta?.tool_calls) {
|
|
205
205
|
for (const tc of delta.tool_calls) {
|
|
@@ -218,11 +218,11 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
if (parsed.choices?.[0]?.finish_reason) {
|
|
223
223
|
finishReason = parsed.choices[0].finish_reason;
|
|
224
224
|
}
|
|
225
|
-
|
|
225
|
+
|
|
226
226
|
// Capture usage data (comes in final chunk when stream_options.include_usage is set)
|
|
227
227
|
if (parsed.usage) {
|
|
228
228
|
streamUsage = parsed.usage;
|
|
@@ -232,21 +232,21 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
// Build response with accumulated data
|
|
237
237
|
const message: OpenRouterMessage = {
|
|
238
238
|
role: 'assistant',
|
|
239
239
|
content: accumulated || null,
|
|
240
240
|
};
|
|
241
|
-
|
|
241
|
+
|
|
242
242
|
if (toolCalls.length > 0) {
|
|
243
243
|
message.tool_calls = toolCalls;
|
|
244
244
|
}
|
|
245
|
-
|
|
246
|
-
return this.parseStreamedResponse(message, finishReason, request.model, streamUsage);
|
|
247
|
-
|
|
245
|
+
|
|
246
|
+
return this.parseStreamedResponse(message, finishReason, request.model, streamUsage, openRouterRequest);
|
|
247
|
+
|
|
248
248
|
} catch (error) {
|
|
249
|
-
throw this.handleError(error);
|
|
249
|
+
throw this.handleError(error, openRouterRequest);
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -437,17 +437,17 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
437
437
|
return response.json() as Promise<OpenRouterResponse>;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
private parseResponse(response: OpenRouterResponse, requestedModel: string): ProviderResponse {
|
|
440
|
+
private parseResponse(response: OpenRouterResponse, requestedModel: string, rawRequest: unknown): ProviderResponse {
|
|
441
441
|
const choice = response.choices[0];
|
|
442
442
|
const message = choice?.message;
|
|
443
|
-
|
|
443
|
+
|
|
444
444
|
// Extract cache tokens - OpenRouter passes through both Anthropic and OpenAI caching
|
|
445
445
|
// Anthropic: cache_creation_input_tokens, cache_read_input_tokens
|
|
446
446
|
// OpenAI: prompt_tokens_details.cached_tokens
|
|
447
447
|
const cacheCreationTokens = response.usage?.cache_creation_input_tokens;
|
|
448
|
-
const cacheReadTokens = response.usage?.cache_read_input_tokens
|
|
448
|
+
const cacheReadTokens = response.usage?.cache_read_input_tokens
|
|
449
449
|
?? response.usage?.prompt_tokens_details?.cached_tokens;
|
|
450
|
-
|
|
450
|
+
|
|
451
451
|
return {
|
|
452
452
|
content: this.messageToContent(message),
|
|
453
453
|
stopReason: this.mapFinishReason(choice?.finish_reason),
|
|
@@ -459,6 +459,7 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
459
459
|
cacheReadTokens: cacheReadTokens ?? undefined,
|
|
460
460
|
},
|
|
461
461
|
model: response.model ?? requestedModel,
|
|
462
|
+
rawRequest,
|
|
462
463
|
raw: response,
|
|
463
464
|
};
|
|
464
465
|
}
|
|
@@ -467,13 +468,14 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
467
468
|
message: OpenRouterMessage,
|
|
468
469
|
finishReason: string,
|
|
469
470
|
requestedModel: string,
|
|
470
|
-
streamUsage?: OpenRouterResponse['usage']
|
|
471
|
+
streamUsage?: OpenRouterResponse['usage'],
|
|
472
|
+
rawRequest?: unknown
|
|
471
473
|
): ProviderResponse {
|
|
472
474
|
// Extract cache tokens if available from stream usage
|
|
473
475
|
const cacheCreationTokens = streamUsage?.cache_creation_input_tokens;
|
|
474
|
-
const cacheReadTokens = streamUsage?.cache_read_input_tokens
|
|
476
|
+
const cacheReadTokens = streamUsage?.cache_read_input_tokens
|
|
475
477
|
?? streamUsage?.prompt_tokens_details?.cached_tokens;
|
|
476
|
-
|
|
478
|
+
|
|
477
479
|
return {
|
|
478
480
|
content: this.messageToContent(message),
|
|
479
481
|
stopReason: this.mapFinishReason(finishReason),
|
|
@@ -485,6 +487,7 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
485
487
|
cacheReadTokens: cacheReadTokens ?? undefined,
|
|
486
488
|
},
|
|
487
489
|
model: requestedModel,
|
|
490
|
+
rawRequest,
|
|
488
491
|
raw: { message, finish_reason: finishReason, usage: streamUsage },
|
|
489
492
|
};
|
|
490
493
|
}
|
|
@@ -527,40 +530,41 @@ export class OpenRouterAdapter implements ProviderAdapter {
|
|
|
527
530
|
}
|
|
528
531
|
}
|
|
529
532
|
|
|
530
|
-
private handleError(error: unknown): MembraneError {
|
|
533
|
+
private handleError(error: unknown, rawRequest?: unknown): MembraneError {
|
|
531
534
|
if (error instanceof Error) {
|
|
532
535
|
const message = error.message;
|
|
533
|
-
|
|
536
|
+
|
|
534
537
|
if (message.includes('429') || message.includes('rate')) {
|
|
535
|
-
return rateLimitError(message, undefined, error);
|
|
538
|
+
return rateLimitError(message, undefined, error, rawRequest);
|
|
536
539
|
}
|
|
537
|
-
|
|
540
|
+
|
|
538
541
|
if (message.includes('401') || message.includes('auth')) {
|
|
539
|
-
return authError(message, error);
|
|
542
|
+
return authError(message, error, rawRequest);
|
|
540
543
|
}
|
|
541
|
-
|
|
544
|
+
|
|
542
545
|
if (message.includes('context') || message.includes('too long')) {
|
|
543
|
-
return contextLengthError(message, error);
|
|
546
|
+
return contextLengthError(message, error, rawRequest);
|
|
544
547
|
}
|
|
545
|
-
|
|
548
|
+
|
|
546
549
|
if (message.includes('500') || message.includes('502') || message.includes('503')) {
|
|
547
|
-
return serverError(message, undefined, error);
|
|
550
|
+
return serverError(message, undefined, error, rawRequest);
|
|
548
551
|
}
|
|
549
|
-
|
|
552
|
+
|
|
550
553
|
if (error.name === 'AbortError') {
|
|
551
|
-
return abortError();
|
|
554
|
+
return abortError(undefined, rawRequest);
|
|
552
555
|
}
|
|
553
|
-
|
|
556
|
+
|
|
554
557
|
if (message.includes('network') || message.includes('fetch')) {
|
|
555
|
-
return networkError(message, error);
|
|
558
|
+
return networkError(message, error, rawRequest);
|
|
556
559
|
}
|
|
557
560
|
}
|
|
558
|
-
|
|
561
|
+
|
|
559
562
|
return new MembraneError({
|
|
560
563
|
type: 'unknown',
|
|
561
564
|
message: error instanceof Error ? error.message : String(error),
|
|
562
565
|
retryable: false,
|
|
563
566
|
rawError: error,
|
|
567
|
+
rawRequest,
|
|
564
568
|
});
|
|
565
569
|
}
|
|
566
570
|
}
|
package/src/types/errors.ts
CHANGED
|
@@ -92,7 +92,7 @@ export class MembraneError extends Error {
|
|
|
92
92
|
// Error Factory Functions
|
|
93
93
|
// ============================================================================
|
|
94
94
|
|
|
95
|
-
export function rateLimitError(message: string, retryAfterMs?: number, raw?: unknown): MembraneError {
|
|
95
|
+
export function rateLimitError(message: string, retryAfterMs?: number, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
96
96
|
return new MembraneError({
|
|
97
97
|
type: 'rate_limit',
|
|
98
98
|
message,
|
|
@@ -100,91 +100,101 @@ export function rateLimitError(message: string, retryAfterMs?: number, raw?: unk
|
|
|
100
100
|
retryAfterMs,
|
|
101
101
|
httpStatus: 429,
|
|
102
102
|
rawError: raw,
|
|
103
|
+
rawRequest,
|
|
103
104
|
});
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
export function contextLengthError(message: string, raw?: unknown): MembraneError {
|
|
107
|
+
export function contextLengthError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
107
108
|
return new MembraneError({
|
|
108
109
|
type: 'context_length',
|
|
109
110
|
message,
|
|
110
111
|
retryable: false,
|
|
111
112
|
httpStatus: 400,
|
|
112
113
|
rawError: raw,
|
|
114
|
+
rawRequest,
|
|
113
115
|
});
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
export function invalidRequestError(message: string, raw?: unknown): MembraneError {
|
|
118
|
+
export function invalidRequestError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
117
119
|
return new MembraneError({
|
|
118
120
|
type: 'invalid_request',
|
|
119
121
|
message,
|
|
120
122
|
retryable: false,
|
|
121
123
|
httpStatus: 400,
|
|
122
124
|
rawError: raw,
|
|
125
|
+
rawRequest,
|
|
123
126
|
});
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
export function authError(message: string, raw?: unknown): MembraneError {
|
|
129
|
+
export function authError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
127
130
|
return new MembraneError({
|
|
128
131
|
type: 'auth',
|
|
129
132
|
message,
|
|
130
133
|
retryable: false,
|
|
131
134
|
httpStatus: 401,
|
|
132
135
|
rawError: raw,
|
|
136
|
+
rawRequest,
|
|
133
137
|
});
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
export function serverError(message: string, httpStatus?: number, raw?: unknown): MembraneError {
|
|
140
|
+
export function serverError(message: string, httpStatus?: number, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
137
141
|
return new MembraneError({
|
|
138
142
|
type: 'server',
|
|
139
143
|
message,
|
|
140
144
|
retryable: true,
|
|
141
145
|
httpStatus: httpStatus ?? 500,
|
|
142
146
|
rawError: raw,
|
|
147
|
+
rawRequest,
|
|
143
148
|
});
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
export function networkError(message: string, raw?: unknown): MembraneError {
|
|
151
|
+
export function networkError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
147
152
|
return new MembraneError({
|
|
148
153
|
type: 'network',
|
|
149
154
|
message,
|
|
150
155
|
retryable: true,
|
|
151
156
|
rawError: raw,
|
|
157
|
+
rawRequest,
|
|
152
158
|
});
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
export function timeoutError(message: string, raw?: unknown): MembraneError {
|
|
161
|
+
export function timeoutError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
156
162
|
return new MembraneError({
|
|
157
163
|
type: 'timeout',
|
|
158
164
|
message,
|
|
159
165
|
retryable: true,
|
|
160
166
|
rawError: raw,
|
|
167
|
+
rawRequest,
|
|
161
168
|
});
|
|
162
169
|
}
|
|
163
170
|
|
|
164
|
-
export function abortError(message: string = 'Request was aborted'): MembraneError {
|
|
171
|
+
export function abortError(message: string = 'Request was aborted', rawRequest?: unknown): MembraneError {
|
|
165
172
|
return new MembraneError({
|
|
166
173
|
type: 'abort',
|
|
167
174
|
message,
|
|
168
175
|
retryable: false,
|
|
169
176
|
rawError: undefined,
|
|
177
|
+
rawRequest,
|
|
170
178
|
});
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
export function safetyError(message: string, raw?: unknown): MembraneError {
|
|
181
|
+
export function safetyError(message: string, raw?: unknown, rawRequest?: unknown): MembraneError {
|
|
174
182
|
return new MembraneError({
|
|
175
183
|
type: 'safety',
|
|
176
184
|
message,
|
|
177
185
|
retryable: false,
|
|
178
186
|
rawError: raw,
|
|
187
|
+
rawRequest,
|
|
179
188
|
});
|
|
180
189
|
}
|
|
181
190
|
|
|
182
|
-
export function unsupportedError(message: string): MembraneError {
|
|
191
|
+
export function unsupportedError(message: string, rawRequest?: unknown): MembraneError {
|
|
183
192
|
return new MembraneError({
|
|
184
193
|
type: 'unsupported',
|
|
185
194
|
message,
|
|
186
195
|
retryable: false,
|
|
187
196
|
rawError: undefined,
|
|
197
|
+
rawRequest,
|
|
188
198
|
});
|
|
189
199
|
}
|
|
190
200
|
|
package/src/types/provider.ts
CHANGED
|
@@ -238,7 +238,10 @@ export interface ProviderResponse {
|
|
|
238
238
|
|
|
239
239
|
/** Model that actually ran */
|
|
240
240
|
model: string;
|
|
241
|
-
|
|
241
|
+
|
|
242
|
+
/** Raw request that was actually sent to the API */
|
|
243
|
+
rawRequest: unknown;
|
|
244
|
+
|
|
242
245
|
/** Raw response for debugging */
|
|
243
246
|
raw: unknown;
|
|
244
247
|
}
|