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.
- package/CHANGELOG.md +22 -0
- package/README.md +7 -7
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +315 -119
- package/browser-runtime/ai/model-controller.mjs +439 -95
- package/package.json +1 -1
- package/runtime/arcane/components/assistant-panel.html +2 -1
- package/runtime/arcane/components/chat.html +469 -220
- package/runtime/arcane/components/speech.html +33 -13
- package/runtime/arcane/components/voice-transcription.html +27 -3
- package/runtime/arcane/entities/Chat.js +165 -97
- package/runtime/arcane/entities/IntentEnvelope.js +52 -118
- package/runtime/arcane/entities/TWiNPolicyDecision.js +33 -130
- package/runtime/arcane/entities/User.js +38 -44
- package/runtime/arcane/modules/AI.js +569 -302
- package/runtime/arcane/modules/AIProviderRuntime.js +776 -151
- package/runtime/arcane/modules/AIRuntimeState.js +22 -24
- package/runtime/arcane/modules/ApiModelDatabase.js +54 -48
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +6 -10
- package/runtime/arcane/modules/CommunicationHub.js +90 -94
- package/runtime/arcane/modules/ComponentContracts.js +23 -9
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +77 -77
- package/runtime/arcane/modules/ConversationTimebox.js +76 -104
- package/runtime/arcane/modules/DBLS.js +14 -12
- package/runtime/arcane/modules/DBOPFS.js +20 -15
- package/runtime/arcane/modules/Errors.js +196 -436
- package/runtime/arcane/modules/HTMLImport.js +49 -32
- package/runtime/arcane/modules/Ollama.js +16 -14
- package/runtime/arcane/modules/PersistentAIChatSession.js +174 -93
- package/runtime/arcane/modules/RecordReviewStore.js +40 -35
- package/runtime/arcane/modules/TerminalClient.js +12 -14
- package/runtime/arcane/modules/ThemeBootstrap.js +7 -7
- package/runtime/arcane/modules/ThemeManager.js +5 -5
- package/runtime/arcane/modules/TimeGuard.js +5 -65
- package/runtime/arcane/modules/WaitForComponent.js +43 -40
- 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
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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 (
|
|
815
|
+
if (pendingToolCallIds.size && calls.length) {
|
|
818
816
|
throw fail(
|
|
819
|
-
"
|
|
820
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
834
|
+
!pendingToolCallIds.size
|
|
837
835
|
|| typeof message.tool_call_id !== "string"
|
|
838
|
-
|| message.tool_call_id
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
878
|
-
return value !== undefined && value !==
|
|
875
|
+
if (parallelValues.some(function invalidParallelBrowserWasmPreference(value) {
|
|
876
|
+
return value !== undefined && typeof value !== "boolean";
|
|
879
877
|
})) {
|
|
880
|
-
throw
|
|
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
|
-
|
|
1046
|
+
validateToolCalls(
|
|
1062
1047
|
messageDescriptor.value,
|
|
1063
1048
|
`The model response choice ${String(choicePosition)}`,
|
|
1064
|
-
)
|
|
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
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
|
1091
|
-
|
|
1092
|
-
|
|
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 =
|
|
1097
|
-
if (projected !==
|
|
1138
|
+
const projected = projectPublicStreamData(item, seen);
|
|
1139
|
+
if (projected !== OMITTED_PUBLIC_STREAM_DATA) result.push(projected);
|
|
1098
1140
|
}
|
|
1099
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
-
|
|
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
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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 (
|
|
1168
|
-
record.
|
|
1169
|
-
|
|
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.
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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 ===
|
|
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
|
-
|
|
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
|
|
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(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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") {
|