@layer-ai/core 0.1.12 → 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.
Files changed (34) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/routes/complete.d.ts.map +1 -1
  5. package/dist/routes/complete.js +24 -4
  6. package/dist/routes/v2/complete.d.ts +4 -0
  7. package/dist/routes/v2/complete.d.ts.map +1 -0
  8. package/dist/routes/v2/complete.js +214 -0
  9. package/dist/routes/v2/tests/test-complete-anthropic.d.ts +2 -0
  10. package/dist/routes/v2/tests/test-complete-anthropic.d.ts.map +1 -0
  11. package/dist/routes/v2/tests/test-complete-anthropic.js +132 -0
  12. package/dist/routes/v2/tests/test-complete-openai.d.ts +2 -0
  13. package/dist/routes/v2/tests/test-complete-openai.d.ts.map +1 -0
  14. package/dist/routes/v2/tests/test-complete-openai.js +178 -0
  15. package/dist/routes/v2/tests/test-complete-routing.d.ts +2 -0
  16. package/dist/routes/v2/tests/test-complete-routing.d.ts.map +1 -0
  17. package/dist/routes/v2/tests/test-complete-routing.js +192 -0
  18. package/dist/services/providers/anthropic-adapter.d.ts +12 -0
  19. package/dist/services/providers/anthropic-adapter.d.ts.map +1 -0
  20. package/dist/services/providers/anthropic-adapter.js +203 -0
  21. package/dist/services/providers/base-adapter.d.ts +1 -1
  22. package/dist/services/providers/base-adapter.d.ts.map +1 -1
  23. package/dist/services/providers/base-adapter.js +1 -1
  24. package/dist/services/providers/openai-adapter.d.ts +2 -2
  25. package/dist/services/providers/openai-adapter.d.ts.map +1 -1
  26. package/dist/services/providers/openai-adapter.js +15 -3
  27. package/dist/services/providers/tests/test-anthropic-adapter.d.ts +2 -0
  28. package/dist/services/providers/tests/test-anthropic-adapter.d.ts.map +1 -0
  29. package/dist/services/providers/tests/test-anthropic-adapter.js +104 -0
  30. package/dist/services/providers/tests/test-openai-adapter.d.ts +2 -0
  31. package/dist/services/providers/tests/test-openai-adapter.d.ts.map +1 -0
  32. package/dist/services/providers/tests/test-openai-adapter.js +118 -0
  33. package/package.json +9 -9
  34. package/LICENSE +0 -21
@@ -0,0 +1,192 @@
1
+ // Test v2 complete route with routing strategies
2
+ // Demonstrates fallback and round-robin across different providers
3
+ async function testFallbackRouting() {
4
+ console.log('Test 1: Fallback routing (Anthropic -> OpenAI)\n');
5
+ const request = {
6
+ gate: 'test-gate-with-fallback',
7
+ model: 'claude-sonnet-4-5-20250929', // Primary model
8
+ type: 'chat',
9
+ data: {
10
+ messages: [
11
+ { role: 'user', content: 'Hello! Please respond.' }
12
+ ],
13
+ maxTokens: 50,
14
+ }
15
+ };
16
+ console.log('Gate configuration should have:');
17
+ console.log('- model: claude-sonnet-4-5-20250929');
18
+ console.log('- routingStrategy: "fallback"');
19
+ console.log('- fallbackModels: ["gpt-4o-mini", "gpt-3.5-turbo"]');
20
+ console.log('\nBehavior:');
21
+ console.log('1. Try primary model (Claude Sonnet)');
22
+ console.log('2. If fails, try first fallback (GPT-4o-mini)');
23
+ console.log('3. If fails, try second fallback (GPT-3.5-turbo)');
24
+ console.log('4. Return first successful response');
25
+ console.log('\nResponse will include "model" field showing which model was used');
26
+ }
27
+ async function testRoundRobinRouting() {
28
+ console.log('\n\nTest 2: Round-robin routing across providers\n');
29
+ const request = {
30
+ gate: 'test-gate-with-round-robin',
31
+ model: 'claude-sonnet-4-5-20250929',
32
+ type: 'chat',
33
+ data: {
34
+ messages: [
35
+ { role: 'user', content: 'Tell me a fun fact.' }
36
+ ],
37
+ maxTokens: 100,
38
+ }
39
+ };
40
+ console.log('Gate configuration should have:');
41
+ console.log('- model: claude-sonnet-4-5-20250929');
42
+ console.log('- routingStrategy: "round-robin"');
43
+ console.log('- fallbackModels: ["gpt-4o-mini", "gpt-4o"]');
44
+ console.log('\nBehavior:');
45
+ console.log('- Randomly selects one of: [Claude Sonnet, GPT-4o-mini, GPT-4o]');
46
+ console.log('- Distributes load across multiple models/providers');
47
+ console.log('- Useful for cost optimization and rate limit management');
48
+ console.log('\nResponse "model" field shows which model was randomly selected');
49
+ }
50
+ async function testCrossProviderFallback() {
51
+ console.log('\n\nTest 3: Cross-provider fallback with vision\n');
52
+ const request = {
53
+ gate: 'test-gate-vision-fallback',
54
+ model: 'claude-sonnet-4-5-20250929',
55
+ type: 'chat',
56
+ data: {
57
+ messages: [
58
+ {
59
+ role: 'user',
60
+ content: 'What do you see in this image?',
61
+ images: [{
62
+ url: 'https://images.unsplash.com/photo-1765202659641-9ad9facfe5cf?q=80&w=1364&auto=format&fit=crop'
63
+ }]
64
+ }
65
+ ],
66
+ maxTokens: 100,
67
+ }
68
+ };
69
+ console.log('Gate configuration:');
70
+ console.log('- model: claude-sonnet-4-5-20250929 (Anthropic vision)');
71
+ console.log('- routingStrategy: "fallback"');
72
+ console.log('- fallbackModels: ["gpt-4o", "gpt-4o-mini"]');
73
+ console.log('\nUse case:');
74
+ console.log('- Try Anthropic vision first');
75
+ console.log('- If rate limited or error, fallback to OpenAI vision');
76
+ console.log('- Ensures high availability for vision workloads');
77
+ }
78
+ async function testToolCallsFallback() {
79
+ console.log('\n\nTest 4: Fallback routing with tool calls\n');
80
+ const request = {
81
+ gate: 'test-gate-tools-fallback',
82
+ model: 'gpt-4o-mini',
83
+ type: 'chat',
84
+ data: {
85
+ messages: [
86
+ { role: 'user', content: 'What is the weather in Tokyo?' }
87
+ ],
88
+ tools: [
89
+ {
90
+ type: 'function',
91
+ function: {
92
+ name: 'get_weather',
93
+ description: 'Get weather for a location',
94
+ parameters: {
95
+ type: 'object',
96
+ properties: {
97
+ location: { type: 'string', description: 'City name' },
98
+ },
99
+ required: ['location'],
100
+ },
101
+ },
102
+ },
103
+ ],
104
+ toolChoice: 'auto',
105
+ maxTokens: 100,
106
+ }
107
+ };
108
+ console.log('Gate configuration:');
109
+ console.log('- model: gpt-4o-mini');
110
+ console.log('- routingStrategy: "fallback"');
111
+ console.log('- fallbackModels: ["gpt-4o", "claude-sonnet-4-5-20250929"]');
112
+ console.log('\nBehavior:');
113
+ console.log('- All three models support function calling');
114
+ console.log('- Fallback works seamlessly across providers');
115
+ console.log('- Response includes toolCalls array from whichever model succeeded');
116
+ }
117
+ async function testCostOptimization() {
118
+ console.log('\n\nTest 5: Cost optimization with round-robin\n');
119
+ const request = {
120
+ gate: 'test-gate-cost-optimization',
121
+ model: 'gpt-4o-mini',
122
+ type: 'chat',
123
+ data: {
124
+ messages: [
125
+ { role: 'user', content: 'Write a short poem about the ocean.' }
126
+ ],
127
+ maxTokens: 150,
128
+ }
129
+ };
130
+ console.log('Gate configuration:');
131
+ console.log('- model: gpt-4o-mini ($0.15/$0.60 per 1M tokens)');
132
+ console.log('- routingStrategy: "round-robin"');
133
+ console.log('- fallbackModels: [');
134
+ console.log(' "gpt-3.5-turbo" ($0.50/$1.50 per 1M)');
135
+ console.log(' "claude-haiku-3-5-20241022" ($0.80/$4.00 per 1M)');
136
+ console.log(' ]');
137
+ console.log('\nUse case:');
138
+ console.log('- Distribute load across cheapest available models');
139
+ console.log('- Balance cost vs quality for non-critical workloads');
140
+ console.log('- Response includes "cost" field for tracking');
141
+ }
142
+ async function testProviderSpecificFeatures() {
143
+ console.log('\n\nTest 6: Mixing provider-specific features\n');
144
+ console.log('Scenario A: Start with Claude (better reasoning)');
145
+ console.log('Gate: model=claude-sonnet, fallback=[gpt-4o]');
146
+ console.log('Use for: Complex reasoning, analysis, code generation');
147
+ console.log('\nScenario B: Start with GPT-4o (multimodal)');
148
+ console.log('Gate: model=gpt-4o, fallback=[claude-sonnet]');
149
+ console.log('Use for: Image analysis, audio processing');
150
+ console.log('\nScenario C: Start with cheapest (GPT-3.5)');
151
+ console.log('Gate: model=gpt-3.5-turbo, fallback=[gpt-4o-mini, claude-haiku]');
152
+ console.log('Use for: Simple tasks, high volume workloads');
153
+ console.log('\nThe v2 API makes it easy to switch strategies per gate');
154
+ }
155
+ async function runTests() {
156
+ console.log('='.repeat(70));
157
+ console.log('V2 Complete Route - Routing Strategies with Mixed Models');
158
+ console.log('='.repeat(70));
159
+ console.log('\nRouting enables:');
160
+ console.log('- High availability (fallback to working models)');
161
+ console.log('- Cost optimization (round-robin across cheap models)');
162
+ console.log('- Provider redundancy (mix OpenAI + Anthropic)');
163
+ console.log('- Rate limit handling (switch when rate limited)');
164
+ console.log('='.repeat(70));
165
+ await testFallbackRouting();
166
+ await testRoundRobinRouting();
167
+ await testCrossProviderFallback();
168
+ await testToolCallsFallback();
169
+ await testCostOptimization();
170
+ await testProviderSpecificFeatures();
171
+ console.log('\n' + '='.repeat(70));
172
+ console.log('How to set up routing:');
173
+ console.log('\n1. Create a gate with routing configuration:');
174
+ console.log(' POST /v1/gates');
175
+ console.log(' {');
176
+ console.log(' "name": "my-gate",');
177
+ console.log(' "model": "claude-sonnet-4-5-20250929",');
178
+ console.log(' "routingStrategy": "fallback",');
179
+ console.log(' "fallbackModels": ["gpt-4o-mini", "gpt-3.5-turbo"]');
180
+ console.log(' }');
181
+ console.log('\n2. Use the gate in v2/complete:');
182
+ console.log(' POST /v2/complete');
183
+ console.log(' {');
184
+ console.log(' "gate": "my-gate",');
185
+ console.log(' "type": "chat",');
186
+ console.log(' "data": { "messages": [...] }');
187
+ console.log(' }');
188
+ console.log('\n3. Check response.model to see which model was used');
189
+ console.log('='.repeat(70));
190
+ }
191
+ runTests();
192
+ export {};
@@ -0,0 +1,12 @@
1
+ import { BaseProviderAdapter } from './base-adapter.js';
2
+ import { LayerRequest, LayerResponse, Role, FinishReason, ToolChoice } from '@layer-ai/sdk';
3
+ export declare class AnthropicAdapter extends BaseProviderAdapter {
4
+ protected provider: string;
5
+ protected roleMappings: Record<Role, string>;
6
+ protected toolChoiceMappings: Record<string, string | object>;
7
+ protected finishReasonMappings: Record<string, FinishReason>;
8
+ protected mapToolChoice(choice: ToolChoice): string | object | undefined;
9
+ call(request: LayerRequest): Promise<LayerResponse>;
10
+ private handleChat;
11
+ }
12
+ //# sourceMappingURL=anthropic-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/anthropic-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAmB,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAC;AAavB,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,SAAS,CAAC,QAAQ,SAAe;IAEjC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAI3D;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM1D;IAEF,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAalE,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;YAiB3C,UAAU;CA2JzB"}
@@ -0,0 +1,203 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { BaseProviderAdapter, ADAPTER_HANDLED } from './base-adapter.js';
3
+ let anthropic = null;
4
+ function getAnthropicClient() {
5
+ if (!anthropic) {
6
+ anthropic = new Anthropic({
7
+ apiKey: process.env.ANTHROPIC_API_KEY,
8
+ });
9
+ }
10
+ return anthropic;
11
+ }
12
+ export class AnthropicAdapter extends BaseProviderAdapter {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.provider = 'anthropic';
16
+ this.roleMappings = {
17
+ system: ADAPTER_HANDLED, // Handled via system parameter
18
+ user: 'user',
19
+ assistant: 'assistant',
20
+ tool: 'user', // Tool results are user messages in Anthropic
21
+ function: 'user', // Function results are user messages in Anthropic
22
+ model: 'assistant',
23
+ developer: 'user',
24
+ };
25
+ this.toolChoiceMappings = {
26
+ auto: { type: 'auto' },
27
+ required: { type: 'any' },
28
+ none: { type: 'none' },
29
+ };
30
+ this.finishReasonMappings = {
31
+ end_turn: 'completed',
32
+ max_tokens: 'length_limit',
33
+ tool_use: 'tool_call',
34
+ stop_sequence: 'completed',
35
+ error_output: 'error',
36
+ };
37
+ }
38
+ mapToolChoice(choice) {
39
+ // Handle object format: { type: 'function', function: { name: 'foo' } } -> { type: 'tool', name: 'foo' }
40
+ if (typeof choice === 'object' && choice.type === 'function') {
41
+ return {
42
+ type: 'tool',
43
+ name: choice.function.name,
44
+ };
45
+ }
46
+ // Handle string format using base mappings
47
+ return super.mapToolChoice(choice);
48
+ }
49
+ async call(request) {
50
+ switch (request.type) {
51
+ case 'chat':
52
+ return this.handleChat(request);
53
+ case 'image':
54
+ throw new Error('image generation not yet supported by LayerAI');
55
+ case 'embeddings':
56
+ throw new Error('embeddings not yet supported by LayerAI');
57
+ case 'tts':
58
+ throw new Error('tts generation not yet supported by LayerAI');
59
+ case 'video':
60
+ throw new Error('Video generation not yet supported by LayerAI');
61
+ default:
62
+ throw new Error(`Unknown modality: ${request.type}`);
63
+ }
64
+ }
65
+ async handleChat(request) {
66
+ const startTime = Date.now();
67
+ const client = getAnthropicClient();
68
+ const { data: chat, model } = request;
69
+ if (!model) {
70
+ throw new Error('Model is required for chat completions');
71
+ }
72
+ const systemPrompt = chat.systemPrompt || undefined;
73
+ const messages = [];
74
+ for (const msg of chat.messages) {
75
+ // Skip system messages - they're handled via the system parameter
76
+ if (msg.role === 'system')
77
+ continue;
78
+ const role = this.mapRole(msg.role);
79
+ if (msg.images && msg.images.length > 0) {
80
+ const content = [];
81
+ if (msg.content) {
82
+ content.push({ type: 'text', text: msg.content });
83
+ }
84
+ for (const image of msg.images) {
85
+ if (image.url) {
86
+ content.push({
87
+ type: 'image',
88
+ source: {
89
+ type: 'url',
90
+ url: image.url,
91
+ }
92
+ });
93
+ }
94
+ else if (image.base64) {
95
+ content.push({
96
+ type: 'image',
97
+ source: {
98
+ type: 'base64',
99
+ media_type: image.mimeType || 'image/jpeg',
100
+ data: image.base64
101
+ }
102
+ });
103
+ }
104
+ }
105
+ messages.push({ role: role, content });
106
+ }
107
+ else if (msg.toolCalls) {
108
+ const content = [];
109
+ if (msg.content) {
110
+ content.push({ type: 'text', text: msg.content });
111
+ }
112
+ for (const toolCall of msg.toolCalls) {
113
+ content.push({
114
+ type: 'tool_use',
115
+ id: toolCall.id,
116
+ name: toolCall.function.name,
117
+ input: JSON.parse(toolCall.function.arguments),
118
+ });
119
+ }
120
+ messages.push({ role: 'assistant', content });
121
+ }
122
+ else if (msg.toolCallId) {
123
+ messages.push({
124
+ role: 'user',
125
+ content: [{
126
+ type: 'tool_result',
127
+ tool_use_id: msg.toolCallId,
128
+ content: msg.content || '',
129
+ }],
130
+ });
131
+ }
132
+ else {
133
+ messages.push({
134
+ role: role,
135
+ content: msg.content || '',
136
+ });
137
+ }
138
+ }
139
+ const anthropicRequest = {
140
+ model: model,
141
+ messages,
142
+ max_tokens: chat.maxTokens || 4096,
143
+ ...(systemPrompt && { system: systemPrompt }),
144
+ ...(chat.temperature !== undefined && { temperature: chat.temperature }),
145
+ ...(chat.temperature === undefined && chat.topP !== undefined && { top_p: chat.topP }),
146
+ ...(chat.stopSequences && { stop_sequences: chat.stopSequences }),
147
+ ...(chat.tools && {
148
+ tools: chat.tools.map(tool => ({
149
+ name: tool.function.name,
150
+ description: tool.function.description,
151
+ input_schema: tool.function.parameters || { type: 'object', properties: {} },
152
+ })),
153
+ ...(chat.toolChoice && { tool_choice: this.mapToolChoice(chat.toolChoice) }),
154
+ }),
155
+ };
156
+ const response = await client.messages.create(anthropicRequest);
157
+ // Extract text content
158
+ let textContent;
159
+ const textBlock = response.content.find(block => block.type === 'text');
160
+ if (textBlock && textBlock.type === 'text') {
161
+ textContent = textBlock.text;
162
+ }
163
+ // Extract tool calls
164
+ let toolCalls;
165
+ const toolUseBlocks = response.content.filter(block => block.type === 'tool_use');
166
+ if (toolUseBlocks.length > 0) {
167
+ toolCalls = toolUseBlocks
168
+ .map(block => {
169
+ if (block.type === 'tool_use') {
170
+ return {
171
+ id: block.id,
172
+ type: 'function',
173
+ function: {
174
+ name: block.name,
175
+ arguments: JSON.stringify(block.input),
176
+ },
177
+ };
178
+ }
179
+ return undefined;
180
+ })
181
+ .filter((call) => call !== undefined);
182
+ }
183
+ const promptTokens = response.usage.input_tokens;
184
+ const completionTokens = response.usage.output_tokens;
185
+ const totalTokens = promptTokens + completionTokens;
186
+ const cost = this.calculateCost(model, promptTokens, completionTokens);
187
+ return {
188
+ content: textContent,
189
+ toolCalls,
190
+ model: response.model,
191
+ finishReason: this.mapFinishReason(response.stop_reason || 'end_turn'),
192
+ rawFinishReason: response.stop_reason || undefined,
193
+ usage: {
194
+ promptTokens,
195
+ completionTokens,
196
+ totalTokens,
197
+ },
198
+ cost,
199
+ latencyMs: Date.now() - startTime,
200
+ raw: response,
201
+ };
202
+ }
203
+ }
@@ -1,6 +1,6 @@
1
1
  import { LayerRequest, LayerResponse, Role, ImageDetail, ImageSize, ImageQuality, ImageStyle, VideoSize, AudioFormat, AudioMimeType, ImageMimeType, FinishReason, ToolChoice, EncodingFormat, ADAPTER_HANDLED } from '@layer-ai/sdk';
2
2
  export { ADAPTER_HANDLED };
3
- export declare abstract class ProviderAdapter {
3
+ export declare abstract class BaseProviderAdapter {
4
4
  protected abstract provider: string;
5
5
  protected roleMappings?: Record<Role, string>;
6
6
  protected imageDetailMappings?: Record<ImageDetail, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,8BAAsB,eAAe;IACnC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAEpC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC/D,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAElE,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAE5D,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAcrC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAQpE,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS;IAQ9D,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,YAAY;IAQrE,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAYxE,SAAS,CAAC,aAAa,CACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACvB,MAAM;CAMV"}
1
+ {"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,8BAAsB,mBAAmB;IACvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAEpC,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC/D,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAElE,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAE5D,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAcrC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAQpE,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS;IAQ9D,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,YAAY;IAQrE,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAYxE,SAAS,CAAC,aAAa,CACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACvB,MAAM;CAMV"}
@@ -1,6 +1,6 @@
1
1
  import { ADAPTER_HANDLED, MODEL_REGISTRY, } from '@layer-ai/sdk';
2
2
  export { ADAPTER_HANDLED };
3
- export class ProviderAdapter {
3
+ export class BaseProviderAdapter {
4
4
  mapRole(role) {
5
5
  if (!this.roleMappings) {
6
6
  return role;
@@ -1,6 +1,6 @@
1
- import { ProviderAdapter } from './base-adapter.js';
1
+ import { BaseProviderAdapter } from './base-adapter.js';
2
2
  import { LayerRequest, LayerResponse, Role, ImageDetail, ImageSize, ImageQuality, ImageStyle, VideoSize, AudioFormat, FinishReason } from '@layer-ai/sdk';
3
- export declare class OpenAIAdapter extends ProviderAdapter {
3
+ export declare class OpenAIAdapter extends BaseProviderAdapter {
4
4
  protected provider: string;
5
5
  protected roleMappings: Record<Role, string>;
6
6
  protected imageDetailMappings: Record<ImageDetail, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AAavB,qBAAa,aAAc,SAAQ,eAAe;IAChD,SAAS,CAAC,QAAQ,SAAY;IAE9B,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAIxD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK1D;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAQpD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtD;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAKpD;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAOxD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;YAiB3C,UAAU;YAgGV,qBAAqB;YAyBrB,gBAAgB;YA6BhB,kBAAkB;CA0BjC"}
1
+ {"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AAavB,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,SAAS,CAAC,QAAQ,SAAY;IAE9B,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAIxD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK1D;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAQpD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtD;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAKpD;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAOxD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;YAiB3C,UAAU;YAoGV,qBAAqB;YA6BrB,gBAAgB;YAiChB,kBAAkB;CA8BjC"}
@@ -1,5 +1,5 @@
1
1
  import OpenAI from 'openai';
2
- import { ProviderAdapter } from './base-adapter.js';
2
+ import { BaseProviderAdapter } from './base-adapter.js';
3
3
  let openai = null;
4
4
  function getOpenAIClient() {
5
5
  if (!openai) {
@@ -9,7 +9,7 @@ function getOpenAIClient() {
9
9
  }
10
10
  return openai;
11
11
  }
12
- export class OpenAIAdapter extends ProviderAdapter {
12
+ export class OpenAIAdapter extends BaseProviderAdapter {
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.provider = 'openai';
@@ -76,7 +76,7 @@ export class OpenAIAdapter extends ProviderAdapter {
76
76
  case 'tts':
77
77
  return this.handleTextToSpeech(request);
78
78
  case 'video':
79
- throw new Error('Video generation not yet supported by OpenAI');
79
+ throw new Error('Video generation not yet supported by LayerAI');
80
80
  default:
81
81
  throw new Error(`Unknown modality: ${request.type}`);
82
82
  }
@@ -85,6 +85,9 @@ export class OpenAIAdapter extends ProviderAdapter {
85
85
  const startTime = Date.now();
86
86
  const client = getOpenAIClient();
87
87
  const { data: chat, model } = request;
88
+ if (!model) {
89
+ throw new Error('Model is required for chat completion');
90
+ }
88
91
  const messages = [];
89
92
  if (chat.systemPrompt) {
90
93
  messages.push({ role: 'system', content: chat.systemPrompt });
@@ -172,6 +175,9 @@ export class OpenAIAdapter extends ProviderAdapter {
172
175
  const startTime = Date.now();
173
176
  const client = getOpenAIClient();
174
177
  const { data: image, model } = request;
178
+ if (!model) {
179
+ throw new Error('Model is required for image generation');
180
+ }
175
181
  const response = await client.images.generate({
176
182
  model: model,
177
183
  prompt: image.prompt,
@@ -194,6 +200,9 @@ export class OpenAIAdapter extends ProviderAdapter {
194
200
  const startTime = Date.now();
195
201
  const client = getOpenAIClient();
196
202
  const { data: embedding, model } = request;
203
+ if (!model) {
204
+ throw new Error('Model is required for embeddings');
205
+ }
197
206
  const response = await client.embeddings.create({
198
207
  model: model,
199
208
  input: embedding.input,
@@ -219,6 +228,9 @@ export class OpenAIAdapter extends ProviderAdapter {
219
228
  const startTime = Date.now();
220
229
  const client = getOpenAIClient();
221
230
  const { data: tts, model } = request;
231
+ if (!model) {
232
+ throw new Error('Model is required for tts');
233
+ }
222
234
  const response = await client.audio.speech.create({
223
235
  model: model,
224
236
  input: tts.input,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-anthropic-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-anthropic-adapter.d.ts","sourceRoot":"","sources":["../../../../src/services/providers/tests/test-anthropic-adapter.ts"],"names":[],"mappings":""}
@@ -0,0 +1,104 @@
1
+ import { AnthropicAdapter } from '../anthropic-adapter.js';
2
+ const adapter = new AnthropicAdapter();
3
+ async function testChatCompletion() {
4
+ console.log('Testing chat completion...');
5
+ const request = {
6
+ gate: 'test-gate',
7
+ model: 'claude-sonnet-4-5-20250929',
8
+ type: 'chat',
9
+ data: {
10
+ messages: [
11
+ { role: 'user', content: 'Say "Hello World" and nothing else.' }
12
+ ],
13
+ temperature: 0.7,
14
+ maxTokens: 10,
15
+ }
16
+ };
17
+ const response = await adapter.call(request);
18
+ console.log('Response:', response.content);
19
+ console.log('Tokens:', response.usage);
20
+ console.log('Cost:', response.cost);
21
+ console.log('Latency:', response.latencyMs + 'ms');
22
+ console.log('Finish reason:', response.finishReason);
23
+ console.log('✅ Chat completion test passed\n');
24
+ }
25
+ async function testChatWithVision() {
26
+ console.log('Testing chat with vision...');
27
+ const request = {
28
+ gate: 'test-gate',
29
+ model: 'claude-sonnet-4-5-20250929',
30
+ type: 'chat',
31
+ data: {
32
+ messages: [
33
+ {
34
+ role: 'user',
35
+ content: 'What color is the sky in this image?',
36
+ images: [{
37
+ url: 'https://images.unsplash.com/photo-1765202659641-9ad9facfe5cf?q=80&w=1364&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
38
+ }]
39
+ }
40
+ ],
41
+ maxTokens: 50,
42
+ }
43
+ };
44
+ const response = await adapter.call(request);
45
+ console.log('Response:', response.content);
46
+ console.log('Finish reason:', response.finishReason);
47
+ console.log('✅ Vision test passed\n');
48
+ }
49
+ async function testToolCalls() {
50
+ console.log('Testing tool calls...');
51
+ const request = {
52
+ gate: 'test-gate',
53
+ model: 'claude-sonnet-4-5-20250929',
54
+ type: 'chat',
55
+ data: {
56
+ messages: [
57
+ { role: 'user', content: 'What is the weather in San Francisco?' }
58
+ ],
59
+ tools: [
60
+ {
61
+ type: 'function',
62
+ function: {
63
+ name: 'get_weather',
64
+ description: 'Get the current weather for a location',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: {
68
+ location: {
69
+ type: 'string',
70
+ description: 'The city and state, e.g. San Francisco, CA',
71
+ },
72
+ },
73
+ required: ['location'],
74
+ },
75
+ },
76
+ },
77
+ ],
78
+ maxTokens: 100,
79
+ }
80
+ };
81
+ const response = await adapter.call(request);
82
+ console.log('Response:', response.content);
83
+ console.log('Tool calls:', response.toolCalls);
84
+ console.log('Finish reason:', response.finishReason);
85
+ if (response.toolCalls && response.toolCalls.length > 0) {
86
+ console.log('✅ Tool calls test passed\n');
87
+ }
88
+ else {
89
+ throw new Error('Expected tool calls but got none');
90
+ }
91
+ }
92
+ async function runTests() {
93
+ try {
94
+ await testChatCompletion();
95
+ await testChatWithVision();
96
+ await testToolCalls();
97
+ console.log('✅ All tests passed!');
98
+ }
99
+ catch (error) {
100
+ console.error('❌ Test failed:', error);
101
+ process.exit(1);
102
+ }
103
+ }
104
+ runTests();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-openai-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-openai-adapter.d.ts","sourceRoot":"","sources":["../../../../src/services/providers/tests/test-openai-adapter.ts"],"names":[],"mappings":""}