@node-llm/core 1.9.0 → 1.11.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/README.md +89 -6
- package/dist/agent/Agent.d.ts +191 -0
- package/dist/agent/Agent.d.ts.map +1 -0
- package/dist/agent/Agent.js +272 -0
- package/dist/aliases.d.ts +102 -9
- package/dist/aliases.d.ts.map +1 -1
- package/dist/aliases.js +102 -9
- package/dist/chat/Chat.d.ts +1 -0
- package/dist/chat/Chat.d.ts.map +1 -1
- package/dist/chat/Chat.js +184 -131
- package/dist/chat/ChatOptions.d.ts +2 -0
- package/dist/chat/ChatOptions.d.ts.map +1 -1
- package/dist/chat/ChatStream.d.ts.map +1 -1
- package/dist/chat/ChatStream.js +109 -66
- package/dist/chat/Tool.d.ts +43 -2
- package/dist/chat/Tool.d.ts.map +1 -1
- package/dist/chat/Tool.js +50 -0
- package/dist/chat/ToolHandler.d.ts +10 -5
- package/dist/chat/ToolHandler.d.ts.map +1 -1
- package/dist/chat/ToolHandler.js +10 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/llm.d.ts +8 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +156 -59
- package/dist/middlewares/CostGuardMiddleware.d.ts +24 -0
- package/dist/middlewares/CostGuardMiddleware.d.ts.map +1 -0
- package/dist/middlewares/CostGuardMiddleware.js +23 -0
- package/dist/middlewares/PIIMaskMiddleware.d.ts +23 -0
- package/dist/middlewares/PIIMaskMiddleware.d.ts.map +1 -0
- package/dist/middlewares/PIIMaskMiddleware.js +41 -0
- package/dist/middlewares/UsageLoggerMiddleware.d.ts +22 -0
- package/dist/middlewares/UsageLoggerMiddleware.d.ts.map +1 -0
- package/dist/middlewares/UsageLoggerMiddleware.js +30 -0
- package/dist/middlewares/index.d.ts +4 -0
- package/dist/middlewares/index.d.ts.map +1 -0
- package/dist/middlewares/index.js +3 -0
- package/dist/models/models.json +1458 -448
- package/dist/providers/BaseProvider.d.ts +6 -1
- package/dist/providers/BaseProvider.d.ts.map +1 -1
- package/dist/providers/BaseProvider.js +19 -0
- package/dist/providers/openai/OpenAIProvider.d.ts +1 -1
- package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/openai/OpenAIProvider.js +13 -2
- package/dist/types/Middleware.d.ts +106 -0
- package/dist/types/Middleware.d.ts.map +1 -0
- package/dist/types/Middleware.js +1 -0
- package/dist/utils/middleware-runner.d.ts +7 -0
- package/dist/utils/middleware-runner.d.ts.map +1 -0
- package/dist/utils/middleware-runner.js +23 -0
- package/package.json +6 -2
package/dist/chat/Chat.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ChatStream } from "./ChatStream.js";
|
|
|
5
5
|
import { ModelRegistry } from "../models/ModelRegistry.js";
|
|
6
6
|
import { Schema } from "../schema/Schema.js";
|
|
7
7
|
import { toJsonSchema } from "../schema/to-json-schema.js";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
8
9
|
import { z } from "zod";
|
|
9
10
|
import { config } from "../config.js";
|
|
10
11
|
import { ToolExecutionMode } from "../constants.js";
|
|
@@ -12,6 +13,7 @@ import { ConfigurationError } from "../errors/index.js";
|
|
|
12
13
|
import { ChatValidator } from "./Validation.js";
|
|
13
14
|
import { ToolHandler } from "./ToolHandler.js";
|
|
14
15
|
import { logger } from "../utils/logger.js";
|
|
16
|
+
import { runMiddleware } from "../utils/middleware-runner.js";
|
|
15
17
|
import { ChatResponseString } from "./ChatResponse.js";
|
|
16
18
|
export class Chat {
|
|
17
19
|
provider;
|
|
@@ -20,10 +22,12 @@ export class Chat {
|
|
|
20
22
|
messages = [];
|
|
21
23
|
systemMessages = [];
|
|
22
24
|
executor;
|
|
25
|
+
middlewares = [];
|
|
23
26
|
constructor(provider, model, options = {}, retryConfig = { attempts: 1, delayMs: 0 }) {
|
|
24
27
|
this.provider = provider;
|
|
25
28
|
this.model = model;
|
|
26
29
|
this.options = options;
|
|
30
|
+
this.middlewares = options.middlewares || [];
|
|
27
31
|
this.executor = new Executor(provider, retryConfig);
|
|
28
32
|
if (options.systemPrompt) {
|
|
29
33
|
this.withInstructions(options.systemPrompt);
|
|
@@ -302,6 +306,8 @@ export class Chat {
|
|
|
302
306
|
* Ask the model a question
|
|
303
307
|
*/
|
|
304
308
|
async ask(content, options) {
|
|
309
|
+
const requestId = randomUUID();
|
|
310
|
+
const state = {};
|
|
305
311
|
let messageContent = content;
|
|
306
312
|
const files = [...(options?.images ?? []), ...(options?.files ?? [])];
|
|
307
313
|
if (files.length > 0) {
|
|
@@ -332,146 +338,74 @@ export class Chat {
|
|
|
332
338
|
}
|
|
333
339
|
};
|
|
334
340
|
}
|
|
335
|
-
|
|
341
|
+
// Prepare Middleware Context
|
|
342
|
+
const context = {
|
|
343
|
+
requestId,
|
|
344
|
+
provider: this.provider.id,
|
|
336
345
|
model: this.model,
|
|
337
346
|
messages: [...this.systemMessages, ...this.messages],
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
max_tokens: options?.maxTokens ?? this.options.maxTokens ?? config.maxTokens,
|
|
341
|
-
headers: { ...this.options.headers, ...options?.headers },
|
|
342
|
-
response_format: responseFormat, // Pass to provider
|
|
343
|
-
requestTimeout: options?.requestTimeout ?? this.options.requestTimeout ?? config.requestTimeout,
|
|
344
|
-
thinking: options?.thinking ?? this.options.thinking,
|
|
345
|
-
signal: options?.signal,
|
|
346
|
-
...this.options.params
|
|
347
|
+
options: this.options,
|
|
348
|
+
state
|
|
347
349
|
};
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
totalUsage.cached_tokens = (totalUsage.cached_tokens ?? 0) + u.cached_tokens;
|
|
372
|
-
}
|
|
373
|
-
if (u.cost !== undefined) {
|
|
374
|
-
totalUsage.cost = (totalUsage.cost ?? 0) + u.cost;
|
|
375
|
-
}
|
|
376
|
-
if (u.input_cost !== undefined) {
|
|
377
|
-
totalUsage.input_cost = (totalUsage.input_cost ?? 0) + u.input_cost;
|
|
378
|
-
}
|
|
379
|
-
if (u.output_cost !== undefined) {
|
|
380
|
-
totalUsage.output_cost = (totalUsage.output_cost ?? 0) + u.output_cost;
|
|
350
|
+
try {
|
|
351
|
+
// 1. onRequest Hook
|
|
352
|
+
await runMiddleware(this.middlewares, "onRequest", context);
|
|
353
|
+
// Re-read mutable context
|
|
354
|
+
const messagesToUse = context.messages || [];
|
|
355
|
+
const executeOptions = {
|
|
356
|
+
model: this.model,
|
|
357
|
+
messages: messagesToUse,
|
|
358
|
+
tools: this.options.tools,
|
|
359
|
+
temperature: options?.temperature ?? this.options.temperature,
|
|
360
|
+
max_tokens: options?.maxTokens ?? this.options.maxTokens ?? config.maxTokens,
|
|
361
|
+
headers: { ...this.options.headers, ...options?.headers },
|
|
362
|
+
response_format: responseFormat, // Pass to provider
|
|
363
|
+
requestTimeout: options?.requestTimeout ?? this.options.requestTimeout ?? config.requestTimeout,
|
|
364
|
+
thinking: options?.thinking ?? this.options.thinking,
|
|
365
|
+
signal: options?.signal,
|
|
366
|
+
...this.options.params
|
|
367
|
+
};
|
|
368
|
+
// --- Content Policy Hooks (Input) ---
|
|
369
|
+
if (this.options.onBeforeRequest) {
|
|
370
|
+
const result = await this.options.onBeforeRequest(executeOptions.messages);
|
|
371
|
+
if (result) {
|
|
372
|
+
executeOptions.messages = result;
|
|
381
373
|
}
|
|
382
374
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const result = await this.options.onAfterResponse(assistantMessage);
|
|
393
|
-
if (result) {
|
|
394
|
-
assistantMessage = result;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
this.messages.push({
|
|
398
|
-
role: "assistant",
|
|
399
|
-
content: assistantMessage?.toString() || null,
|
|
400
|
-
tool_calls: response.tool_calls,
|
|
401
|
-
usage: response.usage
|
|
402
|
-
});
|
|
403
|
-
if (this.options.onEndMessage && (!response.tool_calls || response.tool_calls.length === 0)) {
|
|
404
|
-
this.options.onEndMessage(assistantMessage);
|
|
405
|
-
}
|
|
406
|
-
const maxToolCalls = options?.maxToolCalls ?? this.options.maxToolCalls ?? 5;
|
|
407
|
-
let stepCount = 0;
|
|
408
|
-
while (response.tool_calls && response.tool_calls.length > 0) {
|
|
409
|
-
// Dry-run mode: stop after proposing tools
|
|
410
|
-
if (!ToolHandler.shouldExecuteTools(response.tool_calls, this.options.toolExecution)) {
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
stepCount++;
|
|
414
|
-
if (stepCount > maxToolCalls) {
|
|
415
|
-
throw new Error(`[NodeLLM] Maximum tool execution calls (${maxToolCalls}) exceeded.`);
|
|
416
|
-
}
|
|
417
|
-
for (const toolCall of response.tool_calls) {
|
|
418
|
-
// Human-in-the-loop: check for approval
|
|
419
|
-
if (this.options.toolExecution === ToolExecutionMode.CONFIRM) {
|
|
420
|
-
const approved = await ToolHandler.requestToolConfirmation(toolCall, this.options.onConfirmToolCall);
|
|
421
|
-
if (!approved) {
|
|
422
|
-
this.messages.push(this.provider.formatToolResultMessage(toolCall.id, "Action cancelled by user."));
|
|
423
|
-
continue;
|
|
375
|
+
const totalUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
376
|
+
const trackUsage = (u) => {
|
|
377
|
+
if (u) {
|
|
378
|
+
// Fallback cost calculation if provider didn't return it
|
|
379
|
+
if (u.cost === undefined) {
|
|
380
|
+
const withCost = ModelRegistry.calculateCost(u, this.model, this.provider.id);
|
|
381
|
+
u.cost = withCost.cost;
|
|
382
|
+
u.input_cost = withCost.input_cost;
|
|
383
|
+
u.output_cost = withCost.output_cost;
|
|
424
384
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
catch (error) {
|
|
431
|
-
let currentError = error;
|
|
432
|
-
const directive = await this.options.onToolCallError?.(toolCall, currentError);
|
|
433
|
-
if (directive === "STOP") {
|
|
434
|
-
throw currentError;
|
|
385
|
+
totalUsage.input_tokens += u.input_tokens;
|
|
386
|
+
totalUsage.output_tokens += u.output_tokens;
|
|
387
|
+
totalUsage.total_tokens += u.total_tokens;
|
|
388
|
+
if (u.cached_tokens) {
|
|
389
|
+
totalUsage.cached_tokens = (totalUsage.cached_tokens ?? 0) + u.cached_tokens;
|
|
435
390
|
}
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
const toolResult = await ToolHandler.execute(toolCall, this.options.tools, this.options.onToolCallStart, this.options.onToolCallEnd);
|
|
439
|
-
this.messages.push(this.provider.formatToolResultMessage(toolResult.tool_call_id, toolResult.content));
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
catch (retryError) {
|
|
443
|
-
// If retry also fails, fall through to default logic
|
|
444
|
-
currentError = retryError;
|
|
445
|
-
}
|
|
391
|
+
if (u.cost !== undefined) {
|
|
392
|
+
totalUsage.cost = (totalUsage.cost ?? 0) + u.cost;
|
|
446
393
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
continue;
|
|
394
|
+
if (u.input_cost !== undefined) {
|
|
395
|
+
totalUsage.input_cost = (totalUsage.input_cost ?? 0) + u.input_cost;
|
|
450
396
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const isFatal = errorObj.fatal === true || errorObj.status === 401 || errorObj.status === 403;
|
|
454
|
-
if (isFatal) {
|
|
455
|
-
throw currentError;
|
|
397
|
+
if (u.output_cost !== undefined) {
|
|
398
|
+
totalUsage.output_cost = (totalUsage.output_cost ?? 0) + u.output_cost;
|
|
456
399
|
}
|
|
457
|
-
logger.error(`Tool execution failed for '${toolCall.function.name}':`, currentError);
|
|
458
400
|
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
temperature: options?.temperature ?? this.options.temperature,
|
|
465
|
-
max_tokens: options?.maxTokens ?? this.options.maxTokens ?? config.maxTokens,
|
|
466
|
-
headers: this.options.headers,
|
|
467
|
-
response_format: responseFormat,
|
|
468
|
-
requestTimeout: options?.requestTimeout ?? this.options.requestTimeout ?? config.requestTimeout,
|
|
469
|
-
signal: options?.signal,
|
|
470
|
-
...this.options.params
|
|
471
|
-
});
|
|
401
|
+
};
|
|
402
|
+
// First round
|
|
403
|
+
if (this.options.onNewMessage)
|
|
404
|
+
this.options.onNewMessage();
|
|
405
|
+
let response = await this.executor.executeChat(executeOptions);
|
|
472
406
|
trackUsage(response.usage);
|
|
473
|
-
assistantMessage = new ChatResponseString(response.content ?? "", response.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, this.model, this.provider.id, response.thinking, response.reasoning, response.tool_calls, response.finish_reason, this.options.schema);
|
|
474
|
-
// --- Content Policy Hooks (Output -
|
|
407
|
+
let assistantMessage = new ChatResponseString(response.content ?? "", response.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, this.model, this.provider.id, response.thinking, response.reasoning, response.tool_calls, response.finish_reason, this.options.schema);
|
|
408
|
+
// --- Content Policy Hooks (Output - Turn 1) ---
|
|
475
409
|
if (this.options.onAfterResponse) {
|
|
476
410
|
const result = await this.options.onAfterResponse(assistantMessage);
|
|
477
411
|
if (result) {
|
|
@@ -487,10 +421,129 @@ export class Chat {
|
|
|
487
421
|
if (this.options.onEndMessage && (!response.tool_calls || response.tool_calls.length === 0)) {
|
|
488
422
|
this.options.onEndMessage(assistantMessage);
|
|
489
423
|
}
|
|
424
|
+
const maxToolCalls = options?.maxToolCalls ?? this.options.maxToolCalls ?? 5;
|
|
425
|
+
let stepCount = 0;
|
|
426
|
+
let haltTriggered = false;
|
|
427
|
+
while (response.tool_calls && response.tool_calls.length > 0 && !haltTriggered) {
|
|
428
|
+
// Dry-run mode: stop after proposing tools
|
|
429
|
+
if (!ToolHandler.shouldExecuteTools(response.tool_calls, this.options.toolExecution)) {
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
stepCount++;
|
|
433
|
+
if (stepCount > maxToolCalls) {
|
|
434
|
+
throw new Error(`[NodeLLM] Maximum tool execution calls (${maxToolCalls}) exceeded.`);
|
|
435
|
+
}
|
|
436
|
+
for (const toolCall of response.tool_calls) {
|
|
437
|
+
// Human-in-the-loop: check for approval
|
|
438
|
+
if (this.options.toolExecution === ToolExecutionMode.CONFIRM) {
|
|
439
|
+
const approved = await ToolHandler.requestToolConfirmation(toolCall, this.options.onConfirmToolCall);
|
|
440
|
+
if (!approved) {
|
|
441
|
+
this.messages.push(this.provider.formatToolResultMessage(toolCall.id, "Action cancelled by user."));
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// 2. onToolCallStart Hook
|
|
446
|
+
await runMiddleware(this.middlewares, "onToolCallStart", context, toolCall);
|
|
447
|
+
try {
|
|
448
|
+
const toolResult = await ToolHandler.execute(toolCall, this.options.tools, this.options.onToolCallStart, this.options.onToolCallEnd);
|
|
449
|
+
// 3. onToolCallEnd Hook
|
|
450
|
+
await runMiddleware(this.middlewares, "onToolCallEnd", context, toolCall, toolResult.content);
|
|
451
|
+
this.messages.push(this.provider.formatToolResultMessage(toolResult.tool_call_id, toolResult.content));
|
|
452
|
+
// Check if tool signaled a halt - stop the agentic loop
|
|
453
|
+
if (toolResult.halted) {
|
|
454
|
+
haltTriggered = true;
|
|
455
|
+
// Create final response from halt content
|
|
456
|
+
assistantMessage = new ChatResponseString(toolResult.content, totalUsage, this.model, this.provider.id, undefined, undefined, undefined, "tool_halt");
|
|
457
|
+
if (this.options.onEndMessage) {
|
|
458
|
+
this.options.onEndMessage(assistantMessage);
|
|
459
|
+
}
|
|
460
|
+
break; // Exit the for loop
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
let currentError = error;
|
|
465
|
+
// 4. onToolCallError Hook
|
|
466
|
+
const middlewareDirective = await runMiddleware(this.middlewares, "onToolCallError", context, toolCall, currentError);
|
|
467
|
+
const directive = middlewareDirective ||
|
|
468
|
+
(await this.options.onToolCallError?.(toolCall, currentError));
|
|
469
|
+
if (directive === "STOP") {
|
|
470
|
+
throw currentError;
|
|
471
|
+
}
|
|
472
|
+
if (directive === "RETRY") {
|
|
473
|
+
// ... retry logic (simplified: recurse or duplicate logic? adhering to original logic)
|
|
474
|
+
// Original logic duplicated the execution block. For brevity in this replacement, I'll simplified retry to "try once more"
|
|
475
|
+
try {
|
|
476
|
+
// Retry Hook? Maybe skip start hook on retry or re-run?
|
|
477
|
+
// Let's assume onToolCallStart fires again for cleanliness?
|
|
478
|
+
// Or just execute directly to match existing behavior.
|
|
479
|
+
// Existing logs show we just call ToolHandler.execute again.
|
|
480
|
+
const toolResult = await ToolHandler.execute(toolCall, this.options.tools, this.options.onToolCallStart, this.options.onToolCallEnd);
|
|
481
|
+
await runMiddleware(this.middlewares, "onToolCallEnd", context, toolCall, toolResult.content);
|
|
482
|
+
this.messages.push(this.provider.formatToolResultMessage(toolResult.tool_call_id, toolResult.content));
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
catch (retryError) {
|
|
486
|
+
currentError = retryError;
|
|
487
|
+
await runMiddleware(this.middlewares, "onToolCallError", context, toolCall, currentError);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
this.messages.push(this.provider.formatToolResultMessage(toolCall.id, `Fatal error executing tool '${toolCall.function.name}': ${currentError.message}`, { isError: true }));
|
|
491
|
+
if (directive === "CONTINUE") {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
// Default short-circuit logic
|
|
495
|
+
const errorObj = currentError;
|
|
496
|
+
const isFatal = errorObj.fatal === true || errorObj.status === 401 || errorObj.status === 403;
|
|
497
|
+
if (isFatal) {
|
|
498
|
+
throw currentError;
|
|
499
|
+
}
|
|
500
|
+
logger.error(`Tool execution failed for '${toolCall.function.name}':`, currentError);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// If halt was triggered, exit the while loop immediately
|
|
504
|
+
if (haltTriggered) {
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
response = await this.executor.executeChat({
|
|
508
|
+
model: this.model,
|
|
509
|
+
messages: [...this.systemMessages, ...this.messages], // Use updated history
|
|
510
|
+
tools: this.options.tools,
|
|
511
|
+
temperature: options?.temperature ?? this.options.temperature,
|
|
512
|
+
max_tokens: options?.maxTokens ?? this.options.maxTokens ?? config.maxTokens,
|
|
513
|
+
headers: this.options.headers,
|
|
514
|
+
response_format: responseFormat, // Pass to provider
|
|
515
|
+
requestTimeout: options?.requestTimeout ?? this.options.requestTimeout ?? config.requestTimeout,
|
|
516
|
+
signal: options?.signal,
|
|
517
|
+
...this.options.params
|
|
518
|
+
});
|
|
519
|
+
trackUsage(response.usage);
|
|
520
|
+
assistantMessage = new ChatResponseString(response.content ?? "", response.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, this.model, this.provider.id, response.thinking, response.reasoning, response.tool_calls, response.finish_reason, this.options.schema);
|
|
521
|
+
if (this.options.onAfterResponse) {
|
|
522
|
+
const result = await this.options.onAfterResponse(assistantMessage);
|
|
523
|
+
if (result)
|
|
524
|
+
assistantMessage = result;
|
|
525
|
+
}
|
|
526
|
+
this.messages.push({
|
|
527
|
+
role: "assistant",
|
|
528
|
+
content: assistantMessage?.toString() || null,
|
|
529
|
+
tool_calls: response.tool_calls,
|
|
530
|
+
usage: response.usage
|
|
531
|
+
});
|
|
532
|
+
if (this.options.onEndMessage &&
|
|
533
|
+
(!response.tool_calls || response.tool_calls.length === 0)) {
|
|
534
|
+
this.options.onEndMessage(assistantMessage);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const finalResponse = new ChatResponseString(assistantMessage.toString() || "", totalUsage, this.model, this.provider.id, assistantMessage.thinking, assistantMessage.reasoning, response.tool_calls, assistantMessage.finish_reason, this.options.schema);
|
|
538
|
+
// 5. onResponse Hook
|
|
539
|
+
await runMiddleware(this.middlewares, "onResponse", context, finalResponse);
|
|
540
|
+
return finalResponse;
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
// 6. onError Hook
|
|
544
|
+
await runMiddleware(this.middlewares, "onError", context, err);
|
|
545
|
+
throw err;
|
|
490
546
|
}
|
|
491
|
-
// For the final return, we might want to aggregate reasoning too if it happened in multiple turns?
|
|
492
|
-
// Usually reasoning only happens once or we just want the last one.
|
|
493
|
-
return new ChatResponseString(assistantMessage.toString() || "", totalUsage, this.model, this.provider.id, assistantMessage.thinking, assistantMessage.reasoning, response.tool_calls, assistantMessage.finish_reason, this.options.schema);
|
|
494
547
|
}
|
|
495
548
|
/**
|
|
496
549
|
* Streams the model's response to a user question.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Middleware } from "../types/Middleware.js";
|
|
1
2
|
import { Message } from "./Message.js";
|
|
2
3
|
import { ToolResolvable } from "./Tool.js";
|
|
3
4
|
import { Schema } from "../schema/Schema.js";
|
|
@@ -6,6 +7,7 @@ import { ToolExecutionMode } from "../constants.js";
|
|
|
6
7
|
import { ResponseFormat, ThinkingConfig } from "../providers/Provider.js";
|
|
7
8
|
export interface ChatOptions {
|
|
8
9
|
systemPrompt?: string;
|
|
10
|
+
middlewares?: Middleware[];
|
|
9
11
|
messages?: Message[];
|
|
10
12
|
tools?: ToolResolvable[];
|
|
11
13
|
temperature?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatOptions.d.ts","sourceRoot":"","sources":["../../src/chat/ChatOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7D,eAAe,CAAC,EAAE,CAChB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,KAAK,KACT,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACtE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACxF"}
|
|
1
|
+
{"version":3,"file":"ChatOptions.d.ts","sourceRoot":"","sources":["../../src/chat/ChatOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7D,eAAe,CAAC,EAAE,CAChB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,KAAK,KACT,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACtE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACxF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatStream.d.ts","sourceRoot":"","sources":["../../src/chat/ChatStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,WAAW,EAIZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAyB,MAAM,0BAA0B,CAAC;AAEtF,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatStream.d.ts","sourceRoot":"","sources":["../../src/chat/ChatStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,WAAW,EAIZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAyB,MAAM,0BAA0B,CAAC;AAEtF,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAavC;;;GAGG;AACH,qBAAa,UAAU;IAKnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,cAAc,CAAY;gBAGf,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,EAC1C,QAAQ,CAAC,EAAE,OAAO,EAAE,EACpB,cAAc,CAAC,EAAE,OAAO,EAAE;IA6B5B,IAAI,OAAO,IAAI,SAAS,OAAO,EAAE,CAEhC;IAED,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,CAAC,SAAS,CAAC;CA8WrF"}
|
package/dist/chat/ChatStream.js
CHANGED
|
@@ -9,6 +9,8 @@ import { ChatValidator } from "./Validation.js";
|
|
|
9
9
|
import { ToolHandler } from "./ToolHandler.js";
|
|
10
10
|
import { logger } from "../utils/logger.js";
|
|
11
11
|
import { ModelRegistry } from "../models/ModelRegistry.js";
|
|
12
|
+
import { runMiddleware } from "../utils/middleware-runner.js";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
12
14
|
/**
|
|
13
15
|
* Internal handler for chat streaming logic.
|
|
14
16
|
* Wraps the provider's stream with side effects like history updates and events.
|
|
@@ -58,6 +60,9 @@ export class ChatStream {
|
|
|
58
60
|
...requestOptions,
|
|
59
61
|
headers: { ...baseOptions.headers, ...requestOptions.headers }
|
|
60
62
|
};
|
|
63
|
+
const requestId = randomUUID();
|
|
64
|
+
const state = {};
|
|
65
|
+
const middlewares = options.middlewares || [];
|
|
61
66
|
// Process Multimodal Content
|
|
62
67
|
let messageContent = content;
|
|
63
68
|
const files = [...(requestOptions.images ?? []), ...(requestOptions.files ?? [])];
|
|
@@ -71,63 +76,73 @@ export class ChatStream {
|
|
|
71
76
|
ChatValidator.validateTools(provider, model, true, options);
|
|
72
77
|
}
|
|
73
78
|
messages.push({ role: "user", content: messageContent });
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
// Prepare Middleware Context
|
|
80
|
+
const context = {
|
|
81
|
+
requestId,
|
|
82
|
+
provider: provider.id,
|
|
83
|
+
model: model,
|
|
84
|
+
messages: [...systemMessages, ...messages],
|
|
85
|
+
options: options,
|
|
86
|
+
state
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
// 1. onRequest Hook
|
|
90
|
+
await runMiddleware(middlewares, "onRequest", context);
|
|
91
|
+
if (!provider.stream) {
|
|
92
|
+
throw new Error("Streaming not supported by provider");
|
|
93
|
+
}
|
|
94
|
+
// Process Schema/Structured Output
|
|
95
|
+
let responseFormat = options.responseFormat;
|
|
96
|
+
if (!responseFormat && options.schema) {
|
|
97
|
+
ChatValidator.validateStructuredOutput(provider, model, true, options);
|
|
98
|
+
const jsonSchema = toJsonSchema(options.schema.definition.schema);
|
|
99
|
+
responseFormat = {
|
|
100
|
+
type: "json_schema",
|
|
101
|
+
json_schema: {
|
|
102
|
+
name: options.schema.definition.name,
|
|
103
|
+
description: options.schema.definition.description,
|
|
104
|
+
strict: options.schema.definition.strict ?? true,
|
|
105
|
+
schema: jsonSchema
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
let isFirst = true;
|
|
110
|
+
const maxToolCalls = options.maxToolCalls ?? 5;
|
|
111
|
+
let stepCount = 0;
|
|
112
|
+
const totalUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
113
|
+
const trackUsage = (u) => {
|
|
114
|
+
if (u) {
|
|
115
|
+
// Fallback cost calculation if provider didn't return it
|
|
116
|
+
if (u.cost === undefined) {
|
|
117
|
+
const withCost = ModelRegistry.calculateCost(u, model, provider.id);
|
|
118
|
+
u.cost = withCost.cost;
|
|
119
|
+
u.input_cost = withCost.input_cost;
|
|
120
|
+
u.output_cost = withCost.output_cost;
|
|
121
|
+
}
|
|
122
|
+
totalUsage.input_tokens += u.input_tokens;
|
|
123
|
+
totalUsage.output_tokens += u.output_tokens;
|
|
124
|
+
totalUsage.total_tokens += u.total_tokens;
|
|
125
|
+
if (u.cached_tokens) {
|
|
126
|
+
totalUsage.cached_tokens = (totalUsage.cached_tokens ?? 0) + u.cached_tokens;
|
|
127
|
+
}
|
|
128
|
+
if (u.cost !== undefined) {
|
|
129
|
+
totalUsage.cost = (totalUsage.cost ?? 0) + u.cost;
|
|
130
|
+
}
|
|
89
131
|
}
|
|
90
132
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const maxToolCalls = options.maxToolCalls ?? 5;
|
|
97
|
-
let stepCount = 0;
|
|
98
|
-
const totalUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
99
|
-
const trackUsage = (u) => {
|
|
100
|
-
if (u) {
|
|
101
|
-
// Fallback cost calculation if provider didn't return it
|
|
102
|
-
if (u.cost === undefined) {
|
|
103
|
-
const withCost = ModelRegistry.calculateCost(u, model, provider.id);
|
|
104
|
-
u.cost = withCost.cost;
|
|
105
|
-
u.input_cost = withCost.input_cost;
|
|
106
|
-
u.output_cost = withCost.output_cost;
|
|
133
|
+
let assistantResponse;
|
|
134
|
+
while (true) {
|
|
135
|
+
stepCount++;
|
|
136
|
+
if (stepCount > maxToolCalls) {
|
|
137
|
+
throw new Error(`[NodeLLM] Maximum tool execution calls (${maxToolCalls}) exceeded during streaming.`);
|
|
107
138
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
totalUsage.cost = (totalUsage.cost ?? 0) + u.cost;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
while (true) {
|
|
120
|
-
stepCount++;
|
|
121
|
-
if (stepCount > maxToolCalls) {
|
|
122
|
-
throw new Error(`[NodeLLM] Maximum tool execution calls (${maxToolCalls}) exceeded during streaming.`);
|
|
123
|
-
}
|
|
124
|
-
let fullContent = "";
|
|
125
|
-
let fullReasoning = "";
|
|
126
|
-
const thinking = { text: "" };
|
|
127
|
-
let toolCalls;
|
|
128
|
-
let currentTurnUsage;
|
|
129
|
-
try {
|
|
130
|
-
let requestMessages = [...systemMessages, ...messages];
|
|
139
|
+
let fullContent = "";
|
|
140
|
+
let fullReasoning = "";
|
|
141
|
+
const thinking = { text: "" };
|
|
142
|
+
let toolCalls;
|
|
143
|
+
let currentTurnUsage;
|
|
144
|
+
context.messages = [...systemMessages, ...messages];
|
|
145
|
+
let requestMessages = context.messages; // Use up-to-date messages from context
|
|
131
146
|
if (options.onBeforeRequest) {
|
|
132
147
|
const result = await options.onBeforeRequest(requestMessages);
|
|
133
148
|
if (result) {
|
|
@@ -180,7 +195,7 @@ export class ChatStream {
|
|
|
180
195
|
trackUsage(currentTurnUsage);
|
|
181
196
|
}
|
|
182
197
|
}
|
|
183
|
-
|
|
198
|
+
assistantResponse = new ChatResponseString(fullContent || "", currentTurnUsage || { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, model, provider.id, thinking.text || thinking.signature ? thinking : undefined, fullReasoning || undefined, toolCalls, undefined, // finish_reason
|
|
184
199
|
options.schema);
|
|
185
200
|
if (options.onAfterResponse) {
|
|
186
201
|
const result = await options.onAfterResponse(assistantResponse);
|
|
@@ -212,34 +227,62 @@ export class ChatStream {
|
|
|
212
227
|
continue;
|
|
213
228
|
}
|
|
214
229
|
}
|
|
230
|
+
// 2. onToolCallStart Hook
|
|
231
|
+
await runMiddleware(middlewares, "onToolCallStart", context, toolCall);
|
|
215
232
|
try {
|
|
216
233
|
const toolResult = await ToolHandler.execute(toolCall, options.tools, options.onToolCallStart, options.onToolCallEnd);
|
|
234
|
+
// 3. onToolCallEnd Hook
|
|
235
|
+
await runMiddleware(middlewares, "onToolCallEnd", context, toolCall, toolResult.content);
|
|
217
236
|
messages.push(provider.formatToolResultMessage(toolResult.tool_call_id, toolResult.content));
|
|
218
237
|
}
|
|
219
238
|
catch (error) {
|
|
220
|
-
|
|
221
|
-
|
|
239
|
+
let currentError = error;
|
|
240
|
+
// 4. onToolCallError Hook
|
|
241
|
+
const middlewareDirective = await runMiddleware(middlewares, "onToolCallError", context, toolCall, currentError);
|
|
242
|
+
const directive = middlewareDirective || (await options.onToolCallError?.(toolCall, currentError));
|
|
222
243
|
if (directive === "STOP") {
|
|
223
244
|
throw error;
|
|
224
245
|
}
|
|
225
|
-
|
|
246
|
+
if (directive === "RETRY") {
|
|
247
|
+
try {
|
|
248
|
+
const toolResult = await ToolHandler.execute(toolCall, options.tools, options.onToolCallStart, options.onToolCallEnd);
|
|
249
|
+
await runMiddleware(middlewares, "onToolCallEnd", context, toolCall, toolResult.content);
|
|
250
|
+
messages.push(provider.formatToolResultMessage(toolResult.tool_call_id, toolResult.content));
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
catch (retryError) {
|
|
254
|
+
currentError = retryError;
|
|
255
|
+
await runMiddleware(middlewares, "onToolCallError", context, toolCall, currentError);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
messages.push(provider.formatToolResultMessage(toolCall.id, `Fatal error executing tool '${toolCall.function.name}': ${currentError.message}`, { isError: true }));
|
|
226
259
|
if (directive === "CONTINUE") {
|
|
227
260
|
continue;
|
|
228
261
|
}
|
|
229
|
-
const isFatal =
|
|
262
|
+
const isFatal = currentError.fatal === true ||
|
|
263
|
+
currentError.status === 401 ||
|
|
264
|
+
currentError.status === 403;
|
|
230
265
|
if (isFatal) {
|
|
231
|
-
throw
|
|
266
|
+
throw currentError;
|
|
232
267
|
}
|
|
233
|
-
logger.error(`Tool execution failed for '${toolCall.function.name}':`,
|
|
268
|
+
logger.error(`Tool execution failed for '${toolCall.function.name}':`, currentError);
|
|
234
269
|
}
|
|
235
270
|
}
|
|
271
|
+
// Loop continues -> streaming next chunk
|
|
236
272
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
273
|
+
// 5. onResponse Hook
|
|
274
|
+
if (assistantResponse) {
|
|
275
|
+
await runMiddleware(middlewares, "onResponse", context, assistantResponse);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
// 6. onError Hook
|
|
280
|
+
await runMiddleware(middlewares, "onError", context, err);
|
|
281
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
282
|
+
// Aborted, still maybe want onError? Middleware logic says "onError".
|
|
283
|
+
// But rethrow for sure.
|
|
242
284
|
}
|
|
285
|
+
throw err;
|
|
243
286
|
}
|
|
244
287
|
};
|
|
245
288
|
return new Stream(() => sideEffectGenerator(this, this.provider, this.model, this.messages, this.systemMessages, this.options, controller, content, options), controller);
|