narraeon 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +164 -0
  3. package/README.zh-CN.md +164 -0
  4. package/dist/node/cli/NarraeonCli.js +163 -0
  5. package/dist/node/cli/main.js +38 -0
  6. package/dist/node/protocol/appPreferences.js +5 -0
  7. package/dist/node/protocol/contentTree.js +14 -0
  8. package/dist/node/protocol/modelConnections.js +110 -0
  9. package/dist/node/protocol/playerViews.js +1 -0
  10. package/dist/node/protocol/v1.js +278 -0
  11. package/dist/node/runtime/V1Runtime.js +625 -0
  12. package/dist/node/runtime/artifact/FileNativeArtifactStore.js +1914 -0
  13. package/dist/node/runtime/config/AppPreferencesStore.js +92 -0
  14. package/dist/node/runtime/config/appPaths.js +19 -0
  15. package/dist/node/runtime/content/ContentLimits.js +1 -0
  16. package/dist/node/runtime/content/ContentTreeFile.js +1 -0
  17. package/dist/node/runtime/content/ContentWorkspace.js +601 -0
  18. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +721 -0
  19. package/dist/node/runtime/content/FileNativeContentTree.js +512 -0
  20. package/dist/node/runtime/extension/FrontendExtensionBundle.js +229 -0
  21. package/dist/node/runtime/extension/PlayerViewPanelProjector.js +121 -0
  22. package/dist/node/runtime/model/AiFailureLog.js +332 -0
  23. package/dist/node/runtime/model/FileNativeModelAdapters.js +1246 -0
  24. package/dist/node/runtime/model/ModelConnectionStore.js +378 -0
  25. package/dist/node/runtime/model/ModelHost.js +89 -0
  26. package/dist/node/runtime/model/ModelHostStream.js +367 -0
  27. package/dist/node/runtime/model/ProviderExchangeState.js +1 -0
  28. package/dist/node/runtime/model/ProviderStream.js +90 -0
  29. package/dist/node/runtime/model/SettingAuthorStream.js +118 -0
  30. package/dist/node/runtime/play/FileNativeContinuityCorrection.js +304 -0
  31. package/dist/node/runtime/play/FileNativePlayPresetStore.js +1856 -0
  32. package/dist/node/runtime/play/PlayCallChain.js +1280 -0
  33. package/dist/node/runtime/play/PlayDocumentTools.js +1121 -0
  34. package/dist/node/runtime/play/PlayFollowupRequests.js +183 -0
  35. package/dist/node/runtime/play/PlayPresetWorkbench.js +185 -0
  36. package/dist/node/runtime/prompt/FileNativePromptCompiler.js +1425 -0
  37. package/dist/node/runtime/prompt/FileNativeToolRegistry.js +192 -0
  38. package/dist/node/runtime/prompt/MaterialSelection.js +95 -0
  39. package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +1726 -0
  40. package/dist/node/runtime/world/FileNativeWorldStore.js +2028 -0
  41. package/dist/node/runtime/world/PlayerViewRenderer.js +322 -0
  42. package/dist/node/runtime/world/WorldDocumentStore.js +2808 -0
  43. package/dist/node/runtime/world/WorldOperationCoordinator.js +490 -0
  44. package/dist/node/runtime/world/WorldStateRevision.js +75 -0
  45. package/dist/node/server/createRuntime.js +6 -0
  46. package/dist/node/server/createServer.js +218 -0
  47. package/dist/node/server/main.js +25 -0
  48. package/dist/node/server/startWebServer.js +28 -0
  49. package/dist/node/shared/default-play-prompts.js +85 -0
  50. package/dist/node/shared/default-preset-host.js +538 -0
  51. package/dist/node/shared/default-setting-improvement-prompt.js +77 -0
  52. package/dist/node/shared/first-party-action-choices.js +164 -0
  53. package/dist/node/shared/first-party-generic-panels.js +105 -0
  54. package/dist/node/shared/first-party-play-preset-templates.js +41 -0
  55. package/dist/node/shared/first-party-player-view.js +179 -0
  56. package/dist/node/shared/play-preset-prompt-roles.js +4 -0
  57. package/dist/web/assets/index-CRGTZ9Ak.css +1 -0
  58. package/dist/web/assets/index-DDx1itzT.js +1192 -0
  59. package/dist/web/index.html +14 -0
  60. package/package.json +86 -0
@@ -0,0 +1,367 @@
1
+ import { ModelHostFailureError } from "./ModelHost.js";
2
+ import { providerStreamEvents } from "./ProviderStream.js";
3
+ export async function aggregateChatModelStream(body, onDelta) {
4
+ let content = "";
5
+ let reasoningContent = "";
6
+ let usage;
7
+ let completed = false;
8
+ let sawPayload = false;
9
+ const calls = new Map();
10
+ for await (const event of modelHostEvents(body)) {
11
+ if (event.data === "[DONE]") {
12
+ completed = true;
13
+ break;
14
+ }
15
+ const payload = streamRecord(event.data);
16
+ sawPayload = true;
17
+ if (isRecord(payload.error))
18
+ throw new ModelHostFailureError("Provider SSE returned an explicit error");
19
+ if (!Array.isArray(payload.choices))
20
+ throw new Error("Chat SSE choices are invalid");
21
+ if (payload.choices.length > 1)
22
+ throw new Error("Chat SSE returned multiple choices");
23
+ if (payload.usage !== undefined)
24
+ usage = payload.usage;
25
+ for (const choiceValue of payload.choices) {
26
+ if (!isRecord(choiceValue) || !isRecord(choiceValue.delta))
27
+ throw new Error("Chat SSE choice delta is invalid");
28
+ if (choiceValue.index !== undefined && choiceValue.index !== 0)
29
+ throw new Error("Chat SSE choice index is invalid");
30
+ if (choiceValue.finish_reason !== undefined &&
31
+ choiceValue.finish_reason !== null &&
32
+ typeof choiceValue.finish_reason !== "string")
33
+ throw new Error("Chat SSE finish_reason is invalid");
34
+ const delta = choiceValue.delta;
35
+ if (delta.role !== undefined && delta.role !== "assistant")
36
+ throw new Error("Chat SSE assistant role is invalid");
37
+ if (delta.content !== undefined &&
38
+ delta.content !== null &&
39
+ typeof delta.content !== "string")
40
+ throw new Error("Chat SSE content delta is invalid");
41
+ if (typeof delta.content === "string" && delta.content !== "") {
42
+ content += delta.content;
43
+ onDelta?.({ kind: "text", text: delta.content });
44
+ }
45
+ if (delta.reasoning_content !== undefined &&
46
+ delta.reasoning_content !== null &&
47
+ typeof delta.reasoning_content !== "string")
48
+ throw new Error("Chat SSE reasoning delta is invalid");
49
+ if (typeof delta.reasoning_content === "string" &&
50
+ delta.reasoning_content !== "") {
51
+ reasoningContent += delta.reasoning_content;
52
+ onDelta?.({ kind: "reasoning", text: delta.reasoning_content });
53
+ }
54
+ if (delta.tool_calls === undefined)
55
+ continue;
56
+ if (!Array.isArray(delta.tool_calls))
57
+ throw new Error("Chat SSE tool_calls delta is invalid");
58
+ for (const [position, fragmentValue] of delta.tool_calls.entries()) {
59
+ if (!isRecord(fragmentValue))
60
+ throw new Error("Chat SSE tool-call fragment is invalid");
61
+ const index = typeof fragmentValue.index === "number"
62
+ ? fragmentValue.index
63
+ : position;
64
+ if (!Number.isSafeInteger(index) || index < 0)
65
+ throw new Error("Chat SSE tool-call index is invalid");
66
+ const current = calls.get(index) ?? {
67
+ id: "",
68
+ name: "",
69
+ type: "function",
70
+ arguments: "",
71
+ };
72
+ if (fragmentValue.id !== undefined) {
73
+ if (typeof fragmentValue.id !== "string")
74
+ throw new Error("Chat SSE tool-call id is invalid");
75
+ current.id += fragmentValue.id;
76
+ }
77
+ if (fragmentValue.type !== undefined) {
78
+ if (fragmentValue.type !== "function")
79
+ throw new Error("Chat SSE tool-call type is invalid");
80
+ current.type = fragmentValue.type;
81
+ }
82
+ if (fragmentValue.function !== undefined) {
83
+ if (!isRecord(fragmentValue.function))
84
+ throw new Error("Chat SSE tool-call function is invalid");
85
+ const function_ = fragmentValue.function;
86
+ if (function_.name !== undefined) {
87
+ if (typeof function_.name !== "string")
88
+ throw new Error("Chat SSE tool-call name is invalid");
89
+ current.name += function_.name;
90
+ }
91
+ if (function_.arguments !== undefined) {
92
+ if (typeof function_.arguments !== "string")
93
+ throw new Error("Chat SSE tool-call arguments are invalid");
94
+ current.arguments += function_.arguments;
95
+ if (function_.arguments !== "")
96
+ onDelta?.({ kind: "tool", text: function_.arguments });
97
+ }
98
+ }
99
+ calls.set(index, current);
100
+ }
101
+ }
102
+ }
103
+ if (!completed || !sawPayload)
104
+ throw new Error("Chat SSE ended before a complete response");
105
+ const toolCalls = [...calls.entries()]
106
+ .sort(([left], [right]) => left - right)
107
+ .map(([, call]) => {
108
+ if (call.id === "" || call.name === "" || call.type !== "function")
109
+ throw new Error("Chat SSE tool call is incomplete");
110
+ return { id: call.id, name: call.name, arguments: call.arguments };
111
+ });
112
+ const assistantMessage = {
113
+ role: "assistant",
114
+ content: content === "" ? null : content,
115
+ ...(reasoningContent === "" ? {} : { reasoning_content: reasoningContent }),
116
+ ...(toolCalls.length === 0
117
+ ? {}
118
+ : {
119
+ tool_calls: toolCalls.map((call) => ({
120
+ id: call.id,
121
+ type: "function",
122
+ function: { name: call.name, arguments: call.arguments },
123
+ })),
124
+ }),
125
+ };
126
+ return {
127
+ content,
128
+ reasoningContent,
129
+ assistantMessage,
130
+ toolCalls,
131
+ usage,
132
+ };
133
+ }
134
+ export async function aggregateAnthropicModelStream(body, onDelta) {
135
+ const blocks = new Map();
136
+ const usage = {};
137
+ let started = false;
138
+ let stopped = false;
139
+ let responseId;
140
+ let model;
141
+ let stopReason;
142
+ stream: for await (const event of modelHostEvents(body)) {
143
+ const payload = streamRecord(event.data);
144
+ if (typeof payload.type !== "string")
145
+ throw new Error("Anthropic SSE type is invalid");
146
+ if (event.event !== null && event.event !== payload.type)
147
+ throw new Error("Anthropic SSE event and payload type do not match");
148
+ switch (payload.type) {
149
+ case "message_start": {
150
+ if (started || !isRecord(payload.message))
151
+ throw new Error("Anthropic SSE message_start is invalid");
152
+ if (payload.message.role !== "assistant" ||
153
+ !Array.isArray(payload.message.content))
154
+ throw new Error("Anthropic SSE initial message is invalid");
155
+ started = true;
156
+ responseId = stringOrUndefined(payload.message.id);
157
+ model = stringOrUndefined(payload.message.model);
158
+ mergeUsage(usage, payload.message.usage);
159
+ break;
160
+ }
161
+ case "content_block_start": {
162
+ const index = streamIndex(payload.index);
163
+ if (!isRecord(payload.content_block) || blocks.has(index))
164
+ throw new Error("Anthropic SSE content block start is invalid");
165
+ blocks.set(index, {
166
+ value: structuredClone(payload.content_block),
167
+ partialJson: "",
168
+ stopped: false,
169
+ });
170
+ break;
171
+ }
172
+ case "content_block_delta": {
173
+ const block = blocks.get(streamIndex(payload.index));
174
+ if (block === undefined || block.stopped || !isRecord(payload.delta))
175
+ throw new Error("Anthropic SSE content block delta is invalid");
176
+ const delta = payload.delta;
177
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
178
+ appendBlockString(block.value, "text", delta.text, "text");
179
+ if (delta.text !== "")
180
+ onDelta?.({ kind: "text", text: delta.text });
181
+ break;
182
+ }
183
+ if (delta.type === "thinking_delta" &&
184
+ typeof delta.thinking === "string") {
185
+ appendBlockString(block.value, "thinking", delta.thinking, "thinking");
186
+ if (delta.thinking !== "")
187
+ onDelta?.({ kind: "reasoning", text: delta.thinking });
188
+ break;
189
+ }
190
+ if (delta.type === "signature_delta" &&
191
+ typeof delta.signature === "string") {
192
+ appendBlockString(block.value, "signature", delta.signature, "thinking");
193
+ break;
194
+ }
195
+ if (delta.type === "input_json_delta" &&
196
+ typeof delta.partial_json === "string" &&
197
+ block.value.type === "tool_use") {
198
+ block.partialJson += delta.partial_json;
199
+ if (delta.partial_json !== "")
200
+ onDelta?.({ kind: "tool", text: delta.partial_json });
201
+ break;
202
+ }
203
+ throw new Error("Anthropic SSE delta type is not supported");
204
+ }
205
+ case "content_block_stop": {
206
+ const block = blocks.get(streamIndex(payload.index));
207
+ if (block === undefined || block.stopped)
208
+ throw new Error("Anthropic SSE content block stop is invalid");
209
+ block.stopped = true;
210
+ if (block.value.type === "tool_use" && block.partialJson !== "")
211
+ block.value.input = parseStreamJson(block.partialJson);
212
+ break;
213
+ }
214
+ case "message_delta": {
215
+ if (!isRecord(payload.delta))
216
+ throw new Error("Anthropic SSE message_delta is invalid");
217
+ if (payload.delta.stop_reason !== undefined &&
218
+ payload.delta.stop_reason !== null &&
219
+ typeof payload.delta.stop_reason !== "string")
220
+ throw new Error("Anthropic SSE stop_reason is invalid");
221
+ stopReason = payload.delta.stop_reason;
222
+ mergeUsage(usage, payload.usage);
223
+ break;
224
+ }
225
+ case "message_stop":
226
+ stopped = true;
227
+ break stream;
228
+ case "ping":
229
+ break;
230
+ case "error":
231
+ throw new ModelHostFailureError("Anthropic SSE returned an explicit error");
232
+ default:
233
+ // Anthropic may add top-level event types. Unknown events do not alter
234
+ // the content blocks that form the final continuation.
235
+ break;
236
+ }
237
+ }
238
+ if (!started || !stopped)
239
+ throw new Error("Anthropic SSE ended before a complete response");
240
+ const content = [...blocks.entries()]
241
+ .sort(([left], [right]) => left - right)
242
+ .map(([, block]) => {
243
+ if (!block.stopped)
244
+ throw new Error("Anthropic SSE content block did not end");
245
+ return block.value;
246
+ });
247
+ const text = content
248
+ .flatMap((block) => block.type === "text" && typeof block.text === "string"
249
+ ? [block.text]
250
+ : [])
251
+ .join("");
252
+ const reasoningContent = content
253
+ .flatMap((block) => block.type === "thinking" && typeof block.thinking === "string"
254
+ ? [block.thinking]
255
+ : [])
256
+ .join("");
257
+ const toolCalls = content.flatMap((block) => {
258
+ if (block.type !== "tool_use")
259
+ return [];
260
+ if (typeof block.id !== "string" ||
261
+ block.id === "" ||
262
+ typeof block.name !== "string" ||
263
+ block.name === "" ||
264
+ !isRecord(block.input))
265
+ throw new Error("Anthropic SSE tool call is incomplete");
266
+ return [{ id: block.id, name: block.name, arguments: block.input }];
267
+ });
268
+ return {
269
+ content,
270
+ text,
271
+ reasoningContent,
272
+ toolCalls,
273
+ usage,
274
+ ...(responseId === undefined ? {} : { responseId }),
275
+ ...(model === undefined ? {} : { model }),
276
+ ...(stopReason === undefined ? {} : { stopReason }),
277
+ };
278
+ }
279
+ export async function aggregateResponsesModelStream(body, onDelta) {
280
+ let completed = null;
281
+ for await (const event of modelHostEvents(body)) {
282
+ const payload = streamRecord(event.data);
283
+ if (typeof payload.type !== "string")
284
+ throw new Error("Responses SSE type is invalid");
285
+ if (event.event !== null && event.event !== payload.type)
286
+ throw new Error("Responses SSE event and payload type do not match");
287
+ if ((payload.type === "response.output_text.delta" ||
288
+ payload.type === "response.reasoning_text.delta" ||
289
+ payload.type === "response.reasoning_summary_text.delta" ||
290
+ payload.type === "response.function_call_arguments.delta") &&
291
+ typeof payload.delta !== "string")
292
+ throw new Error("Responses SSE delta is invalid");
293
+ if (payload.type === "response.output_text.delta" && payload.delta !== "")
294
+ onDelta?.({ kind: "text", text: payload.delta });
295
+ else if ((payload.type === "response.reasoning_text.delta" ||
296
+ payload.type === "response.reasoning_summary_text.delta") &&
297
+ payload.delta !== "")
298
+ onDelta?.({ kind: "reasoning", text: payload.delta });
299
+ else if (payload.type === "response.function_call_arguments.delta" &&
300
+ payload.delta !== "")
301
+ onDelta?.({ kind: "tool", text: payload.delta });
302
+ else if (payload.type === "response.completed") {
303
+ if (completed !== null || !isRecord(payload.response))
304
+ throw new Error("Responses SSE completed event is invalid");
305
+ if (payload.response.status !== undefined &&
306
+ payload.response.status !== "completed")
307
+ throw new Error("Responses SSE completed response status is invalid");
308
+ completed = structuredClone(payload.response);
309
+ break;
310
+ }
311
+ else if (payload.type === "response.failed" ||
312
+ payload.type === "response.incomplete" ||
313
+ payload.type === "response.cancelled" ||
314
+ payload.type === "error") {
315
+ throw new ModelHostFailureError("Responses SSE returned an explicit failure");
316
+ }
317
+ }
318
+ if (completed === null)
319
+ throw new Error("Responses SSE ended before response.completed");
320
+ return completed;
321
+ }
322
+ function modelHostEvents(body) {
323
+ return providerStreamEvents(body);
324
+ }
325
+ function streamRecord(data) {
326
+ const value = parseStreamJson(data);
327
+ if (!isRecord(value))
328
+ throw new Error("Provider SSE data is not an object");
329
+ return value;
330
+ }
331
+ function parseStreamJson(source) {
332
+ try {
333
+ return JSON.parse(source);
334
+ }
335
+ catch {
336
+ throw new Error("Provider SSE data is not valid JSON");
337
+ }
338
+ }
339
+ function streamIndex(value) {
340
+ if (!Number.isSafeInteger(value) || value < 0)
341
+ throw new Error("Provider SSE content index is invalid");
342
+ return value;
343
+ }
344
+ function appendBlockString(block, field, delta, expectedType) {
345
+ if (block.type !== expectedType || typeof block[field] !== "string")
346
+ throw new Error("Anthropic SSE content block does not match its delta");
347
+ block[field] += delta;
348
+ }
349
+ function mergeUsage(target, value) {
350
+ if (!isRecord(value))
351
+ return;
352
+ for (const field of [
353
+ "input_tokens",
354
+ "output_tokens",
355
+ "cache_read_input_tokens",
356
+ "cache_creation_input_tokens",
357
+ ]) {
358
+ if (value[field] !== undefined)
359
+ target[field] = value[field];
360
+ }
361
+ }
362
+ function stringOrUndefined(value) {
363
+ return typeof value === "string" ? value : undefined;
364
+ }
365
+ function isRecord(value) {
366
+ return typeof value === "object" && value !== null && !Array.isArray(value);
367
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ export async function* providerStreamEvents(body) {
2
+ const reader = body.getReader();
3
+ const decoder = new TextDecoder();
4
+ let pending = "";
5
+ let event = null;
6
+ let data = [];
7
+ let reachedEof = false;
8
+ const flush = () => {
9
+ if (data.length === 0) {
10
+ event = null;
11
+ return null;
12
+ }
13
+ const framed = { event, data: data.join("\n") };
14
+ event = null;
15
+ data = [];
16
+ return framed;
17
+ };
18
+ try {
19
+ for (;;) {
20
+ const { done, value } = await reader.read();
21
+ if (done)
22
+ reachedEof = true;
23
+ // `stream: true` keeps a split multi-byte character pending in the
24
+ // decoder instead of emitting a replacement character for each half.
25
+ pending += done
26
+ ? decoder.decode()
27
+ : decoder.decode(value, { stream: true });
28
+ let newline = pending.indexOf("\n");
29
+ while (newline >= 0) {
30
+ const line = pending.slice(0, newline).replace(/\r$/u, "");
31
+ pending = pending.slice(newline + 1);
32
+ if (line === "") {
33
+ const framed = flush();
34
+ if (framed !== null) {
35
+ yield framed;
36
+ }
37
+ }
38
+ else if (!line.startsWith(":")) {
39
+ const colon = line.indexOf(":");
40
+ const field = colon < 0 ? line : line.slice(0, colon);
41
+ const rawValue = colon < 0 ? "" : line.slice(colon + 1);
42
+ const value_ = rawValue.startsWith(" ")
43
+ ? rawValue.slice(1)
44
+ : rawValue;
45
+ if (field === "event")
46
+ event = value_;
47
+ else if (field === "data")
48
+ data.push(value_);
49
+ }
50
+ newline = pending.indexOf("\n");
51
+ }
52
+ if (done)
53
+ break;
54
+ }
55
+ // A stream that ends without a trailing blank line still carries an event.
56
+ if (pending.trim() !== "") {
57
+ const line = pending.replace(/\r$/u, "");
58
+ if (line.startsWith("data:")) {
59
+ const rawValue = line.slice(5);
60
+ data.push(rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue);
61
+ }
62
+ }
63
+ const framed = flush();
64
+ if (framed !== null)
65
+ yield framed;
66
+ }
67
+ finally {
68
+ if (!reachedEof) {
69
+ try {
70
+ await reader.cancel();
71
+ }
72
+ catch {
73
+ // The terminal Provider event already decides the result; a transport
74
+ // cancellation failure must not replace it.
75
+ }
76
+ }
77
+ reader.releaseLock();
78
+ }
79
+ }
80
+ /** Parses one `data` payload, skipping the `[DONE]` sentinel and junk. */
81
+ export function providerStreamJson(data) {
82
+ if (data === "" || data === "[DONE]")
83
+ return null;
84
+ try {
85
+ return JSON.parse(data);
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
@@ -0,0 +1,118 @@
1
+ import { providerStreamEvents, providerStreamJson } from "./ProviderStream.js";
2
+ export async function aggregateChatSettingStream(body, onDelta) {
3
+ let content = "";
4
+ let reasoningContent = "";
5
+ let usage = null;
6
+ // Tool calls arrive as fragments keyed by index; id and name usually only
7
+ // appear on the first fragment, while arguments accumulate across many.
8
+ const calls = new Map();
9
+ for await (const event of providerStreamEvents(body)) {
10
+ const payload = providerStreamJson(event.data);
11
+ if (!isRecord(payload))
12
+ continue;
13
+ if (isRecord(payload.usage))
14
+ usage = payload.usage;
15
+ const choices = Array.isArray(payload.choices)
16
+ ? payload.choices
17
+ : [];
18
+ const choice = choices[0];
19
+ if (!isRecord(choice) || !isRecord(choice.delta))
20
+ continue;
21
+ const { delta } = choice;
22
+ if (typeof delta.content === "string" && delta.content !== "") {
23
+ content += delta.content;
24
+ onDelta?.({ kind: "text", text: delta.content });
25
+ }
26
+ if (typeof delta.reasoning_content === "string" &&
27
+ delta.reasoning_content !== "") {
28
+ reasoningContent += delta.reasoning_content;
29
+ onDelta?.({ kind: "reasoning", text: delta.reasoning_content });
30
+ }
31
+ if (!Array.isArray(delta.tool_calls))
32
+ continue;
33
+ for (const [position, fragment] of delta.tool_calls.entries()) {
34
+ if (!isRecord(fragment))
35
+ continue;
36
+ const index = typeof fragment.index === "number" ? fragment.index : position;
37
+ const current = calls.get(index) ?? { id: "", name: "", arguments: "" };
38
+ if (typeof fragment.id === "string" && fragment.id !== "")
39
+ current.id = fragment.id;
40
+ const call = isRecord(fragment.function) ? fragment.function : {};
41
+ if (typeof call.name === "string" && call.name !== "")
42
+ current.name = call.name;
43
+ if (typeof call.arguments === "string" && call.arguments !== "") {
44
+ current.arguments += call.arguments;
45
+ onDelta?.({ kind: "tool", text: call.arguments });
46
+ }
47
+ calls.set(index, current);
48
+ }
49
+ }
50
+ return {
51
+ content,
52
+ reasoningContent,
53
+ toolCalls: [...calls.entries()]
54
+ .sort(([left], [right]) => left - right)
55
+ .map(([, call]) => call),
56
+ usage,
57
+ };
58
+ }
59
+ export async function aggregateAnthropicSettingStream(body, onDelta) {
60
+ let content = "";
61
+ let reasoningContent = "";
62
+ let inputTokens;
63
+ let outputTokens;
64
+ // Anthropic streams one block at a time, so the open block's index decides
65
+ // which accumulator a delta belongs to.
66
+ const blocks = new Map();
67
+ for await (const event of providerStreamEvents(body)) {
68
+ const payload = providerStreamJson(event.data);
69
+ if (!isRecord(payload))
70
+ continue;
71
+ if (isRecord(payload.message) && isRecord(payload.message.usage))
72
+ inputTokens = payload.message.usage.input_tokens;
73
+ if (isRecord(payload.usage) && payload.usage.output_tokens !== undefined)
74
+ outputTokens = payload.usage.output_tokens;
75
+ const index = typeof payload.index === "number" ? payload.index : -1;
76
+ if (payload.type === "content_block_start") {
77
+ const block = isRecord(payload.content_block)
78
+ ? payload.content_block
79
+ : {};
80
+ if (block.type === "tool_use")
81
+ blocks.set(index, {
82
+ id: typeof block.id === "string" ? block.id : "",
83
+ name: typeof block.name === "string" ? block.name : "",
84
+ arguments: "",
85
+ });
86
+ continue;
87
+ }
88
+ if (payload.type !== "content_block_delta" || !isRecord(payload.delta))
89
+ continue;
90
+ const { delta } = payload;
91
+ if (typeof delta.text === "string" && delta.text !== "") {
92
+ content += delta.text;
93
+ onDelta?.({ kind: "text", text: delta.text });
94
+ }
95
+ if (typeof delta.thinking === "string" && delta.thinking !== "") {
96
+ reasoningContent += delta.thinking;
97
+ onDelta?.({ kind: "reasoning", text: delta.thinking });
98
+ }
99
+ if (typeof delta.partial_json === "string" && delta.partial_json !== "") {
100
+ const current = blocks.get(index);
101
+ if (current !== undefined) {
102
+ current.arguments += delta.partial_json;
103
+ onDelta?.({ kind: "tool", text: delta.partial_json });
104
+ }
105
+ }
106
+ }
107
+ return {
108
+ content,
109
+ reasoningContent,
110
+ toolCalls: [...blocks.entries()]
111
+ .sort(([left], [right]) => left - right)
112
+ .map(([, call]) => call),
113
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
114
+ };
115
+ }
116
+ function isRecord(value) {
117
+ return typeof value === "object" && value !== null && !Array.isArray(value);
118
+ }