@dexto/core 1.8.3 → 1.8.4

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 (51) hide show
  1. package/dist/agent/DextoAgent.cjs +3 -1
  2. package/dist/agent/DextoAgent.d.ts +2 -0
  3. package/dist/agent/DextoAgent.d.ts.map +1 -1
  4. package/dist/agent/DextoAgent.js +7 -2
  5. package/dist/events/index.d.ts +3 -0
  6. package/dist/events/index.d.ts.map +1 -1
  7. package/dist/index.browser.cjs +6 -0
  8. package/dist/index.browser.d.ts +2 -0
  9. package/dist/index.browser.d.ts.map +1 -1
  10. package/dist/index.browser.js +4 -0
  11. package/dist/llm/executor/provider-error.cjs +214 -0
  12. package/dist/llm/executor/provider-error.d.ts +26 -0
  13. package/dist/llm/executor/provider-error.d.ts.map +1 -0
  14. package/dist/llm/executor/provider-error.js +190 -0
  15. package/dist/llm/executor/stream-processor.cjs +34 -5
  16. package/dist/llm/executor/stream-processor.d.ts +4 -1
  17. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  18. package/dist/llm/executor/stream-processor.js +34 -5
  19. package/dist/llm/executor/turn-executor.cjs +185 -124
  20. package/dist/llm/executor/turn-executor.d.ts +5 -5
  21. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  22. package/dist/llm/executor/turn-executor.js +184 -120
  23. package/dist/session/title-generator.cjs +19 -2
  24. package/dist/session/title-generator.d.ts +8 -0
  25. package/dist/session/title-generator.d.ts.map +1 -1
  26. package/dist/session/title-generator.js +19 -2
  27. package/dist/systemPrompt/contributors.cjs +10 -1
  28. package/dist/systemPrompt/contributors.d.ts.map +1 -1
  29. package/dist/systemPrompt/contributors.js +10 -1
  30. package/dist/telemetry/browser.cjs +138 -0
  31. package/dist/telemetry/browser.d.ts +30 -0
  32. package/dist/telemetry/browser.d.ts.map +1 -0
  33. package/dist/telemetry/browser.js +115 -0
  34. package/dist/telemetry/index.cjs +5 -2
  35. package/dist/telemetry/index.d.ts +1 -0
  36. package/dist/telemetry/index.d.ts.map +1 -1
  37. package/dist/telemetry/index.js +3 -1
  38. package/dist/telemetry/operation-span.cjs +73 -0
  39. package/dist/telemetry/operation-span.d.ts +13 -0
  40. package/dist/telemetry/operation-span.d.ts.map +1 -0
  41. package/dist/telemetry/operation-span.js +50 -0
  42. package/dist/telemetry/telemetry.cjs +2 -3
  43. package/dist/telemetry/telemetry.d.ts.map +1 -1
  44. package/dist/telemetry/telemetry.js +2 -3
  45. package/dist/telemetry/utils.cjs +11 -12
  46. package/dist/telemetry/utils.d.ts.map +1 -1
  47. package/dist/telemetry/utils.js +11 -12
  48. package/dist/tools/tool-call-metadata.cjs +118 -6
  49. package/dist/tools/tool-call-metadata.d.ts.map +1 -1
  50. package/dist/tools/tool-call-metadata.js +118 -6
  51. package/package.json +2 -2
@@ -0,0 +1,190 @@
1
+ import "../../chunk-C6A6W6XS.js";
2
+ import { APICallError } from "ai";
3
+ import { DextoRuntimeError } from "../../errors/DextoRuntimeError.js";
4
+ import { ErrorScope, ErrorType } from "../../errors/types.js";
5
+ import { LLMErrorCode } from "../error-codes.js";
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null;
8
+ }
9
+ function parseJsonObject(value) {
10
+ if (isRecord(value)) return value;
11
+ if (typeof value !== "string") return null;
12
+ try {
13
+ const parsed = JSON.parse(value);
14
+ return isRecord(parsed) ? parsed : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function readStringOrNumber(value) {
20
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
21
+ }
22
+ function readString(value) {
23
+ return typeof value === "string" && value.length > 0 ? value : void 0;
24
+ }
25
+ function readRetryAfter(headers) {
26
+ const retryAfter = headers?.["retry-after"];
27
+ if (retryAfter === void 0) return void 0;
28
+ const value = Number(retryAfter);
29
+ return Number.isFinite(value) ? value : void 0;
30
+ }
31
+ function stringifyBody(value) {
32
+ if (typeof value === "string") return value;
33
+ if (value === void 0 || value === null) return void 0;
34
+ try {
35
+ return JSON.stringify(value);
36
+ } catch {
37
+ return String(value);
38
+ }
39
+ }
40
+ function readErrorEnvelope(body) {
41
+ const error = body?.error;
42
+ return isRecord(error) ? error : null;
43
+ }
44
+ function addOpenRouterDetails(details, responseBody) {
45
+ const body = parseJsonObject(responseBody);
46
+ const errorEnvelope = readErrorEnvelope(body);
47
+ if (errorEnvelope === null) return;
48
+ const code = readStringOrNumber(errorEnvelope.code);
49
+ if (code !== void 0) details.openRouterErrorCode = code;
50
+ const message = readString(errorEnvelope.message);
51
+ if (message !== void 0) details.openRouterErrorMessage = message;
52
+ const metadata = isRecord(errorEnvelope.metadata) ? errorEnvelope.metadata : null;
53
+ if (metadata === null) return;
54
+ const providerName = readString(metadata.provider_name);
55
+ if (providerName !== void 0) details.openRouterProviderName = providerName;
56
+ if (Reflect.has(metadata, "raw")) {
57
+ details.openRouterProviderRaw = metadata.raw;
58
+ }
59
+ const rawObject = parseJsonObject(metadata.raw);
60
+ const rawError = readErrorEnvelope(rawObject);
61
+ const rawMessage = readString(rawError?.message);
62
+ if (rawMessage !== void 0) details.openRouterProviderRawMessage = rawMessage;
63
+ const rawParam = readString(rawError?.param);
64
+ if (rawParam !== void 0) details.openRouterProviderRawParam = rawParam;
65
+ const rawCode = readStringOrNumber(rawError?.code);
66
+ if (rawCode !== void 0) details.openRouterProviderRawCode = rawCode;
67
+ const previousErrors = metadata.previous_errors;
68
+ if (Array.isArray(previousErrors)) {
69
+ details.openRouterPreviousErrorCount = previousErrors.length;
70
+ }
71
+ }
72
+ function providerMessage(details, fallback) {
73
+ return details.openRouterProviderRawMessage ?? details.openRouterErrorMessage ?? details.responseBody ?? fallback;
74
+ }
75
+ function messageFromUnknown(value) {
76
+ return value instanceof Error ? value.message : String(value);
77
+ }
78
+ function isInvalidSchemaMessage(message) {
79
+ return message.includes("Invalid schema for function") || message.includes("invalid_function_parameters") || message.includes("schema must have type");
80
+ }
81
+ function readNumericField(value, field) {
82
+ if (!isRecord(value)) return void 0;
83
+ const fieldValue = value[field];
84
+ return typeof fieldValue === "number" && Number.isFinite(fieldValue) ? fieldValue : void 0;
85
+ }
86
+ function extractBalance(value) {
87
+ if (!isRecord(value)) return void 0;
88
+ const direct = readNumericField(value, "balance") ?? readNumericField(value, "balanceUsd") ?? readNumericField(value, "creditsUsd");
89
+ if (direct !== void 0) return direct;
90
+ for (const nested of Object.values(value)) {
91
+ const balance = extractBalance(nested);
92
+ if (balance !== void 0) return balance;
93
+ }
94
+ return void 0;
95
+ }
96
+ function errorTypeForStatus(status) {
97
+ if (status === 402) return ErrorType.PAYMENT_REQUIRED;
98
+ if (status === 403) return ErrorType.FORBIDDEN;
99
+ if (status === 408) return ErrorType.TIMEOUT;
100
+ if (status === 429) return ErrorType.RATE_LIMIT;
101
+ if (status !== void 0 && status >= 400 && status < 500) return ErrorType.USER;
102
+ return ErrorType.THIRD_PARTY;
103
+ }
104
+ function errorCodeForStatus(status, details) {
105
+ if (status === 402) return LLMErrorCode.INSUFFICIENT_CREDITS;
106
+ if (status === 429) return LLMErrorCode.RATE_LIMIT_EXCEEDED;
107
+ if (status === 400 && (details.openRouterProviderRawCode === "invalid_function_parameters" || isInvalidSchemaMessage(providerMessage(details, "")))) {
108
+ return LLMErrorCode.REQUEST_INVALID_SCHEMA;
109
+ }
110
+ return LLMErrorCode.GENERATION_FAILED;
111
+ }
112
+ function buildContext(input, details) {
113
+ return {
114
+ ...input.sessionId === void 0 ? {} : { sessionId: input.sessionId },
115
+ provider: input.provider,
116
+ model: input.model,
117
+ ...details
118
+ };
119
+ }
120
+ function extractProviderErrorDetails(input) {
121
+ const details = {
122
+ provider: input.provider,
123
+ model: input.model
124
+ };
125
+ if (!APICallError.isInstance?.(input.error)) return details;
126
+ const responseHeaders = input.error.responseHeaders || {};
127
+ const responseBody = stringifyBody(input.error.responseBody);
128
+ if (input.error.statusCode !== void 0) details.statusCode = input.error.statusCode;
129
+ const retryAfter = readRetryAfter(responseHeaders);
130
+ if (retryAfter !== void 0) details.retryAfter = retryAfter;
131
+ if (responseBody !== void 0) details.responseBody = responseBody;
132
+ if (input.error.url !== void 0) details.url = input.error.url;
133
+ details.isRetryable = input.error.isRetryable;
134
+ if (responseBody !== void 0) {
135
+ addOpenRouterDetails(details, responseBody);
136
+ }
137
+ return details;
138
+ }
139
+ function mapProviderError(input) {
140
+ if (input.error instanceof DextoRuntimeError) return input.error;
141
+ if (!APICallError.isInstance?.(input.error)) {
142
+ const message2 = messageFromUnknown(input.error);
143
+ if (isInvalidSchemaMessage(message2)) {
144
+ return new DextoRuntimeError(
145
+ LLMErrorCode.REQUEST_INVALID_SCHEMA,
146
+ ErrorScope.LLM,
147
+ ErrorType.USER,
148
+ message2,
149
+ buildContext(input, extractProviderErrorDetails(input))
150
+ );
151
+ }
152
+ return new DextoRuntimeError(
153
+ LLMErrorCode.GENERATION_FAILED,
154
+ ErrorScope.LLM,
155
+ ErrorType.THIRD_PARTY,
156
+ message2,
157
+ buildContext(input, extractProviderErrorDetails(input))
158
+ );
159
+ }
160
+ const details = extractProviderErrorDetails(input);
161
+ const status = input.error.statusCode;
162
+ const message = providerMessage(details, input.error.message);
163
+ const code = errorCodeForStatus(status, details);
164
+ const type = errorTypeForStatus(status);
165
+ if (status === 402) {
166
+ const balance = extractBalance(parseJsonObject(details.responseBody));
167
+ return new DextoRuntimeError(
168
+ code,
169
+ ErrorScope.LLM,
170
+ type,
171
+ `Insufficient Dexto credits. Balance: ${balance === void 0 ? "low" : `$${balance.toFixed(2)}`}`,
172
+ {
173
+ ...buildContext(input, details),
174
+ ...balance === void 0 ? {} : { balance }
175
+ },
176
+ "Run `dexto billing` to check your balance"
177
+ );
178
+ }
179
+ return new DextoRuntimeError(
180
+ code,
181
+ ErrorScope.LLM,
182
+ type,
183
+ status === void 0 ? message : `Provider error ${status}: ${message}`,
184
+ buildContext(input, details)
185
+ );
186
+ }
187
+ export {
188
+ extractProviderErrorDetails,
189
+ mapProviderError
190
+ };
@@ -23,6 +23,7 @@ __export(stream_processor_exports, {
23
23
  module.exports = __toCommonJS(stream_processor_exports);
24
24
  var import_types2 = require("../../logger/v2/types.js");
25
25
  var import_usage_metadata = require("../usage-metadata.js");
26
+ var import_provider_error = require("./provider-error.js");
26
27
  function finiteUsageCount(value) {
27
28
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
28
29
  }
@@ -34,13 +35,16 @@ class StreamProcessor {
34
35
  * @param config Provider/model configuration
35
36
  * @param logger Logger instance
36
37
  * @param streaming If true, emits llm:chunk events. Default true.
38
+ * @param emitFatalErrors If true, emits terminal llm:error events. TurnExecutor owns
39
+ * terminal run errors and disables this to avoid duplicate events.
37
40
  */
38
- constructor(contextManager, eventBus, abortSignal, config, logger, streaming = true) {
41
+ constructor(contextManager, eventBus, abortSignal, config, logger, streaming = true, emitFatalErrors = true) {
39
42
  this.contextManager = contextManager;
40
43
  this.eventBus = eventBus;
41
44
  this.abortSignal = abortSignal;
42
45
  this.config = config;
43
46
  this.streaming = streaming;
47
+ this.emitFatalErrors = emitFatalErrors;
44
48
  this.logger = logger.createChild(import_types2.DextoLogComponent.EXECUTOR);
45
49
  this.usageScopeId = config.usageScopeId;
46
50
  }
@@ -267,9 +271,13 @@ class StreamProcessor {
267
271
  break;
268
272
  }
269
273
  case "error": {
270
- const err = event.error instanceof Error ? event.error : new Error(String(event.error));
274
+ const err = (0, import_provider_error.mapProviderError)({
275
+ error: event.error,
276
+ provider: this.config.provider,
277
+ model: this.config.model
278
+ });
271
279
  await this.persistFailedToolResults(err.message);
272
- throw err;
280
+ throw event.error;
273
281
  }
274
282
  case "abort": {
275
283
  this.logger.debug("Stream aborted, emitting partial response");
@@ -325,8 +333,29 @@ class StreamProcessor {
325
333
  toolCalls: this.modelToolCalls
326
334
  };
327
335
  }
328
- this.logger.error("Stream processing failed", { error });
329
- throw error;
336
+ const mappedError = (0, import_provider_error.mapProviderError)({
337
+ error,
338
+ provider: this.config.provider,
339
+ model: this.config.model
340
+ });
341
+ if (!this.emitFatalErrors) {
342
+ this.logger.error("Stream processing failed", { error: mappedError });
343
+ throw error;
344
+ }
345
+ if (this.emitFatalErrors) {
346
+ this.eventBus.emit("llm:error", {
347
+ error: mappedError,
348
+ context: "StreamProcessor",
349
+ recoverable: false,
350
+ details: (0, import_provider_error.extractProviderErrorDetails)({
351
+ error,
352
+ provider: this.config.provider,
353
+ model: this.config.model
354
+ })
355
+ });
356
+ }
357
+ this.logger.error("Stream processing failed", { error: mappedError });
358
+ throw mappedError;
330
359
  }
331
360
  return {
332
361
  text: this.accumulatedText,
@@ -21,6 +21,7 @@ export declare class StreamProcessor {
21
21
  private abortSignal;
22
22
  private config;
23
23
  private streaming;
24
+ private emitFatalErrors;
24
25
  private assistantMessageId;
25
26
  private actualTokens;
26
27
  private finishReason;
@@ -44,8 +45,10 @@ export declare class StreamProcessor {
44
45
  * @param config Provider/model configuration
45
46
  * @param logger Logger instance
46
47
  * @param streaming If true, emits llm:chunk events. Default true.
48
+ * @param emitFatalErrors If true, emits terminal llm:error events. TurnExecutor owns
49
+ * terminal run errors and disables this to avoid duplicate events.
47
50
  */
48
- constructor(contextManager: ContextManager, eventBus: SessionEventBus, abortSignal: AbortSignal, config: StreamProcessorConfig, logger: Logger, streaming?: boolean);
51
+ constructor(contextManager: ContextManager, eventBus: SessionEventBus, abortSignal: AbortSignal, config: StreamProcessorConfig, logger: Logger, streaming?: boolean, emitFatalErrors?: boolean);
49
52
  process(streamFn: () => StreamTextResult<VercelToolSet, unknown>): Promise<StreamProcessorResult>;
50
53
  private getCacheTokensFromProviderMetadata;
51
54
  private normalizeUsage;
@@ -1 +1 @@
1
- {"version":3,"file":"stream-processor.d.ts","sourceRoot":"","sources":["../../../src/llm/executor/stream-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,IAAI,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAmB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAKvD,OAAO,KAAK,EAAE,WAAW,EAAoB,gBAAgB,EAAc,MAAM,YAAY,CAAC;AAgD9F,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,eAAe;IA2BpB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IAEd,OAAO,CAAC,SAAS;IA/BrB,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,YAAY,CAAmE;IACvF,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,iBAAiB,CAAsC;IAC/D,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,cAAc,CAAuB;IAC7C;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAAgD;IACxE,OAAO,CAAC,gBAAgB,CAAkE;IAE1F;;;;;;;OAOG;gBAES,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,qBAAqB,EACrC,MAAM,EAAE,MAAM,EACN,SAAS,GAAE,OAAc;IAM/B,OAAO,CACT,QAAQ,EAAE,MAAM,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,GACzD,OAAO,CAAC,qBAAqB,CAAC;IAiYjC,OAAO,CAAC,kCAAkC;IAoB1C,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,eAAe;YA8BT,gCAAgC;IA0B9C,OAAO,CAAC,sBAAsB;YAwChB,sBAAsB;YAKtB,gBAAgB;IAO9B;;;;OAIG;YACW,2BAA2B;IAQzC;;OAEG;YACW,wBAAwB;YAQxB,yBAAyB;CAuC1C"}
1
+ {"version":3,"file":"stream-processor.d.ts","sourceRoot":"","sources":["../../../src/llm/executor/stream-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,IAAI,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAmB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAKvD,OAAO,KAAK,EAAE,WAAW,EAAoB,gBAAgB,EAAc,MAAM,YAAY,CAAC;AAiD9F,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,eAAe;IA6BpB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IAEd,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,eAAe;IAlC3B,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,YAAY,CAAmE;IACvF,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,iBAAiB,CAAsC;IAC/D,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,cAAc,CAAuB;IAC7C;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAAgD;IACxE,OAAO,CAAC,gBAAgB,CAAkE;IAE1F;;;;;;;;;OASG;gBAES,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,qBAAqB,EACrC,MAAM,EAAE,MAAM,EACN,SAAS,GAAE,OAAc,EACzB,eAAe,GAAE,OAAc;IAMrC,OAAO,CACT,QAAQ,EAAE,MAAM,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,GACzD,OAAO,CAAC,qBAAqB,CAAC;IAwZjC,OAAO,CAAC,kCAAkC;IAoB1C,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,eAAe;YA8BT,gCAAgC;IA0B9C,OAAO,CAAC,sBAAsB;YAwChB,sBAAsB;YAKtB,gBAAgB;IAO9B;;;;OAIG;YACW,2BAA2B;IAQzC;;OAEG;YACW,wBAAwB;YAQxB,yBAAyB;CAuC1C"}
@@ -1,6 +1,7 @@
1
1
  import "../../chunk-C6A6W6XS.js";
2
2
  import { DextoLogComponent } from "../../logger/v2/types.js";
3
3
  import { getUsagePricingMetadata } from "../usage-metadata.js";
4
+ import { extractProviderErrorDetails, mapProviderError } from "./provider-error.js";
4
5
  function finiteUsageCount(value) {
5
6
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
6
7
  }
@@ -12,13 +13,16 @@ class StreamProcessor {
12
13
  * @param config Provider/model configuration
13
14
  * @param logger Logger instance
14
15
  * @param streaming If true, emits llm:chunk events. Default true.
16
+ * @param emitFatalErrors If true, emits terminal llm:error events. TurnExecutor owns
17
+ * terminal run errors and disables this to avoid duplicate events.
15
18
  */
16
- constructor(contextManager, eventBus, abortSignal, config, logger, streaming = true) {
19
+ constructor(contextManager, eventBus, abortSignal, config, logger, streaming = true, emitFatalErrors = true) {
17
20
  this.contextManager = contextManager;
18
21
  this.eventBus = eventBus;
19
22
  this.abortSignal = abortSignal;
20
23
  this.config = config;
21
24
  this.streaming = streaming;
25
+ this.emitFatalErrors = emitFatalErrors;
22
26
  this.logger = logger.createChild(DextoLogComponent.EXECUTOR);
23
27
  this.usageScopeId = config.usageScopeId;
24
28
  }
@@ -245,9 +249,13 @@ class StreamProcessor {
245
249
  break;
246
250
  }
247
251
  case "error": {
248
- const err = event.error instanceof Error ? event.error : new Error(String(event.error));
252
+ const err = mapProviderError({
253
+ error: event.error,
254
+ provider: this.config.provider,
255
+ model: this.config.model
256
+ });
249
257
  await this.persistFailedToolResults(err.message);
250
- throw err;
258
+ throw event.error;
251
259
  }
252
260
  case "abort": {
253
261
  this.logger.debug("Stream aborted, emitting partial response");
@@ -303,8 +311,29 @@ class StreamProcessor {
303
311
  toolCalls: this.modelToolCalls
304
312
  };
305
313
  }
306
- this.logger.error("Stream processing failed", { error });
307
- throw error;
314
+ const mappedError = mapProviderError({
315
+ error,
316
+ provider: this.config.provider,
317
+ model: this.config.model
318
+ });
319
+ if (!this.emitFatalErrors) {
320
+ this.logger.error("Stream processing failed", { error: mappedError });
321
+ throw error;
322
+ }
323
+ if (this.emitFatalErrors) {
324
+ this.eventBus.emit("llm:error", {
325
+ error: mappedError,
326
+ context: "StreamProcessor",
327
+ recoverable: false,
328
+ details: extractProviderErrorDetails({
329
+ error,
330
+ provider: this.config.provider,
331
+ model: this.config.model
332
+ })
333
+ });
334
+ }
335
+ this.logger.error("Stream processing failed", { error: mappedError });
336
+ throw mappedError;
308
337
  }
309
338
  return {
310
339
  text: this.accumulatedText,