@layer-ai/core 2.0.18 → 2.0.19

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.
@@ -392,6 +392,84 @@ async function testGeminiToolCallsStreaming() {
392
392
  console.log(' ⚠️ Tool calls may not have been invoked (model chose not to use tools)\n');
393
393
  }
394
394
  }
395
+ // Test 10: Mistral streaming
396
+ async function testMistralStreaming() {
397
+ console.log('Test 10: Mistral Streaming');
398
+ console.log('-'.repeat(80));
399
+ const request = {
400
+ gateId: 'test-gate',
401
+ model: 'mistral-small-2501',
402
+ type: 'chat',
403
+ data: {
404
+ messages: [
405
+ { role: 'user', content: 'Say "mistral test passed" and nothing else.' }
406
+ ],
407
+ maxTokens: 20,
408
+ stream: true,
409
+ }
410
+ };
411
+ let chunkCount = 0;
412
+ let fullContent = '';
413
+ for await (const chunk of callAdapterStream(request)) {
414
+ chunkCount++;
415
+ if (chunk.content) {
416
+ fullContent += chunk.content;
417
+ }
418
+ }
419
+ console.log(` Chunks received: ${chunkCount}`);
420
+ console.log(` Content: ${fullContent.trim()}`);
421
+ console.log(' ✅ Mistral streaming test passed\n');
422
+ }
423
+ // Test 11: Mistral with tool calls streaming
424
+ async function testMistralToolCallsStreaming() {
425
+ console.log('Test 11: Mistral Tool Calls Streaming');
426
+ console.log('-'.repeat(80));
427
+ const request = {
428
+ gateId: 'test-gate',
429
+ model: 'mistral-small-2501',
430
+ type: 'chat',
431
+ data: {
432
+ messages: [
433
+ { role: 'user', content: 'What is the weather in Berlin?' }
434
+ ],
435
+ tools: [
436
+ {
437
+ type: 'function',
438
+ function: {
439
+ name: 'get_weather',
440
+ description: 'Get weather for a location',
441
+ parameters: {
442
+ type: 'object',
443
+ properties: {
444
+ location: { type: 'string' },
445
+ },
446
+ required: ['location'],
447
+ },
448
+ },
449
+ },
450
+ ],
451
+ stream: true,
452
+ }
453
+ };
454
+ let toolCallsFound = false;
455
+ let finishReason = null;
456
+ for await (const chunk of callAdapterStream(request)) {
457
+ if (chunk.toolCalls && chunk.toolCalls.length > 0) {
458
+ toolCallsFound = true;
459
+ }
460
+ if (chunk.finishReason) {
461
+ finishReason = chunk.finishReason;
462
+ }
463
+ }
464
+ console.log(` Tool calls found: ${toolCallsFound}`);
465
+ console.log(` Finish reason: ${finishReason}`);
466
+ if (toolCallsFound) {
467
+ console.log(' ✅ Mistral tool calls streaming test passed\n');
468
+ }
469
+ else {
470
+ console.log(' ⚠️ Tool calls may not have been invoked (model chose not to use tools)\n');
471
+ }
472
+ }
395
473
  // Run all tests
396
474
  (async () => {
397
475
  try {
@@ -404,6 +482,8 @@ async function testGeminiToolCallsStreaming() {
404
482
  await testMultiProviderFallback();
405
483
  await testGeminiStreaming();
406
484
  await testGeminiToolCallsStreaming();
485
+ await testMistralStreaming();
486
+ await testMistralToolCallsStreaming();
407
487
  console.log('='.repeat(80));
408
488
  console.log('✅ ALL STREAMING ROUTE TESTS PASSED');
409
489
  console.log('='.repeat(80));
@@ -7,7 +7,9 @@ export declare class MistralAdapter extends BaseProviderAdapter {
7
7
  protected finishReasonMappings: Record<string, FinishReason>;
8
8
  protected toolChoiceMappings: Record<string, string>;
9
9
  call(request: LayerRequest, userId?: string): Promise<LayerResponse>;
10
+ callStream(request: LayerRequest, userId?: string): AsyncIterable<LayerResponse>;
10
11
  private handleChat;
12
+ private handleChatStream;
11
13
  private handleEmbeddings;
12
14
  private handleOCR;
13
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mistral-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/mistral-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EAEb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,cAAe,SAAQ,mBAAmB;IACrD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAoB;IAEhD,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAGF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAsB5D,UAAU;YAiMV,gBAAgB;YA0ChB,SAAS;CA8ExB"}
1
+ {"version":3,"file":"mistral-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/mistral-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EAEb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,cAAe,SAAQ,mBAAmB;IACrD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAoB;IAEhD,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAGF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAM1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAsBnE,UAAU,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC;YAYzE,UAAU;YAiMT,gBAAgB;YAgPjB,gBAAgB;YA0ChB,SAAS;CA8ExB"}
@@ -64,6 +64,16 @@ export class MistralAdapter extends BaseProviderAdapter {
64
64
  throw new Error(`Unknown modality: ${request.type}`);
65
65
  }
66
66
  }
67
+ async *callStream(request, userId) {
68
+ const resolved = await resolveApiKey(this.provider, userId, process.env.MISTRAL_API_KEY);
69
+ switch (request.type) {
70
+ case 'chat':
71
+ yield* this.handleChatStream(request, resolved.key, resolved.usedPlatformKey);
72
+ break;
73
+ default:
74
+ throw new Error(`Streaming not supported for type: ${request.type}`);
75
+ }
76
+ }
67
77
  async handleChat(request, apiKey, usedPlatformKey) {
68
78
  const startTime = Date.now();
69
79
  const mistral = getMistralClient(apiKey);
@@ -218,6 +228,201 @@ export class MistralAdapter extends BaseProviderAdapter {
218
228
  raw: response,
219
229
  };
220
230
  }
231
+ async *handleChatStream(request, apiKey, usedPlatformKey) {
232
+ const startTime = Date.now();
233
+ const mistral = getMistralClient(apiKey);
234
+ const { data: chat, model } = request;
235
+ if (!model) {
236
+ throw new Error('Model is required for chat completion');
237
+ }
238
+ // Build messages array (same as non-streaming)
239
+ const messages = [];
240
+ // Handle system prompt
241
+ if (chat.systemPrompt) {
242
+ messages.push({ role: 'system', content: chat.systemPrompt });
243
+ }
244
+ // Convert messages to Mistral format
245
+ for (const msg of chat.messages) {
246
+ const role = this.mapRole(msg.role);
247
+ // Handle vision messages (content + images)
248
+ if (msg.images && msg.images.length > 0 && role === 'user') {
249
+ const content = [];
250
+ if (msg.content) {
251
+ content.push({ type: 'text', text: msg.content });
252
+ }
253
+ for (const image of msg.images) {
254
+ const imageUrl = image.url || `data:${image.mimeType || 'image/jpeg'};base64,${image.base64}`;
255
+ content.push({
256
+ type: 'image_url',
257
+ imageUrl: imageUrl,
258
+ });
259
+ }
260
+ messages.push({ role, content });
261
+ }
262
+ // Handle tool responses
263
+ else if (msg.toolCallId && role === 'tool') {
264
+ messages.push({
265
+ role: 'tool',
266
+ content: msg.content || '',
267
+ toolCallId: msg.toolCallId,
268
+ name: msg.name,
269
+ });
270
+ }
271
+ // Handle assistant messages with tool calls
272
+ else if (msg.toolCalls && msg.toolCalls.length > 0) {
273
+ messages.push({
274
+ role: 'assistant',
275
+ content: msg.content || '',
276
+ toolCalls: msg.toolCalls.map((tc) => ({
277
+ id: tc.id,
278
+ type: 'function',
279
+ function: {
280
+ name: tc.function.name,
281
+ arguments: tc.function.arguments,
282
+ },
283
+ })),
284
+ });
285
+ }
286
+ // Handle regular text messages
287
+ else {
288
+ messages.push({
289
+ role,
290
+ content: msg.content || '',
291
+ });
292
+ }
293
+ }
294
+ // Convert tools to Mistral format
295
+ let tools;
296
+ if (chat.tools && chat.tools.length > 0) {
297
+ tools = chat.tools.map((tool) => ({
298
+ type: 'function',
299
+ function: {
300
+ name: tool.function.name,
301
+ description: tool.function.description,
302
+ parameters: tool.function.parameters || {},
303
+ },
304
+ }));
305
+ }
306
+ // Map tool choice
307
+ let toolChoice;
308
+ if (chat.toolChoice) {
309
+ if (typeof chat.toolChoice === 'object') {
310
+ toolChoice = chat.toolChoice;
311
+ }
312
+ else {
313
+ const mapped = this.mapToolChoice(chat.toolChoice);
314
+ if (mapped === 'auto' || mapped === 'none' || mapped === 'any' || mapped === 'required') {
315
+ toolChoice = mapped;
316
+ }
317
+ }
318
+ }
319
+ const stream = await mistral.chat.stream({
320
+ model,
321
+ messages: messages,
322
+ ...(chat.temperature !== undefined && { temperature: chat.temperature }),
323
+ ...(chat.maxTokens !== undefined && { maxTokens: chat.maxTokens }),
324
+ ...(chat.topP !== undefined && { topP: chat.topP }),
325
+ ...(chat.stopSequences !== undefined && { stop: chat.stopSequences }),
326
+ ...(chat.frequencyPenalty !== undefined && { frequencyPenalty: chat.frequencyPenalty }),
327
+ ...(chat.presencePenalty !== undefined && { presencePenalty: chat.presencePenalty }),
328
+ ...(chat.seed !== undefined && { randomSeed: chat.seed }),
329
+ ...(tools && { tools }),
330
+ ...(toolChoice && { toolChoice }),
331
+ ...(chat.responseFormat && {
332
+ responseFormat: typeof chat.responseFormat === 'string'
333
+ ? { type: chat.responseFormat }
334
+ : chat.responseFormat,
335
+ }),
336
+ });
337
+ let promptTokens = 0;
338
+ let completionTokens = 0;
339
+ let totalTokens = 0;
340
+ let fullContent = '';
341
+ let currentToolCalls = [];
342
+ let finishReason = null;
343
+ let modelVersion;
344
+ for await (const chunk of stream) {
345
+ // Mistral CompletionEvent can be of type 'chunk' or 'usage'
346
+ const event = chunk;
347
+ // Handle chunk events with choices
348
+ if (event.data?.choices) {
349
+ const choice = event.data.choices[0];
350
+ const delta = choice?.delta;
351
+ // Handle text content
352
+ if (delta?.content) {
353
+ const contentStr = typeof delta.content === 'string'
354
+ ? delta.content
355
+ : Array.isArray(delta.content)
356
+ ? delta.content.map((c) => c.text || c.content || '').join('')
357
+ : '';
358
+ if (contentStr) {
359
+ fullContent += contentStr;
360
+ yield {
361
+ content: contentStr,
362
+ model: model,
363
+ stream: true,
364
+ };
365
+ }
366
+ }
367
+ // Handle tool calls
368
+ if (delta?.toolCalls && delta.toolCalls.length > 0) {
369
+ for (const tc of delta.toolCalls) {
370
+ const existingCall = currentToolCalls.find(call => call.id === tc.id);
371
+ if (existingCall) {
372
+ // Append to existing tool call arguments
373
+ if (tc.function?.arguments) {
374
+ existingCall.function.arguments += tc.function.arguments;
375
+ }
376
+ }
377
+ else {
378
+ // New tool call
379
+ currentToolCalls.push({
380
+ id: tc.id || `call_${currentToolCalls.length}`,
381
+ type: 'function',
382
+ function: {
383
+ name: tc.function?.name || '',
384
+ arguments: tc.function?.arguments || '',
385
+ },
386
+ });
387
+ }
388
+ }
389
+ }
390
+ // Handle finish reason
391
+ if (choice?.finishReason) {
392
+ finishReason = choice.finishReason;
393
+ }
394
+ // Handle model version
395
+ if (event.data.model) {
396
+ modelVersion = event.data.model;
397
+ }
398
+ }
399
+ // Handle usage events
400
+ if (event.data?.usage) {
401
+ promptTokens = event.data.usage.promptTokens || 0;
402
+ completionTokens = event.data.usage.completionTokens || 0;
403
+ totalTokens = event.data.usage.totalTokens || 0;
404
+ }
405
+ }
406
+ const cost = this.calculateCost(model, promptTokens, completionTokens);
407
+ const latencyMs = Date.now() - startTime;
408
+ // Yield final chunk with metadata
409
+ yield {
410
+ content: '',
411
+ model: modelVersion || model,
412
+ toolCalls: currentToolCalls.length > 0 ? currentToolCalls : undefined,
413
+ usage: {
414
+ promptTokens,
415
+ completionTokens,
416
+ totalTokens,
417
+ },
418
+ cost,
419
+ latencyMs,
420
+ usedPlatformKey,
421
+ stream: true,
422
+ finishReason: this.mapFinishReason(finishReason || 'stop'),
423
+ rawFinishReason: finishReason || undefined,
424
+ };
425
+ }
221
426
  async handleEmbeddings(request, apiKey, usedPlatformKey) {
222
427
  const startTime = Date.now();
223
428
  const mistral = getMistralClient(apiKey);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=test-mistral-streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-mistral-streaming.d.ts","sourceRoot":"","sources":["../../../../src/services/providers/tests/test-mistral-streaming.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ import { MistralAdapter } from '../mistral-adapter.js';
2
+ const adapter = new MistralAdapter();
3
+ async function testChatStreamingBasic() {
4
+ console.log('Testing basic chat streaming...');
5
+ const request = {
6
+ gateId: 'test-gate',
7
+ model: 'mistral-small-2501',
8
+ type: 'chat',
9
+ data: {
10
+ messages: [
11
+ { role: 'user', content: 'Count from 1 to 5 slowly, one number per line.' }
12
+ ],
13
+ temperature: 0.7,
14
+ maxTokens: 50,
15
+ stream: true,
16
+ }
17
+ };
18
+ if (!adapter.callStream) {
19
+ throw new Error('callStream method not available');
20
+ }
21
+ let chunkCount = 0;
22
+ let fullContent = '';
23
+ let finalUsage = null;
24
+ let finalCost = null;
25
+ console.log('\nStreaming chunks:');
26
+ console.log('---');
27
+ for await (const chunk of adapter.callStream(request)) {
28
+ chunkCount++;
29
+ if (chunk.content) {
30
+ process.stdout.write(chunk.content);
31
+ fullContent += chunk.content;
32
+ }
33
+ if (chunk.usage) {
34
+ finalUsage = chunk.usage;
35
+ }
36
+ if (chunk.cost) {
37
+ finalCost = chunk.cost;
38
+ }
39
+ }
40
+ console.log('\n---\n');
41
+ console.log('Total chunks received:', chunkCount);
42
+ console.log('Full content:', fullContent);
43
+ console.log('Final usage:', finalUsage);
44
+ console.log('Final cost:', finalCost);
45
+ console.log('✅ Basic streaming test passed\n');
46
+ }
47
+ async function testChatStreamingWithToolCalls() {
48
+ console.log('Testing chat streaming with tool calls...');
49
+ const request = {
50
+ gateId: 'test-gate',
51
+ model: 'mistral-small-2501',
52
+ type: 'chat',
53
+ data: {
54
+ messages: [
55
+ { role: 'user', content: 'What is the weather in Paris?' }
56
+ ],
57
+ tools: [
58
+ {
59
+ type: 'function',
60
+ function: {
61
+ name: 'get_weather',
62
+ description: 'Get the current weather for a location',
63
+ parameters: {
64
+ type: 'object',
65
+ properties: {
66
+ location: {
67
+ type: 'string',
68
+ description: 'The city and state, e.g. Paris, France',
69
+ },
70
+ },
71
+ required: ['location'],
72
+ },
73
+ },
74
+ },
75
+ ],
76
+ toolChoice: 'auto',
77
+ stream: true,
78
+ }
79
+ };
80
+ if (!adapter.callStream) {
81
+ throw new Error('callStream method not available');
82
+ }
83
+ let toolCallsReceived = false;
84
+ for await (const chunk of adapter.callStream(request)) {
85
+ if (chunk.toolCalls && chunk.toolCalls.length > 0) {
86
+ console.log('Tool calls received:', JSON.stringify(chunk.toolCalls, null, 2));
87
+ toolCallsReceived = true;
88
+ }
89
+ if (chunk.finishReason === 'tool_call') {
90
+ console.log('Finish reason: tool_call');
91
+ }
92
+ }
93
+ if (!toolCallsReceived) {
94
+ console.warn('⚠️ No tool calls received (model may have chosen not to use tools)');
95
+ }
96
+ else {
97
+ console.log('✅ Tool calls streaming test passed\n');
98
+ }
99
+ }
100
+ async function testChatStreamingError() {
101
+ console.log('Testing streaming with invalid model (error handling)...');
102
+ const request = {
103
+ gateId: 'test-gate',
104
+ model: 'invalid-mistral-model-name',
105
+ type: 'chat',
106
+ data: {
107
+ messages: [
108
+ { role: 'user', content: 'Hello' }
109
+ ],
110
+ stream: true,
111
+ }
112
+ };
113
+ if (!adapter.callStream) {
114
+ throw new Error('callStream method not available');
115
+ }
116
+ try {
117
+ for await (const chunk of adapter.callStream(request)) {
118
+ console.log('Received chunk:', chunk);
119
+ }
120
+ console.error('❌ Should have thrown an error for invalid model');
121
+ }
122
+ catch (error) {
123
+ console.log('✅ Correctly threw error:', error instanceof Error ? error.message : error);
124
+ console.log('✅ Error handling test passed\n');
125
+ }
126
+ }
127
+ // Run all tests
128
+ (async () => {
129
+ try {
130
+ await testChatStreamingBasic();
131
+ await testChatStreamingWithToolCalls();
132
+ await testChatStreamingError();
133
+ console.log('✅ All streaming tests completed successfully!');
134
+ }
135
+ catch (error) {
136
+ console.error('❌ Test failed:', error);
137
+ process.exit(1);
138
+ }
139
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layer-ai/core",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "Core API routes and services for Layer AI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",