arcane-os 0.3.4 → 0.3.6

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 (35) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -7
  3. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +315 -119
  4. package/browser-runtime/ai/model-controller.mjs +439 -95
  5. package/package.json +1 -1
  6. package/runtime/arcane/components/assistant-panel.html +2 -1
  7. package/runtime/arcane/components/chat.html +469 -220
  8. package/runtime/arcane/components/speech.html +33 -13
  9. package/runtime/arcane/components/voice-transcription.html +27 -3
  10. package/runtime/arcane/entities/Chat.js +165 -97
  11. package/runtime/arcane/entities/IntentEnvelope.js +52 -118
  12. package/runtime/arcane/entities/TWiNPolicyDecision.js +33 -130
  13. package/runtime/arcane/entities/User.js +38 -44
  14. package/runtime/arcane/modules/AI.js +569 -302
  15. package/runtime/arcane/modules/AIProviderRuntime.js +776 -151
  16. package/runtime/arcane/modules/AIRuntimeState.js +22 -24
  17. package/runtime/arcane/modules/ApiModelDatabase.js +54 -48
  18. package/runtime/arcane/modules/CaseEvidenceIndexer.js +6 -10
  19. package/runtime/arcane/modules/CommunicationHub.js +90 -94
  20. package/runtime/arcane/modules/ComponentContracts.js +23 -9
  21. package/runtime/arcane/modules/ConfiguredAIChatSession.js +77 -77
  22. package/runtime/arcane/modules/ConversationTimebox.js +76 -104
  23. package/runtime/arcane/modules/DBLS.js +14 -12
  24. package/runtime/arcane/modules/DBOPFS.js +20 -15
  25. package/runtime/arcane/modules/Errors.js +196 -436
  26. package/runtime/arcane/modules/HTMLImport.js +49 -32
  27. package/runtime/arcane/modules/Ollama.js +16 -14
  28. package/runtime/arcane/modules/PersistentAIChatSession.js +174 -93
  29. package/runtime/arcane/modules/RecordReviewStore.js +40 -35
  30. package/runtime/arcane/modules/TerminalClient.js +12 -14
  31. package/runtime/arcane/modules/ThemeBootstrap.js +7 -7
  32. package/runtime/arcane/modules/ThemeManager.js +5 -5
  33. package/runtime/arcane/modules/TimeGuard.js +5 -65
  34. package/runtime/arcane/modules/WaitForComponent.js +43 -40
  35. package/src/installed-sdk-runtime.mjs +11 -1
@@ -339,10 +339,8 @@ export function createBrowserModelSource(descriptor, {
339
339
  open,
340
340
  };
341
341
  if (metadata.legacy) {
342
- Object.defineProperties(sourceRecord, {
343
- name: { value: metadata.files[0].name, enumerable: false },
344
- immutableUrl: { value: metadata.files[0].url, enumerable: false },
345
- });
342
+ sourceRecord.name = metadata.files[0].name;
343
+ sourceRecord.immutableUrl = metadata.files[0].url;
346
344
  }
347
345
  const source = completeValue(sourceRecord);
348
346
  BROWSER_MODEL_SOURCES.add(source);
@@ -799,7 +797,7 @@ function validateToolMessageSchemas(value) {
799
797
  }
800
798
 
801
799
  function validateRequestMessages(messages) {
802
- let pendingToolCallId = null;
800
+ const pendingToolCallIds = new Set();
803
801
  for (const [messageIndex, message] of messages.entries()) {
804
802
  if (!plainStructuralRecord(message)) {
805
803
  throw new TypeError(`messages[${String(messageIndex)}] must be a plain object.`);
@@ -814,14 +812,14 @@ function validateRequestMessages(messages) {
814
812
  `messages[${String(messageIndex)}].tool_calls is supported only for assistant messages.`,
815
813
  );
816
814
  }
817
- if (pendingToolCallId !== null && calls.length) {
815
+ if (pendingToolCallIds.size && calls.length) {
818
816
  throw fail(
819
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
820
- "The Arcane chat session accepts one structural tool call at a time.",
817
+ "ARCANE_AI_TOOL_RESULT_REQUIRED",
818
+ "Every pending structural tool result must be supplied before another assistant tool-call sequence.",
821
819
  );
822
820
  }
823
821
  if (calls.length) {
824
- pendingToolCallId = calls[0].id;
822
+ for (const call of calls) pendingToolCallIds.add(call.id);
825
823
  openedToolCall = true;
826
824
  }
827
825
  }
@@ -833,16 +831,16 @@ function validateRequestMessages(messages) {
833
831
  );
834
832
  }
835
833
  if (
836
- pendingToolCallId === null
834
+ !pendingToolCallIds.size
837
835
  || typeof message.tool_call_id !== "string"
838
- || message.tool_call_id !== pendingToolCallId
836
+ || !pendingToolCallIds.has(message.tool_call_id)
839
837
  ) {
840
838
  throw fail(
841
839
  "ARCANE_AI_INVALID_TOOL_MESSAGE",
842
840
  `messages[${String(messageIndex)}] does not settle the pending structural tool call.`,
843
841
  );
844
842
  }
845
- pendingToolCallId = null;
843
+ pendingToolCallIds.delete(message.tool_call_id);
846
844
  } else {
847
845
  if (Object.hasOwn(message, "tool_call_id")) {
848
846
  throw fail(
@@ -850,7 +848,7 @@ function validateRequestMessages(messages) {
850
848
  `messages[${String(messageIndex)}].tool_call_id is valid only for a tool result.`,
851
849
  );
852
850
  }
853
- if (pendingToolCallId !== null && !openedToolCall) {
851
+ if (pendingToolCallIds.size && !openedToolCall) {
854
852
  throw fail(
855
853
  "ARCANE_AI_TOOL_RESULT_REQUIRED",
856
854
  `messages[${String(messageIndex)}] precedes the pending structural tool result.`,
@@ -858,7 +856,7 @@ function validateRequestMessages(messages) {
858
856
  }
859
857
  }
860
858
  }
861
- if (pendingToolCallId !== null) {
859
+ if (pendingToolCallIds.size) {
862
860
  throw fail(
863
861
  "ARCANE_AI_TOOL_RESULT_REQUIRED",
864
862
  "The pending structural tool call must be settled before requesting another response.",
@@ -874,13 +872,10 @@ function validateStructuralRequest(request) {
874
872
  validateRequestMessages(request.messages);
875
873
  validateToolMessageSchemas(request.tools);
876
874
  const parallelValues = [request.parallelToolCalls, request.parallel_tool_calls];
877
- if (parallelValues.some(function enablesParallelBrowserWasmTools(value) {
878
- return value !== undefined && value !== false;
875
+ if (parallelValues.some(function invalidParallelBrowserWasmPreference(value) {
876
+ return value !== undefined && typeof value !== "boolean";
879
877
  })) {
880
- throw fail(
881
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
882
- "The Arcane chat session accepts one structural tool call at a time.",
883
- );
878
+ throw new TypeError("parallelToolCalls must be a boolean when provided.");
884
879
  }
885
880
  }
886
881
 
@@ -926,11 +921,6 @@ function completionOptions(request, abortSignal, stream) {
926
921
  if (request.tool_choice !== undefined) options.tool_choice = request.tool_choice;
927
922
  if (request.parallelToolCalls !== undefined) options.parallel_tool_calls = request.parallelToolCalls;
928
923
  if (request.parallel_tool_calls !== undefined) options.parallel_tool_calls = request.parallel_tool_calls;
929
- if (
930
- request.tools?.length
931
- && request.parallelToolCalls === undefined
932
- && request.parallel_tool_calls === undefined
933
- ) options.parallel_tool_calls = false;
934
924
  const format = responseFormat(request.structuredOutput);
935
925
  if (format) options.response_format = format;
936
926
  return options;
@@ -942,6 +932,8 @@ function validateToolCalls(message, location = "The model response") {
942
932
  }
943
933
  if (
944
934
  Object.hasOwn(message, "toolCalls")
935
+ || Object.hasOwn(message, "tool_call")
936
+ || Object.hasOwn(message, "toolCall")
945
937
  || Object.hasOwn(message, "function_call")
946
938
  || Object.hasOwn(message, "functionCall")
947
939
  ) {
@@ -953,12 +945,6 @@ function validateToolCalls(message, location = "The model response") {
953
945
  throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains malformed tool calls.`);
954
946
  }
955
947
  const calls = descriptor.value;
956
- if (calls.length > 1) {
957
- throw fail(
958
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
959
- "The Arcane chat session accepts one structural tool call at a time.",
960
- );
961
- }
962
948
  const ids = new Set();
963
949
  for (const call of calls) {
964
950
  if (
@@ -1039,7 +1025,6 @@ function validateCompletion(value, requestId) {
1039
1025
  }
1040
1026
  const choices = choicesDescriptor.value;
1041
1027
  const indexes = new Set();
1042
- let toolCallCount = 0;
1043
1028
  for (let choicePosition = 0; choicePosition < choices.length; choicePosition += 1) {
1044
1029
  const choice = choices[choicePosition];
1045
1030
  const messageDescriptor = plainStructuralRecord(choice)
@@ -1058,75 +1043,123 @@ function validateCompletion(value, requestId) {
1058
1043
  throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid choice index.");
1059
1044
  }
1060
1045
  indexes.add(choice.index);
1061
- const choiceToolCallCount = validateToolCalls(
1046
+ validateToolCalls(
1062
1047
  messageDescriptor.value,
1063
1048
  `The model response choice ${String(choicePosition)}`,
1064
- ).length;
1065
- if (choicePosition > 0 && choiceToolCallCount) {
1066
- throw fail(
1067
- "ARCANE_AI_INVALID_PROVIDER_RESULT",
1068
- "The model placed a structural tool call outside the selected first choice.",
1069
- );
1070
- }
1071
- toolCallCount += choiceToolCallCount;
1072
- if (toolCallCount > 1) {
1073
- throw fail(
1074
- "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
1075
- "The Arcane chat session accepts one structural tool call at a time.",
1076
- );
1077
- }
1049
+ );
1078
1050
  }
1079
1051
  return requestId === undefined ? value : completeValue({ ...value, id: requestId });
1080
1052
  }
1081
1053
 
1082
- function isPublicStreamContentKey(key) {
1083
- return key === "content"
1084
- || key === "text"
1085
- || key === "thinking"
1086
- || key === "reasoning"
1087
- || key === "reasoning_content";
1054
+ function selectedCompletionToolCalls(completion) {
1055
+ const message = Object.hasOwn(completion ?? {}, "message")
1056
+ ? completion.message
1057
+ : completion?.choices?.[0]?.message;
1058
+ return Array.isArray(message?.tool_calls) ? message.tool_calls : [];
1059
+ }
1060
+
1061
+ function sameCanonicalToolCalls(left, right) {
1062
+ return left.length === right.length && left.every((call, index) => {
1063
+ const other = right[index];
1064
+ return call?.id === other?.id
1065
+ && call?.type === other?.type
1066
+ && call?.function?.name === other?.function?.name
1067
+ && call?.function?.arguments === other?.function?.arguments;
1068
+ });
1069
+ }
1070
+
1071
+ function sameCompleteStreamValue(left, right, leftToRight = new Map(), rightToLeft = new Map()) {
1072
+ if (Object.is(left, right)) return true;
1073
+ if (
1074
+ !left
1075
+ || !right
1076
+ || typeof left !== "object"
1077
+ || typeof right !== "object"
1078
+ || Array.isArray(left) !== Array.isArray(right)
1079
+ ) return false;
1080
+ if (leftToRight.has(left) || rightToLeft.has(right)) {
1081
+ return leftToRight.get(left) === right && rightToLeft.get(right) === left;
1082
+ }
1083
+ leftToRight.set(left, right);
1084
+ rightToLeft.set(right, left);
1085
+ const leftKeys = Reflect.ownKeys(left);
1086
+ const rightKeys = Reflect.ownKeys(right);
1087
+ if (leftKeys.length !== rightKeys.length) return false;
1088
+ for (const key of leftKeys) {
1089
+ if (!Object.hasOwn(right, key)) return false;
1090
+ const leftDescriptor = Object.getOwnPropertyDescriptor(left, key);
1091
+ const rightDescriptor = Object.getOwnPropertyDescriptor(right, key);
1092
+ const leftIsData = Boolean(leftDescriptor && Object.hasOwn(leftDescriptor, "value"));
1093
+ const rightIsData = Boolean(rightDescriptor && Object.hasOwn(rightDescriptor, "value"));
1094
+ if (leftIsData !== rightIsData) return false;
1095
+ if (leftIsData) {
1096
+ if (!sameCompleteStreamValue(
1097
+ leftDescriptor.value,
1098
+ rightDescriptor.value,
1099
+ leftToRight,
1100
+ rightToLeft,
1101
+ )) return false;
1102
+ } else if (
1103
+ leftDescriptor?.get !== rightDescriptor?.get
1104
+ || leftDescriptor?.set !== rightDescriptor?.set
1105
+ ) return false;
1106
+ }
1107
+ return true;
1108
+ }
1109
+
1110
+ function completionToolCallsAt(completion, choiceIndex) {
1111
+ if (Object.hasOwn(completion ?? {}, "message")) {
1112
+ if (choiceIndex !== 0 || !Object.hasOwn(completion.message, "tool_calls")) return null;
1113
+ return completion.message.tool_calls;
1114
+ }
1115
+ const choice = completion?.choices?.find((item) => item?.index === choiceIndex);
1116
+ if (!choice || !Object.hasOwn(choice.message, "tool_calls")) return null;
1117
+ return choice.message.tool_calls;
1088
1118
  }
1089
1119
 
1090
- function projectPublicStreamContent(value, seen = new WeakSet()) {
1091
- if (!value || typeof value !== "object" || seen.has(value)) return null;
1092
- seen.add(value);
1120
+ function isPublicStreamStructuralKey(key) {
1121
+ return key === "tool_calls"
1122
+ || key === "toolCalls"
1123
+ || key === "tool_call"
1124
+ || key === "toolCall"
1125
+ || key === "function_call"
1126
+ || key === "functionCall";
1127
+ }
1128
+
1129
+ const OMITTED_PUBLIC_STREAM_DATA = Symbol("omitted-public-stream-data");
1130
+
1131
+ function projectPublicStreamData(value, seen = new Map()) {
1132
+ if (value === null || value === undefined || typeof value !== "object") return value;
1133
+ if (seen.has(value)) return seen.get(value);
1093
1134
  if (Array.isArray(value)) {
1094
1135
  const result = [];
1136
+ seen.set(value, result);
1095
1137
  for (const item of value) {
1096
- const projected = projectPublicStreamContent(item, seen);
1097
- if (projected !== null) result.push(projected);
1138
+ const projected = projectPublicStreamData(item, seen);
1139
+ if (projected !== OMITTED_PUBLIC_STREAM_DATA) result.push(projected);
1098
1140
  }
1099
- seen.delete(value);
1100
- return result.length ? result : null;
1101
- }
1102
- if (!plainStructuralRecord(value)) {
1103
- seen.delete(value);
1104
- return null;
1141
+ return result.length || value.length === 0 ? result : OMITTED_PUBLIC_STREAM_DATA;
1105
1142
  }
1106
1143
  const result = {};
1144
+ seen.set(value, result);
1145
+ let sourceDataFields = 0;
1107
1146
  const descriptors = Object.getOwnPropertyDescriptors(value);
1108
1147
  for (const key of Reflect.ownKeys(descriptors)) {
1109
1148
  if (typeof key === "symbol") continue;
1110
1149
  const descriptor = descriptors[key];
1111
1150
  if (!Object.hasOwn(descriptor, "value")) continue;
1112
- if (
1113
- isPublicStreamContentKey(key)
1114
- && descriptor.value !== null
1115
- && descriptor.value !== undefined
1116
- ) {
1117
- result[key] = descriptor.value;
1118
- continue;
1119
- }
1120
- const projected = projectPublicStreamContent(descriptor.value, seen);
1121
- if (projected !== null) result[key] = projected;
1122
- }
1123
- seen.delete(value);
1124
- return Object.keys(result).length ? result : null;
1151
+ sourceDataFields += 1;
1152
+ if (isPublicStreamStructuralKey(key)) continue;
1153
+ const projected = projectPublicStreamData(descriptor.value, seen);
1154
+ if (projected !== OMITTED_PUBLIC_STREAM_DATA) result[key] = projected;
1155
+ }
1156
+ return Object.keys(result).length || sourceDataFields === 0
1157
+ ? result
1158
+ : OMITTED_PUBLIC_STREAM_DATA;
1125
1159
  }
1126
1160
 
1127
1161
  function projectPublicStreamChunk(value) {
1128
- if (typeof value === "string") return value;
1129
- return projectPublicStreamContent(value);
1162
+ return projectPublicStreamData(value);
1130
1163
  }
1131
1164
 
1132
1165
  function createCompletionAccumulator(modelId, requestId) {
@@ -1146,7 +1179,12 @@ function createCompletionAccumulator(modelId, requestId) {
1146
1179
  sawContent: false,
1147
1180
  reasoning: "",
1148
1181
  sawReasoning: false,
1182
+ reasoningText: "",
1183
+ sawReasoningText: false,
1149
1184
  finish_reason: null,
1185
+ choiceMetadata: {},
1186
+ messageMetadata: {},
1187
+ completeToolCalls: null,
1150
1188
  tools: new Map(),
1151
1189
  });
1152
1190
  }
@@ -1158,15 +1196,54 @@ function createCompletionAccumulator(modelId, requestId) {
1158
1196
  base = { ...base, ...value, id: requestId ?? value.id ?? base.id, choices: [] };
1159
1197
  for (const item of Array.isArray(value.choices) ? value.choices : []) {
1160
1198
  const record = choice(item.index);
1161
- const delta = item.delta ?? {};
1162
- if (typeof delta.role === "string") record.role = delta.role;
1163
- if (typeof delta.content === "string") {
1164
- record.content += delta.content;
1165
- record.sawContent = true;
1199
+ for (const [key, fieldValue] of Object.entries(item)) {
1200
+ if (key !== "delta" && key !== "message") record.choiceMetadata[key] = fieldValue;
1201
+ }
1202
+ const delta = plainStructuralRecord(item.delta) ? item.delta : {};
1203
+ const completeMessage = plainStructuralRecord(item.message) ? item.message : null;
1204
+ for (const [source, replaceText] of [[delta, false], [completeMessage, true]]) {
1205
+ if (!source) continue;
1206
+ for (const [key, fieldValue] of Object.entries(source)) {
1207
+ if (isPublicStreamStructuralKey(key)) continue;
1208
+ if (key === "role" && typeof fieldValue === "string") {
1209
+ record.role = fieldValue;
1210
+ } else if (key === "content") {
1211
+ if (
1212
+ !replaceText
1213
+ && record.sawContent
1214
+ && typeof record.content === "string"
1215
+ && typeof fieldValue === "string"
1216
+ ) record.content += fieldValue;
1217
+ else record.content = fieldValue;
1218
+ record.sawContent = true;
1219
+ } else if (key === "reasoning_content") {
1220
+ if (
1221
+ !replaceText
1222
+ && record.sawReasoning
1223
+ && typeof record.reasoning === "string"
1224
+ && typeof fieldValue === "string"
1225
+ ) record.reasoning += fieldValue;
1226
+ else record.reasoning = fieldValue;
1227
+ record.sawReasoning = true;
1228
+ } else if (key === "reasoning") {
1229
+ if (
1230
+ !replaceText
1231
+ && record.sawReasoningText
1232
+ && typeof record.reasoningText === "string"
1233
+ && typeof fieldValue === "string"
1234
+ ) record.reasoningText += fieldValue;
1235
+ else record.reasoningText = fieldValue;
1236
+ record.sawReasoningText = true;
1237
+ } else {
1238
+ record.messageMetadata[key] = fieldValue;
1239
+ }
1240
+ }
1166
1241
  }
1167
- if (typeof delta.reasoning_content === "string") {
1168
- record.reasoning += delta.reasoning_content;
1169
- record.sawReasoning = true;
1242
+ if (completeMessage && Object.hasOwn(completeMessage, "tool_calls")) {
1243
+ record.completeToolCalls = validateToolCalls(
1244
+ completeMessage,
1245
+ `The model response choice ${String(item.index)} streamed message`,
1246
+ );
1170
1247
  }
1171
1248
  if (item.finish_reason !== undefined) record.finish_reason = item.finish_reason;
1172
1249
  if (delta.tool_calls !== undefined && !Array.isArray(delta.tool_calls)) {
@@ -1212,40 +1289,104 @@ function createCompletionAccumulator(modelId, requestId) {
1212
1289
  }
1213
1290
  }
1214
1291
 
1292
+ function fragmentToolCalls(record) {
1293
+ if (!record.tools.size) return null;
1294
+ const tools = [...record.tools.values()].sort((a, b) => a.index - b.index);
1295
+ for (let index = 0; index < tools.length; index += 1) {
1296
+ if (tools[index].index !== index) {
1297
+ throw fail(
1298
+ "ARCANE_AI_TOOL_CALL_INVALID",
1299
+ "The streamed structural tool calls omitted an ordered call index.",
1300
+ );
1301
+ }
1302
+ }
1303
+ return tools.map((tool) => {
1304
+ if (tool.invalidArguments || tool.invalidIdentity) {
1305
+ throw fail(
1306
+ "ARCANE_AI_TOOL_CALL_INVALID",
1307
+ "A streamed structural tool call changed or omitted an exact field.",
1308
+ );
1309
+ }
1310
+ return {
1311
+ id: tool.id,
1312
+ type: tool.type,
1313
+ function: { name: tool.name, arguments: tool.arguments },
1314
+ };
1315
+ });
1316
+ }
1317
+
1215
1318
  function result() {
1216
1319
  const completion = {
1217
1320
  ...base,
1218
1321
  object: "chat.completion",
1219
1322
  choices: [...choices.values()].sort((a, b) => a.index - b.index).map((record) => {
1220
1323
  const message = {
1324
+ ...record.messageMetadata,
1221
1325
  role: record.role,
1222
1326
  content: record.sawContent ? record.content : null,
1223
1327
  };
1224
1328
  if (record.sawReasoning) message.reasoning_content = record.reasoning;
1225
- if (record.tools.size) {
1226
- message.tool_calls = [...record.tools.values()]
1227
- .sort((a, b) => a.index - b.index)
1228
- .map((tool) => {
1229
- if (tool.invalidArguments || tool.invalidIdentity) {
1230
- throw fail(
1231
- "ARCANE_AI_TOOL_CALL_INVALID",
1232
- "A streamed structural tool call changed or omitted an exact field.",
1233
- );
1234
- }
1235
- return {
1236
- id: tool.id,
1237
- type: tool.type,
1238
- function: { name: tool.name, arguments: tool.arguments },
1239
- };
1240
- });
1329
+ if (record.sawReasoningText) message.reasoning = record.reasoningText;
1330
+ const fragmentCalls = fragmentToolCalls(record);
1331
+ if (
1332
+ record.completeToolCalls
1333
+ && fragmentCalls
1334
+ && !sameCanonicalToolCalls(fragmentCalls, record.completeToolCalls)
1335
+ ) {
1336
+ throw fail(
1337
+ "ARCANE_AI_TOOL_CALL_INVALID",
1338
+ "The streamed structural tool calls do not match the terminal message.",
1339
+ );
1340
+ }
1341
+ if (record.completeToolCalls) {
1342
+ message.tool_calls = record.completeToolCalls;
1343
+ } else if (fragmentCalls) {
1344
+ message.tool_calls = fragmentCalls;
1241
1345
  }
1242
- return { index: record.index, message, finish_reason: record.finish_reason };
1346
+ return {
1347
+ ...record.choiceMetadata,
1348
+ index: record.index,
1349
+ message,
1350
+ finish_reason: record.finish_reason,
1351
+ };
1243
1352
  }),
1244
1353
  };
1245
1354
  return validateCompletion(completion, requestId);
1246
1355
  }
1247
1356
 
1248
- return completeValue({ push, result });
1357
+ function hasToolCalls() {
1358
+ return [...choices.values()].some(
1359
+ (record) => record.completeToolCalls !== null || record.tools.size > 0,
1360
+ );
1361
+ }
1362
+
1363
+ function correlateToolCalls(completion) {
1364
+ for (const record of choices.values()) {
1365
+ const fragmentCalls = fragmentToolCalls(record);
1366
+ if (record.completeToolCalls === null && fragmentCalls === null) continue;
1367
+ const terminalCalls = completionToolCallsAt(completion, record.index);
1368
+ if (
1369
+ record.completeToolCalls !== null
1370
+ && !sameCompleteStreamValue(record.completeToolCalls, terminalCalls)
1371
+ ) {
1372
+ throw fail(
1373
+ "ARCANE_AI_TOOL_CALL_INVALID",
1374
+ "The v1 provider stream changed or omitted its terminal structural tool calls.",
1375
+ );
1376
+ }
1377
+ if (
1378
+ fragmentCalls !== null
1379
+ && (!Array.isArray(terminalCalls) || !sameCanonicalToolCalls(fragmentCalls, terminalCalls))
1380
+ ) {
1381
+ throw fail(
1382
+ "ARCANE_AI_TOOL_CALL_INVALID",
1383
+ "The v1 provider stream changed or omitted its terminal structural tool calls.",
1384
+ );
1385
+ }
1386
+ }
1387
+ }
1388
+
1389
+ return completeValue({ push, result, hasToolCalls, correlateToolCalls });
1249
1390
  }
1250
1391
 
1251
1392
  function callbackStreamHandle({ runtime, request, signal, onSettled }) {
@@ -1260,10 +1401,12 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1260
1401
  // This gate prevents delivery after public cancellation. It is not proof
1261
1402
  // that the underlying request stopped; the runtime records that separately.
1262
1403
  if (ended || linked.controller.signal.aborted) return;
1263
- const chunk = request.id === undefined ? value : { ...value, id: request.id };
1404
+ const chunk = request.id === undefined || !plainStructuralRecord(value)
1405
+ ? value
1406
+ : { ...value, id: request.id };
1264
1407
  accumulator.push(chunk);
1265
1408
  const publicChunk = projectPublicStreamChunk(chunk);
1266
- if (publicChunk === null) return;
1409
+ if (publicChunk === OMITTED_PUBLIC_STREAM_DATA) return;
1267
1410
  const waiter = waiters.shift();
1268
1411
  if (waiter) waiter.resolve({ value: publicChunk, done: false });
1269
1412
  else chunks.push(publicChunk);
@@ -1272,7 +1415,9 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1272
1415
  function finish(error = null) {
1273
1416
  ended = true;
1274
1417
  terminalError = error;
1275
- if (error) chunks.length = 0;
1418
+ while (waiters.length && chunks.length) {
1419
+ waiters.shift().resolve({ value: chunks.shift(), done: false });
1420
+ }
1276
1421
  while (waiters.length) {
1277
1422
  const waiter = waiters.shift();
1278
1423
  if (error) waiter.reject(error);
@@ -1328,9 +1473,9 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1328
1473
  return cancelPromise;
1329
1474
  },
1330
1475
  async next() {
1476
+ if (chunks.length) return { value: chunks.shift(), done: false };
1331
1477
  if (terminalError) throw terminalError;
1332
1478
  throwIfAborted(linked.controller.signal);
1333
- if (chunks.length) return { value: chunks.shift(), done: false };
1334
1479
  if (ended) return { value: undefined, done: true };
1335
1480
  return new Promise((resolve, reject) => waiters.push({ resolve, reject }));
1336
1481
  },
@@ -1396,26 +1541,77 @@ function validatedV1StreamHandle(opened, request) {
1396
1541
  "The browser-WASM adapter stream iterator has no next() method.",
1397
1542
  );
1398
1543
  }
1399
- const result = Promise.resolve(opened.result).then(
1544
+ const accumulator = createCompletionAccumulator(request.model ?? null, request.id);
1545
+ const publicChunks = [];
1546
+ const publicChunkWaiters = [];
1547
+ let publicStreamSettled = false;
1548
+ let publicStreamError = null;
1549
+
1550
+ function publishPublicChunk(value) {
1551
+ if (publicStreamSettled) return;
1552
+ const waiter = publicChunkWaiters.shift();
1553
+ if (waiter) waiter.resolve({ value, done: false });
1554
+ else publicChunks.push(value);
1555
+ }
1556
+
1557
+ function settlePublicStream(error = null) {
1558
+ if (publicStreamSettled) return;
1559
+ publicStreamSettled = true;
1560
+ publicStreamError = error;
1561
+ while (publicChunkWaiters.length && publicChunks.length) {
1562
+ publicChunkWaiters.shift().resolve({ value: publicChunks.shift(), done: false });
1563
+ }
1564
+ while (publicChunkWaiters.length) {
1565
+ const waiter = publicChunkWaiters.shift();
1566
+ if (error) waiter.reject(error);
1567
+ else waiter.resolve({ value: undefined, done: true });
1568
+ }
1569
+ }
1570
+
1571
+ const privateStreamPump = (async function pumpValidatedV1Stream() {
1572
+ try {
1573
+ while (true) {
1574
+ const next = await iterator.next();
1575
+ if (next.done) {
1576
+ settlePublicStream();
1577
+ return true;
1578
+ }
1579
+ accumulator.push(next.value);
1580
+ const projected = projectPublicStreamChunk(next.value);
1581
+ if (projected !== OMITTED_PUBLIC_STREAM_DATA) publishPublicChunk(projected);
1582
+ }
1583
+ } catch (error) {
1584
+ settlePublicStream(error);
1585
+ throw error;
1586
+ }
1587
+ })();
1588
+ privateStreamPump.catch(function retainV1PrivateStreamRejection() {});
1589
+
1590
+ const terminalResult = Promise.resolve(opened.result).then(
1400
1591
  function validateV1StreamTerminal(value) {
1401
1592
  return validateCompletion(value, request.id);
1402
1593
  },
1403
1594
  );
1595
+ terminalResult.catch(function retainV1StreamTerminalRejection() {});
1596
+ const result = Promise.all([terminalResult, privateStreamPump]).then(
1597
+ function correlateV1StreamTerminal([terminal]) {
1598
+ accumulator.correlateToolCalls(terminal);
1599
+ return terminal;
1600
+ },
1601
+ );
1404
1602
  result.catch(function retainV1StreamTerminalRejection() {});
1405
1603
  const handle = {
1406
1604
  result,
1407
1605
  cancel: function cancelValidatedV1Stream(reason) {
1408
1606
  return opened.cancel(reason);
1409
1607
  },
1410
- async next(value) {
1411
- let nextValue = value;
1412
- while (true) {
1413
- const next = await iterator.next(nextValue);
1414
- nextValue = undefined;
1415
- if (next.done) return { value: undefined, done: true };
1416
- const projected = projectPublicStreamChunk(next.value);
1417
- if (projected !== null) return { value: projected, done: false };
1418
- }
1608
+ async next() {
1609
+ if (publicChunks.length) return { value: publicChunks.shift(), done: false };
1610
+ if (publicStreamError) throw publicStreamError;
1611
+ if (publicStreamSettled) return { value: undefined, done: true };
1612
+ return new Promise(function waitForProjectedV1StreamChunk(resolve, reject) {
1613
+ publicChunkWaiters.push({ resolve, reject });
1614
+ });
1419
1615
  },
1420
1616
  async return(value) {
1421
1617
  if (typeof iterator.return === "function") {