@llumiverse/core 1.2.0 → 1.4.0-dev.20260629.090753Z

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 (145) hide show
  1. package/lib/CompletionStream.d.ts +48 -0
  2. package/lib/CompletionStream.d.ts.map +1 -0
  3. package/lib/{esm/CompletionStream.js → CompletionStream.js} +142 -67
  4. package/lib/CompletionStream.js.map +1 -0
  5. package/lib/{types/Driver.d.ts → Driver.d.ts} +38 -5
  6. package/lib/Driver.d.ts.map +1 -0
  7. package/lib/{esm/Driver.js → Driver.js} +152 -107
  8. package/lib/Driver.js.map +1 -0
  9. package/lib/{types/async.d.ts → async.d.ts} +3 -3
  10. package/lib/async.d.ts.map +1 -0
  11. package/lib/{esm/async.js → async.js} +4 -4
  12. package/lib/async.js.map +1 -0
  13. package/lib/{types/conversation-utils.d.ts → conversation-utils.d.ts} +9 -0
  14. package/lib/conversation-utils.d.ts.map +1 -0
  15. package/lib/{esm/conversation-utils.js → conversation-utils.js} +78 -15
  16. package/lib/conversation-utils.js.map +1 -0
  17. package/lib/embeddings.d.ts +58 -0
  18. package/lib/embeddings.d.ts.map +1 -0
  19. package/lib/embeddings.js +131 -0
  20. package/lib/embeddings.js.map +1 -0
  21. package/lib/formatters/commons.d.ts +3 -0
  22. package/lib/formatters/commons.d.ts.map +1 -0
  23. package/lib/formatters/commons.js +4 -0
  24. package/lib/formatters/commons.js.map +1 -0
  25. package/lib/{types/formatters → formatters}/generic.d.ts +2 -2
  26. package/lib/formatters/generic.d.ts.map +1 -0
  27. package/lib/{esm/formatters → formatters}/generic.js +8 -8
  28. package/lib/formatters/generic.js.map +1 -0
  29. package/lib/formatters/index.d.ts +4 -0
  30. package/lib/formatters/index.d.ts.map +1 -0
  31. package/lib/formatters/index.js +4 -0
  32. package/lib/formatters/index.js.map +1 -0
  33. package/lib/{types/formatters → formatters}/nova.d.ts +4 -4
  34. package/lib/formatters/nova.d.ts.map +1 -0
  35. package/lib/{esm/formatters → formatters}/nova.js +19 -17
  36. package/lib/formatters/nova.js.map +1 -0
  37. package/lib/http-agent.d.ts +53 -0
  38. package/lib/http-agent.d.ts.map +1 -0
  39. package/lib/http-agent.js +141 -0
  40. package/lib/http-agent.js.map +1 -0
  41. package/lib/index.d.ts +8 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +7 -0
  44. package/lib/index.js.map +1 -0
  45. package/lib/{types/json.d.ts → json.d.ts} +1 -1
  46. package/lib/json.d.ts.map +1 -0
  47. package/lib/{esm/json.js → json.js} +4 -3
  48. package/lib/json.js.map +1 -0
  49. package/lib/logger.d.ts +3 -0
  50. package/lib/logger.d.ts.map +1 -0
  51. package/lib/logger.js +42 -0
  52. package/lib/logger.js.map +1 -0
  53. package/lib/resolver.d.ts +2 -0
  54. package/lib/resolver.d.ts.map +1 -0
  55. package/lib/{esm/resolver.js → resolver.js} +7 -4
  56. package/lib/resolver.js.map +1 -0
  57. package/lib/stream.d.ts.map +1 -0
  58. package/lib/stream.js.map +1 -0
  59. package/lib/{types/validation.d.ts → validation.d.ts} +1 -1
  60. package/lib/validation.d.ts.map +1 -0
  61. package/lib/validation.js +100 -0
  62. package/lib/validation.js.map +1 -0
  63. package/package.json +29 -38
  64. package/src/CompletionStream.ts +208 -115
  65. package/src/Driver.error.test.ts +101 -72
  66. package/src/Driver.ts +208 -152
  67. package/src/async.ts +30 -30
  68. package/src/conversation-utils.ts +92 -16
  69. package/src/embeddings.ts +157 -0
  70. package/src/formatters/commons.ts +2 -2
  71. package/src/formatters/generic.ts +20 -17
  72. package/src/formatters/index.ts +3 -3
  73. package/src/formatters/nova.ts +53 -51
  74. package/src/http-agent.test.ts +281 -0
  75. package/src/http-agent.ts +177 -0
  76. package/src/index.ts +7 -5
  77. package/src/json.ts +6 -5
  78. package/src/logger.ts +42 -0
  79. package/src/resolver.ts +12 -9
  80. package/src/stream.ts +4 -5
  81. package/src/validation.ts +42 -29
  82. package/lib/cjs/CompletionStream.js +0 -293
  83. package/lib/cjs/CompletionStream.js.map +0 -1
  84. package/lib/cjs/Driver.js +0 -300
  85. package/lib/cjs/Driver.js.map +0 -1
  86. package/lib/cjs/async.js +0 -131
  87. package/lib/cjs/async.js.map +0 -1
  88. package/lib/cjs/conversation-utils.js +0 -579
  89. package/lib/cjs/conversation-utils.js.map +0 -1
  90. package/lib/cjs/formatters/commons.js +0 -7
  91. package/lib/cjs/formatters/commons.js.map +0 -1
  92. package/lib/cjs/formatters/generic.js +0 -59
  93. package/lib/cjs/formatters/generic.js.map +0 -1
  94. package/lib/cjs/formatters/index.js +0 -20
  95. package/lib/cjs/formatters/index.js.map +0 -1
  96. package/lib/cjs/formatters/nova.js +0 -99
  97. package/lib/cjs/formatters/nova.js.map +0 -1
  98. package/lib/cjs/index.js +0 -22
  99. package/lib/cjs/index.js.map +0 -1
  100. package/lib/cjs/json.js +0 -29
  101. package/lib/cjs/json.js.map +0 -1
  102. package/lib/cjs/package.json +0 -3
  103. package/lib/cjs/resolver.js +0 -45
  104. package/lib/cjs/resolver.js.map +0 -1
  105. package/lib/cjs/stream.js +0 -30
  106. package/lib/cjs/stream.js.map +0 -1
  107. package/lib/cjs/validation.js +0 -96
  108. package/lib/cjs/validation.js.map +0 -1
  109. package/lib/esm/CompletionStream.js.map +0 -1
  110. package/lib/esm/Driver.js.map +0 -1
  111. package/lib/esm/async.js.map +0 -1
  112. package/lib/esm/conversation-utils.js.map +0 -1
  113. package/lib/esm/formatters/commons.js +0 -4
  114. package/lib/esm/formatters/commons.js.map +0 -1
  115. package/lib/esm/formatters/generic.js.map +0 -1
  116. package/lib/esm/formatters/index.js +0 -4
  117. package/lib/esm/formatters/index.js.map +0 -1
  118. package/lib/esm/formatters/nova.js.map +0 -1
  119. package/lib/esm/index.js +0 -6
  120. package/lib/esm/index.js.map +0 -1
  121. package/lib/esm/json.js.map +0 -1
  122. package/lib/esm/resolver.js.map +0 -1
  123. package/lib/esm/stream.js.map +0 -1
  124. package/lib/esm/validation.js +0 -88
  125. package/lib/esm/validation.js.map +0 -1
  126. package/lib/types/CompletionStream.d.ts +0 -20
  127. package/lib/types/CompletionStream.d.ts.map +0 -1
  128. package/lib/types/Driver.d.ts.map +0 -1
  129. package/lib/types/async.d.ts.map +0 -1
  130. package/lib/types/conversation-utils.d.ts.map +0 -1
  131. package/lib/types/formatters/commons.d.ts +0 -3
  132. package/lib/types/formatters/commons.d.ts.map +0 -1
  133. package/lib/types/formatters/generic.d.ts.map +0 -1
  134. package/lib/types/formatters/index.d.ts +0 -4
  135. package/lib/types/formatters/index.d.ts.map +0 -1
  136. package/lib/types/formatters/nova.d.ts.map +0 -1
  137. package/lib/types/index.d.ts +0 -6
  138. package/lib/types/index.d.ts.map +0 -1
  139. package/lib/types/json.d.ts.map +0 -1
  140. package/lib/types/resolver.d.ts +0 -2
  141. package/lib/types/resolver.d.ts.map +0 -1
  142. package/lib/types/stream.d.ts.map +0 -1
  143. package/lib/types/validation.d.ts.map +0 -1
  144. /package/lib/{types/stream.d.ts → stream.d.ts} +0 -0
  145. /package/lib/{esm/stream.js → stream.js} +0 -0
@@ -1,22 +1,92 @@
1
1
  import {
2
- CompletionStream,
3
- DriverOptions,
4
- ExecutionOptions,
5
- ExecutionResponse,
6
- ExecutionTokenUsage,
7
- ToolUse,
8
- LlumiverseError
9
- } from "@llumiverse/common";
10
- import { AbstractDriver } from "./Driver.js";
2
+ type CompletionChunkObject,
3
+ type CompletionResult,
4
+ type CompletionStream,
5
+ type DriverOptions,
6
+ type ExecutionOptions,
7
+ type ExecutionResponse,
8
+ type ExecutionTokenUsage,
9
+ LlumiverseError,
10
+ type ToolUse,
11
+ } from '@llumiverse/common';
12
+ import type { AbstractDriver } from './Driver.js';
11
13
 
12
- export class DefaultCompletionStream<PromptT = any> implements CompletionStream<PromptT> {
14
+ type StreamingToolUse = ToolUse<unknown> & { _actual_id?: string };
13
15
 
16
+ /**
17
+ * Merge a single streamed `tool_use` fragment into the accumulator map keyed by tool id.
18
+ *
19
+ * Providers stream a tool call across several chunks. Different fields arrive in
20
+ * different chunks and must be reassembled:
21
+ * - `tool_input` arrives as string pieces (concatenated) or partial objects (merged).
22
+ * - `tool_name` and the provider's real id (`_actual_id`) may only appear in a later chunk.
23
+ * - `thought_signature` (Gemini 2.5+/3.x thinking models) is an opaque base64 byte string
24
+ * that must be passed back verbatim with the assistant turn on the next request.
25
+ *
26
+ * Before this helper existed, the inline accumulator merged `tool_input`/`tool_name`/`_actual_id`
27
+ * but silently dropped `thought_signature` for any chunk after the first for a given tool id.
28
+ * When a provider delivers the signature in pieces (large high-effort signatures are split
29
+ * across chunks) or only on a later chunk, the accumulated signature was left truncated or
30
+ * absent — producing an invalid base64 byte value that the provider rejects with a
31
+ * "Base64 decoding failed" (TYPE_BYTES) error when the assistant turn is threaded back.
32
+ *
33
+ * The signature is therefore reassembled by concatenating fragments in arrival order, exactly
34
+ * like `tool_input` string pieces. When the whole signature arrives in a single chunk there is
35
+ * only one fragment, so concatenation is a no-op and the value round-trips byte-identically.
36
+ *
37
+ * Exported for unit testing the round-trip in isolation (no network / no full driver).
38
+ */
39
+ export function accumulateToolUseChunk(
40
+ accumulatedToolUse: Map<string, StreamingToolUse>,
41
+ tool: StreamingToolUse,
42
+ ): void {
43
+ const existing = accumulatedToolUse.get(tool.id);
44
+ if (!existing) {
45
+ // New tool call
46
+ accumulatedToolUse.set(tool.id, { ...tool });
47
+ return;
48
+ }
49
+ // Merge tool input (for streaming where arguments come as string pieces)
50
+ if (tool.tool_input !== null && tool.tool_input !== undefined) {
51
+ const existingInput = existing.tool_input as unknown;
52
+ const newInput = tool.tool_input as unknown;
53
+ if (typeof existingInput === 'string' && typeof newInput === 'string') {
54
+ // Concatenate string arguments
55
+ existing.tool_input = (existingInput + newInput) as typeof existing.tool_input;
56
+ } else if (existingInput && typeof existingInput === 'object' && newInput && typeof newInput === 'object') {
57
+ // Merge objects
58
+ existing.tool_input = {
59
+ ...(existingInput as Record<string, unknown>),
60
+ ...(newInput as Record<string, unknown>),
61
+ } as typeof existing.tool_input;
62
+ } else {
63
+ existing.tool_input = tool.tool_input;
64
+ }
65
+ }
66
+ // Update tool name if provided (might come in later chunk)
67
+ if (tool.tool_name) {
68
+ existing.tool_name = tool.tool_name;
69
+ }
70
+ // Reassemble the thought signature: concatenate fragments in arrival order so a
71
+ // signature split across chunks is restored byte-for-byte instead of being truncated.
72
+ if (tool.thought_signature) {
73
+ existing.thought_signature = (existing.thought_signature ?? '') + tool.thought_signature;
74
+ }
75
+ // Update actual ID if provided (OpenAI sends id only in first chunk)
76
+ if (tool._actual_id) {
77
+ existing._actual_id = tool._actual_id;
78
+ }
79
+ }
80
+
81
+ export class DefaultCompletionStream<PromptT = unknown> implements CompletionStream<PromptT> {
14
82
  chunks: number; // Counter for number of chunks instead of storing strings
15
83
  completion: ExecutionResponse<PromptT> | undefined;
16
84
 
17
- constructor(public driver: AbstractDriver<DriverOptions, PromptT>,
85
+ constructor(
86
+ public driver: AbstractDriver<DriverOptions, PromptT>,
18
87
  public prompt: PromptT,
19
- public options: ExecutionOptions) {
88
+ public options: ExecutionOptions,
89
+ ) {
20
90
  this.chunks = 0;
21
91
  }
22
92
 
@@ -24,31 +94,43 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
24
94
  // reset state
25
95
  this.completion = undefined;
26
96
  this.chunks = 0;
27
- const accumulatedResults: any[] = []; // Accumulate CompletionResult[] from chunks
28
- const accumulatedToolUse: Map<string, ToolUse> = new Map(); // Accumulate tool_use by id
97
+ const accumulatedResults: CompletionResult[] = []; // Accumulate CompletionResult[] from chunks
98
+ const accumulatedToolUse: Map<string, StreamingToolUse> = new Map(); // Accumulate tool_use by id
29
99
 
30
- this.driver.logger.debug(
31
- `[${this.driver.provider}] Streaming Execution of ${this.options.model} with prompt`,
32
- );
100
+ this.driver.logger.debug(`[${this.driver.provider}] Streaming Execution of ${this.options.model} with prompt`);
33
101
 
34
102
  const start = Date.now();
35
- let finish_reason: string | undefined = undefined;
103
+ let finish_reason: string | undefined;
36
104
  let promptTokens: number = 0;
37
- let resultTokens: number | undefined = undefined;
38
- let promptCachedTokens: number | undefined = undefined;
39
- let promptCacheWriteTokens: number | undefined = undefined;
40
- let promptNewTokens: number | undefined = undefined;
105
+ let resultTokens: number | undefined;
106
+ let promptCachedTokens: number | undefined;
107
+ let promptCacheWriteTokens: number | undefined;
108
+ let promptNewTokens: number | undefined;
109
+ const httpScope = this.driver.createExecutionHttpAgentScope(this.options);
110
+ let sourceIterator: AsyncIterator<CompletionChunkObject> | undefined;
111
+ let streamCompleted = false;
41
112
 
42
113
  try {
43
- const stream = await this.driver.requestTextCompletionStream(this.prompt, this.options);
44
- for await (const chunk of stream) {
114
+ const stream = await httpScope.run(() =>
115
+ this.driver.requestTextCompletionStream(this.prompt, this.options),
116
+ );
117
+ const iterator = stream[Symbol.asyncIterator]();
118
+ sourceIterator = iterator;
119
+ while (true) {
120
+ const next = await httpScope.run(() => iterator.next());
121
+ if (next.done) {
122
+ streamCompleted = true;
123
+ break;
124
+ }
125
+ const chunk = next.value;
45
126
  if (chunk) {
46
127
  if (typeof chunk === 'string') {
47
128
  this.chunks++;
48
129
  yield chunk;
49
130
  } else {
50
- if (chunk.finish_reason) { //Do not replace non-null values with null values
51
- finish_reason = chunk.finish_reason; //Used to skip empty finish_reason chunks coming after "stop" or "length"
131
+ if (chunk.finish_reason) {
132
+ //Do not replace non-null values with null values
133
+ finish_reason = chunk.finish_reason; //Used to skip empty finish_reason chunks coming after "stop" or "length"
52
134
  }
53
135
  if (chunk.token_usage) {
54
136
  //Tokens returned include prior parts of stream,
@@ -56,42 +138,17 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
56
138
  //Math.max used as some models report final token count at beginning of stream
57
139
  promptTokens = Math.max(promptTokens, chunk.token_usage.prompt ?? 0);
58
140
  resultTokens = Math.max(resultTokens ?? 0, chunk.token_usage.result ?? 0);
59
- if (chunk.token_usage.prompt_cached != null) promptCachedTokens = chunk.token_usage.prompt_cached;
60
- if (chunk.token_usage.prompt_cache_write != null) promptCacheWriteTokens = chunk.token_usage.prompt_cache_write;
141
+ if (chunk.token_usage.prompt_cached != null)
142
+ promptCachedTokens = chunk.token_usage.prompt_cached;
143
+ if (chunk.token_usage.prompt_cache_write != null)
144
+ promptCacheWriteTokens = chunk.token_usage.prompt_cache_write;
61
145
  if (chunk.token_usage.prompt_new != null) promptNewTokens = chunk.token_usage.prompt_new;
62
146
  }
63
147
  // Accumulate tool_use from chunks
64
148
  // Note: During streaming, tool_input comes as string chunks that need concatenation
65
149
  if (chunk.tool_use && chunk.tool_use.length > 0) {
66
150
  for (const tool of chunk.tool_use) {
67
- const existing = accumulatedToolUse.get(tool.id);
68
- if (existing) {
69
- // Merge tool input (for streaming where arguments come as string pieces)
70
- if (tool.tool_input !== null && tool.tool_input !== undefined) {
71
- const existingInput = existing.tool_input as unknown;
72
- const newInput = tool.tool_input as unknown;
73
- if (typeof existingInput === 'string' && typeof newInput === 'string') {
74
- // Concatenate string arguments
75
- (existing as any).tool_input = existingInput + newInput;
76
- } else if (existingInput && typeof existingInput === 'object' && newInput && typeof newInput === 'object') {
77
- // Merge objects
78
- existing.tool_input = { ...(existingInput as object), ...(newInput as object) } as any;
79
- } else {
80
- existing.tool_input = tool.tool_input;
81
- }
82
- }
83
- // Update tool name if provided (might come in later chunk)
84
- if (tool.tool_name) {
85
- existing.tool_name = tool.tool_name;
86
- }
87
- // Update actual ID if provided (OpenAI sends id only in first chunk)
88
- if ((tool as any)._actual_id) {
89
- (existing as any)._actual_id = (tool as any)._actual_id;
90
- }
91
- } else {
92
- // New tool call
93
- accumulatedToolUse.set(tool.id, { ...tool });
94
- }
151
+ accumulateToolUseChunk(accumulatedToolUse, tool as StreamingToolUse);
95
152
  }
96
153
  }
97
154
  if (Array.isArray(chunk.result) && chunk.result.length > 0) {
@@ -100,9 +157,11 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
100
157
  // Check if we can combine with the last accumulated result
101
158
  const lastResult = accumulatedResults[accumulatedResults.length - 1];
102
159
 
103
- if (lastResult &&
160
+ if (
161
+ lastResult &&
104
162
  ((lastResult.type === 'text' && result.type === 'text') ||
105
- (lastResult.type === 'json' && result.type === 'json'))) {
163
+ (lastResult.type === 'json' && result.type === 'json'))
164
+ ) {
106
165
  // Combine consecutive text or JSON results
107
166
  if (result.type === 'text') {
108
167
  lastResult.value += result.value;
@@ -111,14 +170,24 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
111
170
  try {
112
171
  const lastParsed = lastResult.value;
113
172
  const currentParsed = result.value;
114
- if (lastParsed !== null && typeof lastParsed === 'object' &&
115
- currentParsed !== null && typeof currentParsed === 'object') {
173
+ if (
174
+ lastParsed !== null &&
175
+ typeof lastParsed === 'object' &&
176
+ currentParsed !== null &&
177
+ typeof currentParsed === 'object'
178
+ ) {
116
179
  const combined = { ...lastParsed, ...currentParsed };
117
180
  lastResult.value = combined;
118
181
  } else {
119
182
  // If not objects, convert to string and concatenate
120
- const lastStr = typeof lastParsed === 'string' ? lastParsed : JSON.stringify(lastParsed);
121
- const currentStr = typeof currentParsed === 'string' ? currentParsed : JSON.stringify(currentParsed);
183
+ const lastStr =
184
+ typeof lastParsed === 'string'
185
+ ? lastParsed
186
+ : JSON.stringify(lastParsed);
187
+ const currentStr =
188
+ typeof currentParsed === 'string'
189
+ ? currentParsed
190
+ : JSON.stringify(currentParsed);
122
191
  lastResult.value = lastStr + currentStr;
123
192
  }
124
193
  } catch {
@@ -134,20 +203,27 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
134
203
 
135
204
  // Convert CompletionResult[] to string for streaming
136
205
  // Only yield if we have results to show
137
- const resultText = chunk.result.map(r => {
138
- switch (r.type) {
139
- case 'text':
140
- return r.value;
141
- case 'json':
142
- return JSON.stringify(r.value);
143
- case 'image':
144
- // Show truncated image placeholder for streaming
145
- const truncatedValue = typeof r.value === 'string' ? r.value.slice(0, 10) : String(r.value).slice(0, 10);
146
- return `\n[Image: ${truncatedValue}...]\n`;
147
- default:
148
- return String((r as any).value || '');
149
- }
150
- }).join('');
206
+ const resultText = chunk.result
207
+ .map((r) => {
208
+ switch (r.type) {
209
+ case 'text':
210
+ return r.value;
211
+ case 'json':
212
+ return JSON.stringify(r.value);
213
+ case 'image': {
214
+ const truncatedValue =
215
+ typeof r.value === 'string'
216
+ ? r.value.slice(0, 10)
217
+ : String(r.value).slice(0, 10);
218
+ return `\n[Image: ${truncatedValue}...]\n`;
219
+ }
220
+ default: {
221
+ const _exhaustive: never = r;
222
+ return String(_exhaustive);
223
+ }
224
+ }
225
+ })
226
+ .join('');
151
227
 
152
228
  if (resultText) {
153
229
  this.chunks++;
@@ -157,7 +233,7 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
157
233
  }
158
234
  }
159
235
  }
160
- } catch (error: any) {
236
+ } catch (error: unknown) {
161
237
  // Don't wrap if already a LlumiverseError
162
238
  if (LlumiverseError.isLlumiverseError(error)) {
163
239
  throw error;
@@ -167,18 +243,31 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
167
243
  model: this.options.model,
168
244
  operation: 'stream',
169
245
  });
246
+ } finally {
247
+ if (!streamCompleted && sourceIterator?.return) {
248
+ const returnIterator = sourceIterator.return.bind(sourceIterator);
249
+ try {
250
+ await httpScope.run(() => returnIterator());
251
+ } catch {
252
+ /* stream cleanup best-effort */
253
+ }
254
+ }
255
+ await httpScope.close();
170
256
  }
171
257
 
172
258
  // Return undefined only if we never received any token data from the provider.
173
259
  // Use !== undefined (not truthiness) because resultTokens === 0 is valid (e.g. empty output with stop).
174
- const tokens: ExecutionTokenUsage | undefined = resultTokens !== undefined ? {
175
- prompt: promptTokens,
176
- result: resultTokens,
177
- total: resultTokens + promptTokens,
178
- ...(promptCachedTokens != null && { prompt_cached: promptCachedTokens }),
179
- ...(promptCacheWriteTokens != null && { prompt_cache_write: promptCacheWriteTokens }),
180
- ...(promptNewTokens != null && { prompt_new: promptNewTokens }),
181
- } : undefined
260
+ const tokens: ExecutionTokenUsage | undefined =
261
+ resultTokens !== undefined
262
+ ? {
263
+ prompt: promptTokens,
264
+ result: resultTokens,
265
+ total: resultTokens + promptTokens,
266
+ ...(promptCachedTokens != null && { prompt_cached: promptCachedTokens }),
267
+ ...(promptCacheWriteTokens != null && { prompt_cache_write: promptCacheWriteTokens }),
268
+ ...(promptNewTokens != null && { prompt_new: promptNewTokens }),
269
+ }
270
+ : undefined;
182
271
 
183
272
  // Convert accumulated tool_use Map to array
184
273
  let toolUseArray = accumulatedToolUse.size > 0 ? Array.from(accumulatedToolUse.values()) : undefined;
@@ -188,9 +277,9 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
188
277
  const truncatedToolIds = new Set<string>();
189
278
  for (const tool of toolUseArray) {
190
279
  // Restore actual ID from OpenAI (was stored in _actual_id during streaming)
191
- if ((tool as any)._actual_id) {
192
- tool.id = (tool as any)._actual_id;
193
- delete (tool as any)._actual_id;
280
+ if (tool._actual_id) {
281
+ tool.id = tool._actual_id;
282
+ delete tool._actual_id;
194
283
  }
195
284
  // Parse tool_input strings as JSON if needed (streaming sends arguments as string chunks)
196
285
  if (typeof tool.tool_input === 'string') {
@@ -208,7 +297,7 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
208
297
  // If finish_reason is "length" (max_tokens hit), drop truncated tool calls entirely —
209
298
  // they were cut off mid-generation and would produce invalid results.
210
299
  if (finish_reason === 'length' && truncatedToolIds.size > 0) {
211
- toolUseArray = toolUseArray.filter(t => !truncatedToolIds.has(t.id));
300
+ toolUseArray = toolUseArray.filter((t) => !truncatedToolIds.has(t.id));
212
301
  if (toolUseArray.length === 0) {
213
302
  toolUseArray = undefined;
214
303
  }
@@ -217,20 +306,20 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
217
306
 
218
307
  this.completion = {
219
308
  result: accumulatedResults, // Return the accumulated CompletionResult[] instead of text
220
- prompt: this.prompt,
309
+ prompt: this.driver.formatDebugPrompt(this.prompt),
221
310
  execution_time: Date.now() - start,
222
311
  token_usage: tokens,
223
312
  finish_reason: finish_reason,
224
313
  chunks: this.chunks,
225
314
  tool_use: toolUseArray,
226
- }
315
+ };
227
316
 
228
317
  // Build conversation context for multi-turn support
229
318
  const conversation = this.driver.buildStreamingConversation(
230
319
  this.prompt,
231
320
  accumulatedResults,
232
321
  toolUseArray,
233
- this.options
322
+ this.options,
234
323
  );
235
324
  if (conversation !== undefined) {
236
325
  this.completion.conversation = conversation;
@@ -240,7 +329,7 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
240
329
  if (this.completion) {
241
330
  this.driver.validateResult(this.completion, this.options);
242
331
  }
243
- } catch (error: any) {
332
+ } catch (error: unknown) {
244
333
  // Don't wrap if already a LlumiverseError
245
334
  if (LlumiverseError.isLlumiverseError(error)) {
246
335
  throw error;
@@ -252,44 +341,48 @@ export class DefaultCompletionStream<PromptT = any> implements CompletionStream<
252
341
  });
253
342
  }
254
343
  }
255
-
256
344
  }
257
345
 
258
- export class FallbackCompletionStream<PromptT = any> implements CompletionStream<PromptT> {
259
-
346
+ export class FallbackCompletionStream<PromptT = unknown> implements CompletionStream<PromptT> {
260
347
  completion: ExecutionResponse<PromptT> | undefined;
261
348
 
262
- constructor(public driver: AbstractDriver<DriverOptions, PromptT>,
349
+ constructor(
350
+ public driver: AbstractDriver<DriverOptions, PromptT>,
263
351
  public prompt: PromptT,
264
- public options: ExecutionOptions) {
265
- }
352
+ public options: ExecutionOptions,
353
+ ) {}
266
354
 
267
355
  async *[Symbol.asyncIterator]() {
268
356
  // reset state
269
357
  this.completion = undefined;
270
358
  this.driver.logger.debug(
271
- `[${this.driver.provider}] Streaming is not supported, falling back to blocking execution`
359
+ `[${this.driver.provider}] Streaming is not supported, falling back to blocking execution`,
272
360
  );
273
361
  try {
274
362
  const completion = await this.driver._execute(this.prompt, this.options);
275
363
  // For fallback streaming, yield the text content but keep the original completion
276
- const content = completion.result.map(r => {
277
- switch (r.type) {
278
- case 'text':
279
- return r.value;
280
- case 'json':
281
- return JSON.stringify(r.value);
282
- case 'image':
283
- // Show truncated image placeholder for streaming
284
- const truncatedValue = typeof r.value === 'string' ? r.value.slice(0, 10) : String(r.value).slice(0, 10);
285
- return `[Image: ${truncatedValue}...]`;
286
- default:
287
- return String((r as any).value || '');
288
- }
289
- }).join('');
364
+ const content = completion.result
365
+ .map((r) => {
366
+ switch (r.type) {
367
+ case 'text':
368
+ return r.value;
369
+ case 'json':
370
+ return JSON.stringify(r.value);
371
+ case 'image': {
372
+ const truncatedValue =
373
+ typeof r.value === 'string' ? r.value.slice(0, 10) : String(r.value).slice(0, 10);
374
+ return `[Image: ${truncatedValue}...]`;
375
+ }
376
+ default: {
377
+ const _exhaustive: never = r;
378
+ return String(_exhaustive);
379
+ }
380
+ }
381
+ })
382
+ .join('');
290
383
  yield content;
291
384
  this.completion = completion; // Return the original completion with untouched CompletionResult[]
292
- } catch (error: any) {
385
+ } catch (error: unknown) {
293
386
  // Don't wrap if already a LlumiverseError
294
387
  if (LlumiverseError.isLlumiverseError(error)) {
295
388
  throw error;