@llumiverse/core 1.3.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.
- package/lib/CompletionStream.d.ts +48 -0
- package/lib/CompletionStream.d.ts.map +1 -0
- package/lib/{esm/CompletionStream.js → CompletionStream.js} +142 -67
- package/lib/CompletionStream.js.map +1 -0
- package/lib/{types/Driver.d.ts → Driver.d.ts} +38 -5
- package/lib/Driver.d.ts.map +1 -0
- package/lib/{esm/Driver.js → Driver.js} +151 -106
- package/lib/Driver.js.map +1 -0
- package/lib/{types/async.d.ts → async.d.ts} +3 -3
- package/lib/async.d.ts.map +1 -0
- package/lib/{esm/async.js → async.js} +4 -4
- package/lib/async.js.map +1 -0
- package/lib/{types/conversation-utils.d.ts → conversation-utils.d.ts} +9 -0
- package/lib/conversation-utils.d.ts.map +1 -0
- package/lib/{esm/conversation-utils.js → conversation-utils.js} +78 -15
- package/lib/conversation-utils.js.map +1 -0
- package/lib/embeddings.d.ts +58 -0
- package/lib/embeddings.d.ts.map +1 -0
- package/lib/embeddings.js +131 -0
- package/lib/embeddings.js.map +1 -0
- package/lib/formatters/commons.d.ts +3 -0
- package/lib/formatters/commons.d.ts.map +1 -0
- package/lib/formatters/commons.js +4 -0
- package/lib/formatters/commons.js.map +1 -0
- package/lib/{types/formatters → formatters}/generic.d.ts +2 -2
- package/lib/formatters/generic.d.ts.map +1 -0
- package/lib/{esm/formatters → formatters}/generic.js +8 -8
- package/lib/formatters/generic.js.map +1 -0
- package/lib/formatters/index.d.ts +4 -0
- package/lib/formatters/index.d.ts.map +1 -0
- package/lib/formatters/index.js +4 -0
- package/lib/formatters/index.js.map +1 -0
- package/lib/{types/formatters → formatters}/nova.d.ts +4 -4
- package/lib/formatters/nova.d.ts.map +1 -0
- package/lib/{esm/formatters → formatters}/nova.js +19 -17
- package/lib/formatters/nova.js.map +1 -0
- package/lib/http-agent.d.ts +53 -0
- package/lib/http-agent.d.ts.map +1 -0
- package/lib/http-agent.js +141 -0
- package/lib/http-agent.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -0
- package/lib/{types/json.d.ts → json.d.ts} +1 -1
- package/lib/json.d.ts.map +1 -0
- package/lib/{esm/json.js → json.js} +4 -3
- package/lib/json.js.map +1 -0
- package/lib/logger.d.ts +3 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +42 -0
- package/lib/logger.js.map +1 -0
- package/lib/resolver.d.ts +2 -0
- package/lib/resolver.d.ts.map +1 -0
- package/lib/{esm/resolver.js → resolver.js} +7 -4
- package/lib/resolver.js.map +1 -0
- package/lib/stream.d.ts.map +1 -0
- package/lib/stream.js.map +1 -0
- package/lib/{types/validation.d.ts → validation.d.ts} +1 -1
- package/lib/validation.d.ts.map +1 -0
- package/lib/validation.js +100 -0
- package/lib/validation.js.map +1 -0
- package/package.json +28 -38
- package/src/CompletionStream.ts +208 -115
- package/src/Driver.error.test.ts +101 -72
- package/src/Driver.ts +208 -152
- package/src/async.ts +30 -30
- package/src/conversation-utils.ts +92 -16
- package/src/embeddings.ts +157 -0
- package/src/formatters/commons.ts +2 -2
- package/src/formatters/generic.ts +20 -17
- package/src/formatters/index.ts +3 -3
- package/src/formatters/nova.ts +53 -51
- package/src/http-agent.test.ts +281 -0
- package/src/http-agent.ts +177 -0
- package/src/index.ts +7 -5
- package/src/json.ts +6 -5
- package/src/logger.ts +42 -0
- package/src/resolver.ts +12 -9
- package/src/stream.ts +4 -5
- package/src/validation.ts +42 -29
- package/lib/cjs/CompletionStream.js +0 -293
- package/lib/cjs/CompletionStream.js.map +0 -1
- package/lib/cjs/Driver.js +0 -300
- package/lib/cjs/Driver.js.map +0 -1
- package/lib/cjs/async.js +0 -131
- package/lib/cjs/async.js.map +0 -1
- package/lib/cjs/conversation-utils.js +0 -579
- package/lib/cjs/conversation-utils.js.map +0 -1
- package/lib/cjs/formatters/commons.js +0 -7
- package/lib/cjs/formatters/commons.js.map +0 -1
- package/lib/cjs/formatters/generic.js +0 -59
- package/lib/cjs/formatters/generic.js.map +0 -1
- package/lib/cjs/formatters/index.js +0 -20
- package/lib/cjs/formatters/index.js.map +0 -1
- package/lib/cjs/formatters/nova.js +0 -99
- package/lib/cjs/formatters/nova.js.map +0 -1
- package/lib/cjs/index.js +0 -22
- package/lib/cjs/index.js.map +0 -1
- package/lib/cjs/json.js +0 -29
- package/lib/cjs/json.js.map +0 -1
- package/lib/cjs/package.json +0 -3
- package/lib/cjs/resolver.js +0 -45
- package/lib/cjs/resolver.js.map +0 -1
- package/lib/cjs/stream.js +0 -30
- package/lib/cjs/stream.js.map +0 -1
- package/lib/cjs/validation.js +0 -96
- package/lib/cjs/validation.js.map +0 -1
- package/lib/esm/CompletionStream.js.map +0 -1
- package/lib/esm/Driver.js.map +0 -1
- package/lib/esm/async.js.map +0 -1
- package/lib/esm/conversation-utils.js.map +0 -1
- package/lib/esm/formatters/commons.js +0 -4
- package/lib/esm/formatters/commons.js.map +0 -1
- package/lib/esm/formatters/generic.js.map +0 -1
- package/lib/esm/formatters/index.js +0 -4
- package/lib/esm/formatters/index.js.map +0 -1
- package/lib/esm/formatters/nova.js.map +0 -1
- package/lib/esm/index.js +0 -6
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/json.js.map +0 -1
- package/lib/esm/resolver.js.map +0 -1
- package/lib/esm/stream.js.map +0 -1
- package/lib/esm/validation.js +0 -88
- package/lib/esm/validation.js.map +0 -1
- package/lib/types/CompletionStream.d.ts +0 -20
- package/lib/types/CompletionStream.d.ts.map +0 -1
- package/lib/types/Driver.d.ts.map +0 -1
- package/lib/types/async.d.ts.map +0 -1
- package/lib/types/conversation-utils.d.ts.map +0 -1
- package/lib/types/formatters/commons.d.ts +0 -3
- package/lib/types/formatters/commons.d.ts.map +0 -1
- package/lib/types/formatters/generic.d.ts.map +0 -1
- package/lib/types/formatters/index.d.ts +0 -4
- package/lib/types/formatters/index.d.ts.map +0 -1
- package/lib/types/formatters/nova.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -6
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/json.d.ts.map +0 -1
- package/lib/types/resolver.d.ts +0 -2
- package/lib/types/resolver.d.ts.map +0 -1
- package/lib/types/stream.d.ts.map +0 -1
- package/lib/types/validation.d.ts.map +0 -1
- /package/lib/{types/stream.d.ts → stream.d.ts} +0 -0
- /package/lib/{esm/stream.js → stream.js} +0 -0
package/src/CompletionStream.ts
CHANGED
|
@@ -1,22 +1,92 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
28
|
-
const accumulatedToolUse: Map<string,
|
|
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
|
|
103
|
+
let finish_reason: string | undefined;
|
|
36
104
|
let promptTokens: number = 0;
|
|
37
|
-
let resultTokens: number | undefined
|
|
38
|
-
let promptCachedTokens: number | undefined
|
|
39
|
-
let promptCacheWriteTokens: number | undefined
|
|
40
|
-
let promptNewTokens: number | 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
|
|
44
|
-
|
|
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) {
|
|
51
|
-
|
|
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)
|
|
60
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
115
|
-
|
|
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 =
|
|
121
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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:
|
|
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 =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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 (
|
|
192
|
-
tool.id =
|
|
193
|
-
delete
|
|
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:
|
|
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 =
|
|
259
|
-
|
|
346
|
+
export class FallbackCompletionStream<PromptT = unknown> implements CompletionStream<PromptT> {
|
|
260
347
|
completion: ExecutionResponse<PromptT> | undefined;
|
|
261
348
|
|
|
262
|
-
constructor(
|
|
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
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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:
|
|
385
|
+
} catch (error: unknown) {
|
|
293
386
|
// Don't wrap if already a LlumiverseError
|
|
294
387
|
if (LlumiverseError.isLlumiverseError(error)) {
|
|
295
388
|
throw error;
|