@animalabs/membrane 0.1.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.
Files changed (131) hide show
  1. package/dist/context/index.d.ts +10 -0
  2. package/dist/context/index.d.ts.map +1 -0
  3. package/dist/context/index.js +9 -0
  4. package/dist/context/index.js.map +1 -0
  5. package/dist/context/process.d.ts +22 -0
  6. package/dist/context/process.d.ts.map +1 -0
  7. package/dist/context/process.js +369 -0
  8. package/dist/context/process.js.map +1 -0
  9. package/dist/context/types.d.ts +118 -0
  10. package/dist/context/types.d.ts.map +1 -0
  11. package/dist/context/types.js +60 -0
  12. package/dist/context/types.js.map +1 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +18 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/membrane.d.ts +96 -0
  18. package/dist/membrane.d.ts.map +1 -0
  19. package/dist/membrane.js +893 -0
  20. package/dist/membrane.js.map +1 -0
  21. package/dist/providers/anthropic.d.ts +36 -0
  22. package/dist/providers/anthropic.d.ts.map +1 -0
  23. package/dist/providers/anthropic.js +265 -0
  24. package/dist/providers/anthropic.js.map +1 -0
  25. package/dist/providers/index.d.ts +8 -0
  26. package/dist/providers/index.d.ts.map +1 -0
  27. package/dist/providers/index.js +8 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/providers/openai-compatible.d.ts +74 -0
  30. package/dist/providers/openai-compatible.d.ts.map +1 -0
  31. package/dist/providers/openai-compatible.js +412 -0
  32. package/dist/providers/openai-compatible.js.map +1 -0
  33. package/dist/providers/openai.d.ts +69 -0
  34. package/dist/providers/openai.d.ts.map +1 -0
  35. package/dist/providers/openai.js +455 -0
  36. package/dist/providers/openai.js.map +1 -0
  37. package/dist/providers/openrouter.d.ts +76 -0
  38. package/dist/providers/openrouter.d.ts.map +1 -0
  39. package/dist/providers/openrouter.js +492 -0
  40. package/dist/providers/openrouter.js.map +1 -0
  41. package/dist/transforms/chat.d.ts +52 -0
  42. package/dist/transforms/chat.d.ts.map +1 -0
  43. package/dist/transforms/chat.js +136 -0
  44. package/dist/transforms/chat.js.map +1 -0
  45. package/dist/transforms/index.d.ts +6 -0
  46. package/dist/transforms/index.d.ts.map +1 -0
  47. package/dist/transforms/index.js +6 -0
  48. package/dist/transforms/index.js.map +1 -0
  49. package/dist/transforms/prefill.d.ts +89 -0
  50. package/dist/transforms/prefill.d.ts.map +1 -0
  51. package/dist/transforms/prefill.js +401 -0
  52. package/dist/transforms/prefill.js.map +1 -0
  53. package/dist/types/config.d.ts +103 -0
  54. package/dist/types/config.d.ts.map +1 -0
  55. package/dist/types/config.js +21 -0
  56. package/dist/types/config.js.map +1 -0
  57. package/dist/types/content.d.ts +81 -0
  58. package/dist/types/content.d.ts.map +1 -0
  59. package/dist/types/content.js +40 -0
  60. package/dist/types/content.js.map +1 -0
  61. package/dist/types/errors.d.ts +42 -0
  62. package/dist/types/errors.d.ts.map +1 -0
  63. package/dist/types/errors.js +208 -0
  64. package/dist/types/errors.js.map +1 -0
  65. package/dist/types/index.d.ts +18 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/index.js +9 -0
  68. package/dist/types/index.js.map +1 -0
  69. package/dist/types/message.d.ts +46 -0
  70. package/dist/types/message.d.ts.map +1 -0
  71. package/dist/types/message.js +38 -0
  72. package/dist/types/message.js.map +1 -0
  73. package/dist/types/provider.d.ts +155 -0
  74. package/dist/types/provider.d.ts.map +1 -0
  75. package/dist/types/provider.js +5 -0
  76. package/dist/types/provider.js.map +1 -0
  77. package/dist/types/request.d.ts +78 -0
  78. package/dist/types/request.d.ts.map +1 -0
  79. package/dist/types/request.js +5 -0
  80. package/dist/types/request.js.map +1 -0
  81. package/dist/types/response.d.ts +131 -0
  82. package/dist/types/response.d.ts.map +1 -0
  83. package/dist/types/response.js +7 -0
  84. package/dist/types/response.js.map +1 -0
  85. package/dist/types/streaming.d.ts +164 -0
  86. package/dist/types/streaming.d.ts.map +1 -0
  87. package/dist/types/streaming.js +5 -0
  88. package/dist/types/streaming.js.map +1 -0
  89. package/dist/types/tools.d.ts +71 -0
  90. package/dist/types/tools.d.ts.map +1 -0
  91. package/dist/types/tools.js +5 -0
  92. package/dist/types/tools.js.map +1 -0
  93. package/dist/utils/index.d.ts +5 -0
  94. package/dist/utils/index.d.ts.map +1 -0
  95. package/dist/utils/index.js +5 -0
  96. package/dist/utils/index.js.map +1 -0
  97. package/dist/utils/stream-parser.d.ts +53 -0
  98. package/dist/utils/stream-parser.d.ts.map +1 -0
  99. package/dist/utils/stream-parser.js +359 -0
  100. package/dist/utils/stream-parser.js.map +1 -0
  101. package/dist/utils/tool-parser.d.ts +130 -0
  102. package/dist/utils/tool-parser.d.ts.map +1 -0
  103. package/dist/utils/tool-parser.js +571 -0
  104. package/dist/utils/tool-parser.js.map +1 -0
  105. package/package.json +37 -0
  106. package/src/context/index.ts +24 -0
  107. package/src/context/process.ts +520 -0
  108. package/src/context/types.ts +231 -0
  109. package/src/index.ts +23 -0
  110. package/src/membrane.ts +1174 -0
  111. package/src/providers/anthropic.ts +340 -0
  112. package/src/providers/index.ts +31 -0
  113. package/src/providers/openai-compatible.ts +570 -0
  114. package/src/providers/openai.ts +625 -0
  115. package/src/providers/openrouter.ts +662 -0
  116. package/src/transforms/chat.ts +212 -0
  117. package/src/transforms/index.ts +22 -0
  118. package/src/transforms/prefill.ts +585 -0
  119. package/src/types/config.ts +172 -0
  120. package/src/types/content.ts +181 -0
  121. package/src/types/errors.ts +277 -0
  122. package/src/types/index.ts +154 -0
  123. package/src/types/message.ts +89 -0
  124. package/src/types/provider.ts +249 -0
  125. package/src/types/request.ts +131 -0
  126. package/src/types/response.ts +223 -0
  127. package/src/types/streaming.ts +231 -0
  128. package/src/types/tools.ts +92 -0
  129. package/src/utils/index.ts +15 -0
  130. package/src/utils/stream-parser.ts +440 -0
  131. package/src/utils/tool-parser.ts +715 -0
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Anthropic provider adapter
3
+ */
4
+
5
+ import Anthropic from '@anthropic-ai/sdk';
6
+ import type {
7
+ ProviderAdapter,
8
+ ProviderRequest,
9
+ ProviderRequestOptions,
10
+ ProviderResponse,
11
+ StreamCallbacks,
12
+ ContentBlock,
13
+ } from '../types/index.js';
14
+ import {
15
+ MembraneError,
16
+ rateLimitError,
17
+ contextLengthError,
18
+ authError,
19
+ serverError,
20
+ abortError,
21
+ } from '../types/index.js';
22
+
23
+ // ============================================================================
24
+ // Adapter Configuration
25
+ // ============================================================================
26
+
27
+ export interface AnthropicAdapterConfig {
28
+ /** API key (defaults to ANTHROPIC_API_KEY env var) */
29
+ apiKey?: string;
30
+
31
+ /** Base URL override */
32
+ baseURL?: string;
33
+
34
+ /** Default max tokens */
35
+ defaultMaxTokens?: number;
36
+ }
37
+
38
+ // ============================================================================
39
+ // Anthropic Adapter
40
+ // ============================================================================
41
+
42
+ export class AnthropicAdapter implements ProviderAdapter {
43
+ readonly name = 'anthropic';
44
+ private client: Anthropic;
45
+ private defaultMaxTokens: number;
46
+
47
+ constructor(config: AnthropicAdapterConfig = {}) {
48
+ this.client = new Anthropic({
49
+ apiKey: config.apiKey,
50
+ baseURL: config.baseURL,
51
+ });
52
+ this.defaultMaxTokens = config.defaultMaxTokens ?? 4096;
53
+ }
54
+
55
+ supportsModel(modelId: string): boolean {
56
+ return modelId.startsWith('claude-');
57
+ }
58
+
59
+ async complete(
60
+ request: ProviderRequest,
61
+ options?: ProviderRequestOptions
62
+ ): Promise<ProviderResponse> {
63
+ const anthropicRequest = this.buildRequest(request);
64
+
65
+ try {
66
+ const response = await this.client.messages.create({
67
+ ...anthropicRequest,
68
+ stream: false,
69
+ }, {
70
+ signal: options?.signal,
71
+ });
72
+
73
+ return this.parseResponse(response);
74
+ } catch (error) {
75
+ throw this.handleError(error);
76
+ }
77
+ }
78
+
79
+ async stream(
80
+ request: ProviderRequest,
81
+ callbacks: StreamCallbacks,
82
+ options?: ProviderRequestOptions
83
+ ): Promise<ProviderResponse> {
84
+ const anthropicRequest = this.buildRequest(request);
85
+
86
+ try {
87
+ const stream = await this.client.messages.stream(anthropicRequest, {
88
+ signal: options?.signal,
89
+ });
90
+
91
+ let accumulated = '';
92
+ const contentBlocks: unknown[] = [];
93
+ let currentBlockIndex = -1;
94
+
95
+ for await (const event of stream) {
96
+ if (event.type === 'content_block_start') {
97
+ currentBlockIndex = event.index;
98
+ contentBlocks[currentBlockIndex] = event.content_block;
99
+ callbacks.onContentBlock?.(currentBlockIndex, event.content_block);
100
+ } else if (event.type === 'content_block_delta') {
101
+ if (event.delta.type === 'text_delta') {
102
+ const chunk = event.delta.text;
103
+ accumulated += chunk;
104
+ callbacks.onChunk(chunk);
105
+ } else if (event.delta.type === 'thinking_delta') {
106
+ // Handle thinking delta
107
+ callbacks.onChunk(event.delta.thinking);
108
+ }
109
+ } else if (event.type === 'content_block_stop') {
110
+ callbacks.onContentBlock?.(currentBlockIndex, contentBlocks[currentBlockIndex]);
111
+ }
112
+ }
113
+
114
+ const finalMessage = await stream.finalMessage();
115
+ return this.parseResponse(finalMessage);
116
+
117
+ } catch (error) {
118
+ throw this.handleError(error);
119
+ }
120
+ }
121
+
122
+ private buildRequest(request: ProviderRequest): Anthropic.MessageCreateParams {
123
+ const params: Anthropic.MessageCreateParams = {
124
+ model: request.model,
125
+ max_tokens: request.maxTokens || this.defaultMaxTokens,
126
+ messages: request.messages as Anthropic.MessageParam[],
127
+ };
128
+
129
+ // Handle system prompt - can be string or content blocks with cache_control
130
+ if (request.system) {
131
+ if (typeof request.system === 'string') {
132
+ params.system = request.system;
133
+ } else if (Array.isArray(request.system)) {
134
+ // System is an array of content blocks (with potential cache_control)
135
+ params.system = request.system as Anthropic.TextBlockParam[];
136
+ }
137
+ }
138
+
139
+ if (request.temperature !== undefined) {
140
+ params.temperature = request.temperature;
141
+ }
142
+
143
+ if (request.stopSequences && request.stopSequences.length > 0) {
144
+ params.stop_sequences = request.stopSequences;
145
+ }
146
+
147
+ if (request.tools && request.tools.length > 0) {
148
+ params.tools = request.tools as Anthropic.Tool[];
149
+ }
150
+
151
+ // Apply extra params
152
+ if (request.extra) {
153
+ Object.assign(params, request.extra);
154
+ }
155
+
156
+ return params;
157
+ }
158
+
159
+ private parseResponse(response: Anthropic.Message): ProviderResponse {
160
+ return {
161
+ content: response.content,
162
+ stopReason: response.stop_reason ?? 'end_turn',
163
+ stopSequence: response.stop_sequence ?? undefined,
164
+ usage: {
165
+ inputTokens: response.usage.input_tokens,
166
+ outputTokens: response.usage.output_tokens,
167
+ cacheCreationTokens: (response.usage as any).cache_creation_input_tokens,
168
+ cacheReadTokens: (response.usage as any).cache_read_input_tokens,
169
+ },
170
+ model: response.model,
171
+ raw: response,
172
+ };
173
+ }
174
+
175
+ private handleError(error: unknown): MembraneError {
176
+ if (error instanceof Anthropic.APIError) {
177
+ const status = error.status;
178
+ const message = error.message;
179
+
180
+ if (status === 429) {
181
+ // Try to parse retry-after
182
+ const retryAfter = this.parseRetryAfter(error);
183
+ return rateLimitError(message, retryAfter, error);
184
+ }
185
+
186
+ if (status === 401) {
187
+ return authError(message, error);
188
+ }
189
+
190
+ if (message.includes('context') || message.includes('too long')) {
191
+ return contextLengthError(message, error);
192
+ }
193
+
194
+ if (status >= 500) {
195
+ return serverError(message, status, error);
196
+ }
197
+ }
198
+
199
+ if (error instanceof Error && error.name === 'AbortError') {
200
+ return abortError();
201
+ }
202
+
203
+ return new MembraneError({
204
+ type: 'unknown',
205
+ message: error instanceof Error ? error.message : String(error),
206
+ retryable: false,
207
+ rawError: error,
208
+ });
209
+ }
210
+
211
+ private parseRetryAfter(error: { message: string }): number | undefined {
212
+ // Try to extract retry-after from headers or message
213
+ const message = error.message;
214
+ const match = message.match(/retry after (\d+)/i);
215
+ if (match && match[1]) {
216
+ return parseInt(match[1], 10) * 1000;
217
+ }
218
+ return undefined;
219
+ }
220
+ }
221
+
222
+ // ============================================================================
223
+ // Content Conversion Utilities
224
+ // ============================================================================
225
+
226
+ /**
227
+ * Convert normalized content blocks to Anthropic format
228
+ * Preserves cache_control for prompt caching
229
+ */
230
+ export function toAnthropicContent(blocks: ContentBlock[]): Anthropic.ContentBlockParam[] {
231
+ const result: Anthropic.ContentBlockParam[] = [];
232
+
233
+ for (const block of blocks) {
234
+ switch (block.type) {
235
+ case 'text': {
236
+ const textBlock: any = { type: 'text', text: block.text };
237
+ // Preserve cache_control if present
238
+ if (block.cache_control) {
239
+ textBlock.cache_control = block.cache_control;
240
+ }
241
+ result.push(textBlock);
242
+ break;
243
+ }
244
+
245
+ case 'image':
246
+ if (block.source.type === 'base64') {
247
+ result.push({
248
+ type: 'image',
249
+ source: {
250
+ type: 'base64',
251
+ media_type: block.source.mediaType as 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp',
252
+ data: block.source.data,
253
+ },
254
+ });
255
+ }
256
+ break;
257
+
258
+ case 'document':
259
+ result.push({
260
+ type: 'document',
261
+ source: {
262
+ type: 'base64',
263
+ media_type: block.source.mediaType as 'application/pdf',
264
+ data: block.source.data,
265
+ },
266
+ });
267
+ break;
268
+
269
+ case 'tool_use':
270
+ result.push({
271
+ type: 'tool_use',
272
+ id: block.id,
273
+ name: block.name,
274
+ input: block.input,
275
+ });
276
+ break;
277
+
278
+ case 'tool_result':
279
+ result.push({
280
+ type: 'tool_result',
281
+ tool_use_id: block.toolUseId,
282
+ content: typeof block.content === 'string'
283
+ ? block.content
284
+ : JSON.stringify(block.content),
285
+ is_error: block.isError,
286
+ });
287
+ break;
288
+
289
+ case 'thinking':
290
+ result.push({
291
+ type: 'thinking',
292
+ thinking: block.thinking,
293
+ } as any);
294
+ break;
295
+ }
296
+ }
297
+
298
+ return result;
299
+ }
300
+
301
+ /**
302
+ * Convert Anthropic response content to normalized format
303
+ */
304
+ export function fromAnthropicContent(blocks: Anthropic.ContentBlock[]): ContentBlock[] {
305
+ const result: ContentBlock[] = [];
306
+
307
+ for (const block of blocks) {
308
+ switch (block.type) {
309
+ case 'text':
310
+ result.push({ type: 'text', text: block.text });
311
+ break;
312
+
313
+ case 'tool_use':
314
+ result.push({
315
+ type: 'tool_use',
316
+ id: block.id,
317
+ name: block.name,
318
+ input: block.input as Record<string, unknown>,
319
+ });
320
+ break;
321
+
322
+ case 'thinking':
323
+ result.push({
324
+ type: 'thinking',
325
+ thinking: (block as any).thinking,
326
+ signature: (block as any).signature,
327
+ });
328
+ break;
329
+
330
+ default:
331
+ // Handle redacted_thinking or unknown types
332
+ if ((block as any).type === 'redacted_thinking') {
333
+ result.push({ type: 'redacted_thinking' });
334
+ }
335
+ break;
336
+ }
337
+ }
338
+
339
+ return result;
340
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Provider exports
3
+ */
4
+
5
+ export {
6
+ AnthropicAdapter,
7
+ toAnthropicContent,
8
+ fromAnthropicContent,
9
+ type AnthropicAdapterConfig,
10
+ } from './anthropic.js';
11
+
12
+ export {
13
+ OpenRouterAdapter,
14
+ toOpenRouterMessages,
15
+ fromOpenRouterMessage,
16
+ type OpenRouterAdapterConfig,
17
+ } from './openrouter.js';
18
+
19
+ export {
20
+ OpenAIAdapter,
21
+ toOpenAIContent,
22
+ fromOpenAIContent,
23
+ type OpenAIAdapterConfig,
24
+ } from './openai.js';
25
+
26
+ export {
27
+ OpenAICompatibleAdapter,
28
+ toOpenAIMessages,
29
+ fromOpenAIMessage,
30
+ type OpenAICompatibleAdapterConfig,
31
+ } from './openai-compatible.js';