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
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
margin: 0;
|
|
160
160
|
padding: clamp(1rem, 2vw, 1.5rem);
|
|
161
161
|
list-style: none;
|
|
162
|
-
overflow-x:
|
|
162
|
+
overflow-x: auto;
|
|
163
163
|
overflow-y: auto;
|
|
164
164
|
overscroll-behavior: contain;
|
|
165
165
|
scrollbar-gutter: stable;
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
clear: both;
|
|
182
182
|
border: 1px solid var(--arcane-border, var(--border-color));
|
|
183
183
|
border-radius: calc(var(--arcane-chat-radius) - .1rem);
|
|
184
|
-
overflow-x:
|
|
184
|
+
overflow-x: auto;
|
|
185
185
|
overflow-wrap: anywhere;
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
padding:.65rem;
|
|
239
239
|
border:1px solid currentColor;
|
|
240
240
|
border-radius:var(--arcane-radius-small,.3rem);
|
|
241
|
-
overflow-x:
|
|
241
|
+
overflow-x:auto;
|
|
242
242
|
overflow-wrap:anywhere;
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
max-inline-size:100%;
|
|
257
257
|
max-width:100%;
|
|
258
258
|
margin-top:.55rem;
|
|
259
|
-
overflow-x:
|
|
259
|
+
overflow-x:auto;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
.message_tool_details summary{
|
|
@@ -930,8 +930,9 @@
|
|
|
930
930
|
let activeSessionMessageToken=null;
|
|
931
931
|
let sessionBindingGeneration=0;
|
|
932
932
|
let sessionMessageSequence=0;
|
|
933
|
-
let
|
|
933
|
+
let pendingStructuralToolCalls=[];
|
|
934
934
|
let pendingStructuralToolMessage='';
|
|
935
|
+
let sessionHistoryRecoveryMessage='';
|
|
935
936
|
const activeSubmissionOwnerships=new Set();
|
|
936
937
|
const conversationTimeboxRetryDelays=[0,500,1_500];
|
|
937
938
|
const chatReasons={
|
|
@@ -1032,10 +1033,9 @@
|
|
|
1032
1033
|
}
|
|
1033
1034
|
|
|
1034
1035
|
function isPlainRecord(value){
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
&&Object.getPrototypeOf(value)===Object.prototype;
|
|
1036
|
+
if(!value||typeof value!=='object'||Array.isArray(value))return false;
|
|
1037
|
+
const prototype=Object.getPrototypeOf(value);
|
|
1038
|
+
return prototype===Object.prototype||prototype===null;
|
|
1039
1039
|
}
|
|
1040
1040
|
|
|
1041
1041
|
function visibleErrorMessage(error,fallback){
|
|
@@ -1122,6 +1122,7 @@
|
|
|
1122
1122
|
failure.code='AI_CHAT_INVALID_TOOL_CALL';
|
|
1123
1123
|
throw failure;
|
|
1124
1124
|
}
|
|
1125
|
+
const ids=new Set();
|
|
1125
1126
|
return value.map((call,index)=>{
|
|
1126
1127
|
if(
|
|
1127
1128
|
!call
|
|
@@ -1133,6 +1134,7 @@
|
|
|
1133
1134
|
||Array.isArray(call.function)
|
|
1134
1135
|
||typeof call.id!=='string'
|
|
1135
1136
|
||!call.id.trim()
|
|
1137
|
+
||ids.has(call.id)
|
|
1136
1138
|
||typeof call.function.name!=='string'
|
|
1137
1139
|
||!call.function.name.trim()
|
|
1138
1140
|
||typeof call.function.arguments!=='string'
|
|
@@ -1142,10 +1144,57 @@
|
|
|
1142
1144
|
throw failure;
|
|
1143
1145
|
}
|
|
1144
1146
|
structuralToolMessage(call);
|
|
1145
|
-
|
|
1147
|
+
ids.add(call.id);
|
|
1148
|
+
return {
|
|
1149
|
+
...call,
|
|
1150
|
+
function:{...call.function},
|
|
1151
|
+
id:call.id,
|
|
1152
|
+
type:'function'
|
|
1153
|
+
};
|
|
1146
1154
|
});
|
|
1147
1155
|
}
|
|
1148
1156
|
|
|
1157
|
+
function copyCompleteChatData(value,seen=new Map()){
|
|
1158
|
+
if(value===null||typeof value!=='object') return value;
|
|
1159
|
+
if(seen.has(value)) return seen.get(value);
|
|
1160
|
+
if(Array.isArray(value)){
|
|
1161
|
+
const result=[];
|
|
1162
|
+
seen.set(value,result);
|
|
1163
|
+
for(const item of value) result.push(copyCompleteChatData(item,seen));
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
const result={};
|
|
1167
|
+
seen.set(value,result);
|
|
1168
|
+
for(const [key,descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))){
|
|
1169
|
+
if(Object.hasOwn(descriptor,'value')){
|
|
1170
|
+
result[key]=copyCompleteChatData(descriptor.value,seen);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return result;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function sameCompleteChatData(left,right,seen=new Map()){
|
|
1177
|
+
if(Object.is(left,right)) return true;
|
|
1178
|
+
if(!left||!right||typeof left!=='object'||typeof right!=='object') return false;
|
|
1179
|
+
if(Array.isArray(left)!==Array.isArray(right)) return false;
|
|
1180
|
+
const matched=seen.get(left);
|
|
1181
|
+
if(matched!==undefined) return matched===right;
|
|
1182
|
+
seen.set(left,right);
|
|
1183
|
+
if(Array.isArray(left)){
|
|
1184
|
+
return left.length===right.length
|
|
1185
|
+
&&left.every((value,index)=>sameCompleteChatData(value,right[index],seen));
|
|
1186
|
+
}
|
|
1187
|
+
const leftKeys=Reflect.ownKeys(left).filter(
|
|
1188
|
+
key=>Object.prototype.propertyIsEnumerable.call(left,key)
|
|
1189
|
+
);
|
|
1190
|
+
const rightKeys=Reflect.ownKeys(right).filter(
|
|
1191
|
+
key=>Object.prototype.propertyIsEnumerable.call(right,key)
|
|
1192
|
+
);
|
|
1193
|
+
return leftKeys.length===rightKeys.length
|
|
1194
|
+
&&leftKeys.every(key=>Object.prototype.propertyIsEnumerable.call(right,key)
|
|
1195
|
+
&&sameCompleteChatData(left[key],right[key],seen));
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1149
1198
|
function structuralToolMessage(call){
|
|
1150
1199
|
let argumentsRecord;
|
|
1151
1200
|
try{
|
|
@@ -1175,40 +1224,50 @@
|
|
|
1175
1224
|
return argumentsRecord.message;
|
|
1176
1225
|
}
|
|
1177
1226
|
|
|
1178
|
-
function
|
|
1179
|
-
if(
|
|
1180
|
-
|
|
1227
|
+
function setPendingStructuralToolCalls(calls=[]){
|
|
1228
|
+
if(!Array.isArray(calls)||calls.length===0){
|
|
1229
|
+
pendingStructuralToolCalls=[];
|
|
1181
1230
|
pendingStructuralToolMessage='';
|
|
1182
|
-
return
|
|
1231
|
+
return [];
|
|
1183
1232
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
name:call.function.name,
|
|
1190
|
-
arguments:call.function.arguments
|
|
1191
|
-
}
|
|
1192
|
-
};
|
|
1193
|
-
return pendingStructuralToolCall;
|
|
1233
|
+
pendingStructuralToolCalls=normalizeVisibleToolCalls(calls);
|
|
1234
|
+
pendingStructuralToolMessage=pendingStructuralToolCalls
|
|
1235
|
+
.map(structuralToolMessage)
|
|
1236
|
+
.join(' · ');
|
|
1237
|
+
return pendingStructuralToolCalls;
|
|
1194
1238
|
}
|
|
1195
1239
|
|
|
1196
1240
|
function sameStructuralToolCall(left,right){
|
|
1197
|
-
return
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1241
|
+
return sameCompleteChatData(left,right);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function sameStructuralToolCalls(left,right){
|
|
1245
|
+
return Array.isArray(left)
|
|
1246
|
+
&&Array.isArray(right)
|
|
1247
|
+
&&left.length===right.length
|
|
1248
|
+
&&left.every((call,index)=>sameStructuralToolCall(call,right[index]));
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function pendingStructuralToolSummaries(){
|
|
1252
|
+
return pendingStructuralToolCalls.map(call=>({
|
|
1253
|
+
id:call.id,
|
|
1254
|
+
name:call.function.name,
|
|
1255
|
+
message:structuralToolMessage(call)
|
|
1256
|
+
}));
|
|
1202
1257
|
}
|
|
1203
1258
|
|
|
1204
1259
|
function pendingStructuralToolSummary(){
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1260
|
+
const summaries=pendingStructuralToolSummaries();
|
|
1261
|
+
return summaries.length===1?summaries[0]:null;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function pendingStructuralToolCallsComplete(){
|
|
1265
|
+
return pendingStructuralToolCalls.map(call=>copyCompleteChatData(call));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function pendingStructuralToolCallComplete(){
|
|
1269
|
+
const calls=pendingStructuralToolCallsComplete();
|
|
1270
|
+
return calls.length===1?calls[0]:null;
|
|
1212
1271
|
}
|
|
1213
1272
|
|
|
1214
1273
|
function appendVisibleToolCall(item,call){
|
|
@@ -1237,7 +1296,7 @@
|
|
|
1237
1296
|
summary.textContent='Tool call details';
|
|
1238
1297
|
const argumentsBlock=item.ownerDocument.createElement('pre');
|
|
1239
1298
|
const code=item.ownerDocument.createElement('code');
|
|
1240
|
-
code.textContent=call
|
|
1299
|
+
code.textContent=completeVisibleValue(call);
|
|
1241
1300
|
argumentsBlock.append(code);
|
|
1242
1301
|
details.append(summary,argumentsBlock);
|
|
1243
1302
|
entry.append(heading,userMessage,details);
|
|
@@ -1273,23 +1332,27 @@
|
|
|
1273
1332
|
return true;
|
|
1274
1333
|
}
|
|
1275
1334
|
|
|
1276
|
-
function
|
|
1335
|
+
function sameTranscriptRequest(actual,expected){
|
|
1336
|
+
return actual?.role===expected?.role
|
|
1337
|
+
&&actual.content===expected.content
|
|
1338
|
+
&&(
|
|
1339
|
+
expected.role!=='tool'
|
|
1340
|
+
||actual.tool_call_id===expected.tool_call_id
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function latestCommittedTranscriptTurn(transcript,requestMessages){
|
|
1277
1345
|
if(!Array.isArray(transcript)) return null;
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
|
|
1346
|
+
const expected=Array.isArray(requestMessages)
|
|
1347
|
+
?requestMessages
|
|
1348
|
+
:[requestMessages];
|
|
1349
|
+
if(!expected.length) return null;
|
|
1350
|
+
for(let index=transcript.length-1;index>=expected.length;index--){
|
|
1281
1351
|
const response=transcript[index];
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
&&request.content===requestContent
|
|
1287
|
-
&&(
|
|
1288
|
-
requestRole!=='tool'
|
|
1289
|
-
||request.tool_call_id===requestMessage.tool_call_id
|
|
1290
|
-
)
|
|
1291
|
-
){
|
|
1292
|
-
return {request,response};
|
|
1352
|
+
if(response?.role!=='assistant') continue;
|
|
1353
|
+
const requests=transcript.slice(index-expected.length,index);
|
|
1354
|
+
if(requests.every((request,offset)=>sameTranscriptRequest(request,expected[offset]))){
|
|
1355
|
+
return {request:requests.at(-1),requests,response};
|
|
1293
1356
|
}
|
|
1294
1357
|
}
|
|
1295
1358
|
return null;
|
|
@@ -1325,87 +1388,131 @@
|
|
|
1325
1388
|
return item;
|
|
1326
1389
|
}
|
|
1327
1390
|
|
|
1391
|
+
function completeVisibleValue(value){
|
|
1392
|
+
try{
|
|
1393
|
+
const serialized=JSON.stringify(value,null,2);
|
|
1394
|
+
if(serialized!==undefined) return serialized;
|
|
1395
|
+
}catch(error){
|
|
1396
|
+
console.error('Arcane saved chat record could not be serialized as JSON.',error);
|
|
1397
|
+
}
|
|
1398
|
+
return String(value);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function createSavedRecordFallback(message,index){
|
|
1402
|
+
const content=isPlainRecord(message)&&typeof message.content==='string'
|
|
1403
|
+
?message.content
|
|
1404
|
+
:'Saved record content is available in details.';
|
|
1405
|
+
const item=createTranscriptMessage(
|
|
1406
|
+
'assistant',
|
|
1407
|
+
content,
|
|
1408
|
+
`Saved record ${index+1}`,
|
|
1409
|
+
{timestamp:isPlainRecord(message)?message.timestamp:undefined}
|
|
1410
|
+
);
|
|
1411
|
+
const details=item.ownerDocument.createElement('details');
|
|
1412
|
+
details.className='message_tool_details';
|
|
1413
|
+
const summary=item.ownerDocument.createElement('summary');
|
|
1414
|
+
summary.textContent='Saved record details';
|
|
1415
|
+
const block=item.ownerDocument.createElement('pre');
|
|
1416
|
+
const code=item.ownerDocument.createElement('code');
|
|
1417
|
+
code.textContent=completeVisibleValue(message);
|
|
1418
|
+
block.append(code);
|
|
1419
|
+
details.append(summary,block);
|
|
1420
|
+
item.insertBefore(details,item.querySelector('.message_timestamp'));
|
|
1421
|
+
return item;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1328
1424
|
function renderSessionHistory(history){
|
|
1329
1425
|
if(!Array.isArray(history)){
|
|
1330
1426
|
throw new TypeError('Chat session history must be an array.');
|
|
1331
1427
|
}
|
|
1332
1428
|
const fragment=chatOutput.ownerDocument.createDocumentFragment();
|
|
1333
1429
|
const rendered=[];
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if(
|
|
1430
|
+
const restoredPendingToolCalls=new Map();
|
|
1431
|
+
let actionable=true;
|
|
1432
|
+
let recoveryNeeded=false;
|
|
1433
|
+
for(const [index,message] of history.entries()){
|
|
1434
|
+
let item;
|
|
1435
|
+
try{
|
|
1436
|
+
if(!isPlainRecord(message)||!['assistant','system','tool','user'].includes(message.role)){
|
|
1341
1437
|
throw chatError(
|
|
1342
|
-
'Saved chat contains
|
|
1438
|
+
'Saved chat contains an unsupported record.',
|
|
1343
1439
|
'AI_CHAT_INCOHERENT_PERSISTENCE'
|
|
1344
1440
|
);
|
|
1345
1441
|
}
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1348
|
-
if(!['assistant','tool','user'].includes(message.role)){
|
|
1349
|
-
throw new TypeError(`Chat session history contains an unsupported role: ${message.role}.`);
|
|
1350
|
-
}
|
|
1351
|
-
if(typeof message.content!=='string'){
|
|
1352
|
-
throw new TypeError('Chat session history message content must be a string.');
|
|
1353
|
-
}
|
|
1354
|
-
if(message.role==='tool'&&!message.content.trim()){
|
|
1355
|
-
throw chatError(
|
|
1356
|
-
'Saved chat contains a blank structural tool result.',
|
|
1357
|
-
'AI_CHAT_INVALID_TOOL_MESSAGE'
|
|
1358
|
-
);
|
|
1359
|
-
}
|
|
1360
|
-
const calls=normalizeVisibleToolCalls(message.tool_calls);
|
|
1361
|
-
if(calls.length>1){
|
|
1362
|
-
throw chatError(
|
|
1363
|
-
'Saved chat contains parallel structural tool calls.',
|
|
1364
|
-
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED'
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
if(message.role!=='assistant'&&calls.length){
|
|
1368
|
-
throw chatError(
|
|
1369
|
-
'Only assistant records may contain structural tool calls.',
|
|
1370
|
-
'AI_CHAT_INVALID_TOOL_CALL'
|
|
1371
|
-
);
|
|
1372
|
-
}
|
|
1373
|
-
if(message.role==='tool'){
|
|
1374
|
-
if(
|
|
1375
|
-
!restoredPendingToolCall
|
|
1376
|
-
||typeof message.tool_call_id!=='string'
|
|
1377
|
-
||message.tool_call_id!==restoredPendingToolCall.id
|
|
1378
|
-
){
|
|
1442
|
+
if(typeof message.content!=='string'){
|
|
1379
1443
|
throw chatError(
|
|
1380
|
-
'Saved chat contains a
|
|
1381
|
-
'
|
|
1444
|
+
'Saved chat contains a record without text content.',
|
|
1445
|
+
'AI_CHAT_INCOHERENT_PERSISTENCE'
|
|
1382
1446
|
);
|
|
1383
1447
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1448
|
+
const calls=normalizeVisibleToolCalls(message.tool_calls);
|
|
1449
|
+
if(message.role!=='assistant'&&calls.length){
|
|
1450
|
+
throw chatError(
|
|
1451
|
+
'Only assistant records may contain structural tool calls.',
|
|
1452
|
+
'AI_CHAT_INVALID_TOOL_CALL'
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
if(message.role!=='tool'&&message.tool_call_id!==undefined){
|
|
1387
1456
|
throw chatError(
|
|
1388
|
-
'
|
|
1389
|
-
'
|
|
1457
|
+
'Only tool records may contain a structural tool-call ID.',
|
|
1458
|
+
'AI_CHAT_INVALID_TOOL_MESSAGE'
|
|
1390
1459
|
);
|
|
1391
1460
|
}
|
|
1392
|
-
if(
|
|
1393
|
-
|
|
1461
|
+
if(actionable){
|
|
1462
|
+
if(message.role==='tool'){
|
|
1463
|
+
if(
|
|
1464
|
+
typeof message.tool_call_id!=='string'
|
|
1465
|
+
||!message.tool_call_id.trim()
|
|
1466
|
+
||!message.content.trim()
|
|
1467
|
+
||!restoredPendingToolCalls.has(message.tool_call_id)
|
|
1468
|
+
){
|
|
1469
|
+
throw chatError(
|
|
1470
|
+
'Saved chat contains a tool result that does not match a pending structural tool call.',
|
|
1471
|
+
'AI_CHAT_INVALID_TOOL_MESSAGE'
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
restoredPendingToolCalls.delete(message.tool_call_id);
|
|
1475
|
+
}else{
|
|
1476
|
+
if(restoredPendingToolCalls.size){
|
|
1477
|
+
throw chatError(
|
|
1478
|
+
'Saved chat continues before all pending structural tool results.',
|
|
1479
|
+
'AI_CHAT_TOOL_RESULT_REQUIRED'
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
if(calls.length){
|
|
1483
|
+
for(const call of calls){
|
|
1484
|
+
restoredPendingToolCalls.set(call.id,call);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1394
1488
|
}
|
|
1489
|
+
const name=message.role==='user'
|
|
1490
|
+
?host.name
|
|
1491
|
+
:message.role==='tool'
|
|
1492
|
+
?'Tool'
|
|
1493
|
+
:message.role==='system'
|
|
1494
|
+
?'System'
|
|
1495
|
+
:host.aiName;
|
|
1496
|
+
item=createTranscriptMessage(message.role,message.content,name,{
|
|
1497
|
+
timestamp:message.timestamp,
|
|
1498
|
+
toolCalls:calls,
|
|
1499
|
+
});
|
|
1500
|
+
}catch(error){
|
|
1501
|
+
recoveryNeeded=true;
|
|
1502
|
+
actionable=false;
|
|
1503
|
+
restoredPendingToolCalls.clear();
|
|
1504
|
+
console.error(`Arcane saved chat record ${index+1} is not actionable.`,error);
|
|
1505
|
+
item=createSavedRecordFallback(message,index);
|
|
1395
1506
|
}
|
|
1396
|
-
const name=message.role==='user'
|
|
1397
|
-
?host.name
|
|
1398
|
-
:message.role==='tool'
|
|
1399
|
-
?'Tool'
|
|
1400
|
-
:host.aiName;
|
|
1401
|
-
const item=createTranscriptMessage(message.role,message.content,name,{
|
|
1402
|
-
timestamp:message.timestamp,
|
|
1403
|
-
toolCalls:calls,
|
|
1404
|
-
});
|
|
1405
1507
|
fragment.append(item);
|
|
1406
1508
|
rendered.push(item);
|
|
1407
1509
|
}
|
|
1408
|
-
|
|
1510
|
+
sessionHistoryRecoveryMessage=recoveryNeeded
|
|
1511
|
+
?'This saved chat is readable, but some saved actions cannot be resumed.'
|
|
1512
|
+
:'';
|
|
1513
|
+
setPendingStructuralToolCalls(
|
|
1514
|
+
actionable?[...restoredPendingToolCalls.values()]:[]
|
|
1515
|
+
);
|
|
1409
1516
|
chatOutput.replaceChildren(fragment);
|
|
1410
1517
|
scrollTranscriptToBottom();
|
|
1411
1518
|
return rendered;
|
|
@@ -1474,7 +1581,7 @@
|
|
|
1474
1581
|
host.session=session;
|
|
1475
1582
|
setSessionStatus(
|
|
1476
1583
|
pendingStructuralToolMessage?'tool':'ready',
|
|
1477
|
-
pendingStructuralToolMessage||'Chat ready.'
|
|
1584
|
+
pendingStructuralToolMessage||sessionHistoryRecoveryMessage||'Chat ready.'
|
|
1478
1585
|
);
|
|
1479
1586
|
applyAIAvailability(host.aiAvailability);
|
|
1480
1587
|
sessionBindingPending=false;
|
|
@@ -1483,7 +1590,10 @@
|
|
|
1483
1590
|
session,
|
|
1484
1591
|
ai:boundChatAI,
|
|
1485
1592
|
history,
|
|
1486
|
-
pendingTool:pendingStructuralToolSummary()
|
|
1593
|
+
pendingTool:pendingStructuralToolSummary(),
|
|
1594
|
+
pendingTools:pendingStructuralToolSummaries(),
|
|
1595
|
+
pendingToolCall:pendingStructuralToolCallComplete(),
|
|
1596
|
+
pendingToolCalls:pendingStructuralToolCallsComplete()
|
|
1487
1597
|
};
|
|
1488
1598
|
dispatchChatEvent(
|
|
1489
1599
|
'chat-session-bound',
|
|
@@ -1652,11 +1762,27 @@
|
|
|
1652
1762
|
host.bindSession=bindSession;
|
|
1653
1763
|
host.submitMessage=submitMessage;
|
|
1654
1764
|
host.submitToolResult=submitToolResult;
|
|
1765
|
+
host.submitToolResults=submitToolResults;
|
|
1655
1766
|
Object.defineProperty(host,'pendingTool',{
|
|
1656
1767
|
configurable:true,
|
|
1657
1768
|
enumerable:true,
|
|
1658
1769
|
get:pendingStructuralToolSummary
|
|
1659
1770
|
});
|
|
1771
|
+
Object.defineProperty(host,'pendingTools',{
|
|
1772
|
+
configurable:true,
|
|
1773
|
+
enumerable:true,
|
|
1774
|
+
get:pendingStructuralToolSummaries
|
|
1775
|
+
});
|
|
1776
|
+
Object.defineProperty(host,'pendingToolCall',{
|
|
1777
|
+
configurable:true,
|
|
1778
|
+
enumerable:true,
|
|
1779
|
+
get:pendingStructuralToolCallComplete
|
|
1780
|
+
});
|
|
1781
|
+
Object.defineProperty(host,'pendingToolCalls',{
|
|
1782
|
+
configurable:true,
|
|
1783
|
+
enumerable:true,
|
|
1784
|
+
get:pendingStructuralToolCallsComplete
|
|
1785
|
+
});
|
|
1660
1786
|
host.destroy=destroy;
|
|
1661
1787
|
host.session=null;
|
|
1662
1788
|
host.sessionStatus={state:'idle',message:'Chat session is not connected.'};
|
|
@@ -1672,8 +1798,13 @@
|
|
|
1672
1798
|
);
|
|
1673
1799
|
window.addEventListener(
|
|
1674
1800
|
'pagehide',
|
|
1675
|
-
|
|
1676
|
-
{
|
|
1801
|
+
handlePageHide,
|
|
1802
|
+
{signal:aiRuntimeStateAbortController.signal}
|
|
1803
|
+
);
|
|
1804
|
+
window.addEventListener(
|
|
1805
|
+
'pageshow',
|
|
1806
|
+
handlePageShow,
|
|
1807
|
+
{signal:aiRuntimeStateAbortController.signal}
|
|
1677
1808
|
);
|
|
1678
1809
|
window.addEventListener(
|
|
1679
1810
|
AI_TTS_FAILURE_EVENT,
|
|
@@ -1863,9 +1994,12 @@
|
|
|
1863
1994
|
send.disabled=host.conversationComplete
|
|
1864
1995
|
||!availability.llm
|
|
1865
1996
|
||sessionMessagePending
|
|
1866
|
-
||Boolean(pendingStructuralToolMessage)
|
|
1997
|
+
||Boolean(pendingStructuralToolMessage)
|
|
1998
|
+
||Boolean(sessionHistoryRecoveryMessage);
|
|
1867
1999
|
send.title=pendingStructuralToolMessage
|
|
1868
2000
|
?pendingStructuralToolMessage
|
|
2001
|
+
:sessionHistoryRecoveryMessage
|
|
2002
|
+
?'This saved chat is readable but cannot continue.'
|
|
1869
2003
|
:availability.llm
|
|
1870
2004
|
?'Send message'
|
|
1871
2005
|
:'The selected language model service is unavailable.';
|
|
@@ -1910,16 +2044,6 @@
|
|
|
1910
2044
|
return configured||role?.modelId||'the selected language model';
|
|
1911
2045
|
}
|
|
1912
2046
|
|
|
1913
|
-
function byteProgressUnit(value){
|
|
1914
|
-
const unit=String(value??'')
|
|
1915
|
-
.trim()
|
|
1916
|
-
.toLowerCase()
|
|
1917
|
-
.replace(/[\s_-]+/g,'');
|
|
1918
|
-
return unit.includes('byte')
|
|
1919
|
-
||unit.includes('octet')
|
|
1920
|
-
||/^(?:[kmgtpe]?i?b)(?:(?:\/|per)?(?:s|sec|second))?$/u.test(unit);
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
2047
|
function determinateProgress(){
|
|
1924
2048
|
const total=Number(role?.progress?.total);
|
|
1925
2049
|
const completed=Number(role?.progress?.completed);
|
|
@@ -1927,15 +2051,14 @@
|
|
|
1927
2051
|
?role.progress.unit
|
|
1928
2052
|
:'items';
|
|
1929
2053
|
if(
|
|
1930
|
-
|
|
1931
|
-
||!Number.isFinite(total)
|
|
2054
|
+
!Number.isFinite(total)
|
|
1932
2055
|
||total<=0
|
|
1933
2056
|
||!Number.isFinite(completed)
|
|
1934
2057
|
){
|
|
1935
2058
|
return null;
|
|
1936
2059
|
}
|
|
1937
2060
|
return {
|
|
1938
|
-
completed
|
|
2061
|
+
completed,
|
|
1939
2062
|
total,
|
|
1940
2063
|
unit
|
|
1941
2064
|
};
|
|
@@ -1972,7 +2095,7 @@
|
|
|
1972
2095
|
return;
|
|
1973
2096
|
}
|
|
1974
2097
|
progress.max=measured.total;
|
|
1975
|
-
progress.value=
|
|
2098
|
+
progress.value=measured.completed;
|
|
1976
2099
|
}
|
|
1977
2100
|
|
|
1978
2101
|
function render(){
|
|
@@ -2727,6 +2850,7 @@
|
|
|
2727
2850
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
2728
2851
|
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
2729
2852
|
'AI_CHAT_TOOL_RESULT_NOT_PENDING',
|
|
2853
|
+
'AI_CHAT_TOOL_RESULT_BATCH_REQUIRED',
|
|
2730
2854
|
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
2731
2855
|
'AI_CHAT_TRANSACTION_SETTLED'
|
|
2732
2856
|
].includes(error?.code);
|
|
@@ -2756,7 +2880,7 @@
|
|
|
2756
2880
|
async function sendMessageThroughBoundSession(
|
|
2757
2881
|
text,
|
|
2758
2882
|
context,
|
|
2759
|
-
|
|
2883
|
+
sessionRequest={content:text,role:'user'},
|
|
2760
2884
|
perTurnRequest=null
|
|
2761
2885
|
){
|
|
2762
2886
|
const session=boundChatSession;
|
|
@@ -2774,13 +2898,21 @@
|
|
|
2774
2898
|
);
|
|
2775
2899
|
}
|
|
2776
2900
|
|
|
2901
|
+
const sessionRequestMessages=Array.isArray(sessionRequest)
|
|
2902
|
+
?sessionRequest
|
|
2903
|
+
:[sessionRequest];
|
|
2904
|
+
if(!sessionRequestMessages.length){
|
|
2905
|
+
throw new TypeError('A chat session request must contain at least one message.');
|
|
2906
|
+
}
|
|
2777
2907
|
const request={
|
|
2778
|
-
|
|
2908
|
+
...(sessionRequestMessages.length===1
|
|
2909
|
+
?{message:sessionRequestMessages[0]}
|
|
2910
|
+
:{messages:sessionRequestMessages}),
|
|
2779
2911
|
signal:context.signal,
|
|
2780
2912
|
...(perTurnRequest===null?{}:{request:perTurnRequest})
|
|
2781
2913
|
};
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2914
|
+
const previousPendingToolCalls=[...pendingStructuralToolCalls];
|
|
2915
|
+
const streamedStructuralToolCalls=[];
|
|
2784
2916
|
const messageId=`session-${++sessionMessageSequence}`;
|
|
2785
2917
|
const sessionMessageToken=Symbol(messageId);
|
|
2786
2918
|
activeSessionMessageToken=sessionMessageToken;
|
|
@@ -2821,20 +2953,20 @@
|
|
|
2821
2953
|
)||null;
|
|
2822
2954
|
if(!message) return false;
|
|
2823
2955
|
const normalized=normalizeVisibleToolCalls([call])[0];
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
){
|
|
2956
|
+
const existing=streamedStructuralToolCalls.find(
|
|
2957
|
+
candidate=>candidate.id===normalized.id
|
|
2958
|
+
);
|
|
2959
|
+
if(existing&&!sameStructuralToolCall(existing,normalized)){
|
|
2828
2960
|
throw chatError(
|
|
2829
2961
|
'The streamed structural tool call changed before completion.',
|
|
2830
2962
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH'
|
|
2831
2963
|
);
|
|
2832
2964
|
}
|
|
2833
|
-
if(
|
|
2965
|
+
if(existing){
|
|
2834
2966
|
return true;
|
|
2835
2967
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2968
|
+
streamedStructuralToolCalls.push(normalized);
|
|
2969
|
+
setPendingStructuralToolCalls(streamedStructuralToolCalls);
|
|
2838
2970
|
appendVisibleToolCall(message,normalized);
|
|
2839
2971
|
setSessionStatus('tool',pendingStructuralToolMessage);
|
|
2840
2972
|
return true;
|
|
@@ -2858,7 +2990,7 @@
|
|
|
2858
2990
|
const committedTurn=typeof session.transcript==='function'
|
|
2859
2991
|
?latestCommittedTranscriptTurn(
|
|
2860
2992
|
await session.transcript(),
|
|
2861
|
-
|
|
2993
|
+
sessionRequestMessages
|
|
2862
2994
|
)
|
|
2863
2995
|
:null;
|
|
2864
2996
|
const message=[...chatOutput.children].find(
|
|
@@ -2872,41 +3004,32 @@
|
|
|
2872
3004
|
result.message.content,
|
|
2873
3005
|
committedTurn?.response?.timestamp??result.message.timestamp
|
|
2874
3006
|
);
|
|
2875
|
-
const
|
|
3007
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2876
3008
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2877
|
-
)||null;
|
|
2878
|
-
setTranscriptMessageTimestamp(
|
|
2879
|
-
requestMessage,
|
|
2880
|
-
committedTurn?.request?.timestamp
|
|
2881
3009
|
);
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3010
|
+
for(const [index,requestMessage] of requestMessages.entries()){
|
|
3011
|
+
setTranscriptMessageTimestamp(
|
|
3012
|
+
requestMessage,
|
|
3013
|
+
committedTurn?.requests?.[index]?.timestamp
|
|
3014
|
+
??committedTurn?.request?.timestamp
|
|
2887
3015
|
);
|
|
2888
3016
|
}
|
|
3017
|
+
const terminalToolCalls=normalizeVisibleToolCalls(result.message.tool_calls);
|
|
2889
3018
|
if(
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
terminalToolCalls.length!==1
|
|
2893
|
-
||!sameStructuralToolCall(
|
|
2894
|
-
streamedStructuralToolCall,
|
|
2895
|
-
terminalToolCalls[0]
|
|
2896
|
-
)
|
|
2897
|
-
)
|
|
3019
|
+
streamedStructuralToolCalls.length
|
|
3020
|
+
&&!sameStructuralToolCalls(streamedStructuralToolCalls,terminalToolCalls)
|
|
2898
3021
|
){
|
|
2899
3022
|
throw chatError(
|
|
2900
|
-
'The terminal structural tool
|
|
3023
|
+
'The terminal structural tool calls do not match the streamed calls.',
|
|
2901
3024
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH'
|
|
2902
3025
|
);
|
|
2903
3026
|
}
|
|
2904
|
-
if(!
|
|
2905
|
-
|
|
3027
|
+
if(!streamedStructuralToolCalls.length){
|
|
3028
|
+
for(const call of terminalToolCalls){
|
|
3029
|
+
appendVisibleToolCall(message,call);
|
|
3030
|
+
}
|
|
2906
3031
|
}
|
|
2907
|
-
|
|
2908
|
-
terminalToolCalls.length?terminalToolCalls.at(-1):null
|
|
2909
|
-
);
|
|
3032
|
+
setPendingStructuralToolCalls(terminalToolCalls);
|
|
2910
3033
|
setSessionStatus(
|
|
2911
3034
|
pendingStructuralToolMessage?'tool':'ready',
|
|
2912
3035
|
pendingStructuralToolMessage||'Chat ready.'
|
|
@@ -2916,7 +3039,11 @@
|
|
|
2916
3039
|
request,
|
|
2917
3040
|
result,
|
|
2918
3041
|
message:result.message,
|
|
2919
|
-
context
|
|
3042
|
+
context,
|
|
3043
|
+
pendingTool:pendingStructuralToolSummary(),
|
|
3044
|
+
pendingTools:pendingStructuralToolSummaries(),
|
|
3045
|
+
pendingToolCall:pendingStructuralToolCallComplete(),
|
|
3046
|
+
pendingToolCalls:pendingStructuralToolCallsComplete()
|
|
2920
3047
|
};
|
|
2921
3048
|
if(activeSessionMessageToken===sessionMessageToken){
|
|
2922
3049
|
activeSessionMessageToken=null;
|
|
@@ -2939,38 +3066,42 @@
|
|
|
2939
3066
|
return result;
|
|
2940
3067
|
}catch(error){
|
|
2941
3068
|
if(!destroyed&&bindingGeneration===sessionBindingGeneration){
|
|
2942
|
-
|
|
3069
|
+
setPendingStructuralToolCalls(previousPendingToolCalls);
|
|
2943
3070
|
}
|
|
2944
3071
|
if(
|
|
2945
3072
|
!destroyed
|
|
2946
3073
|
&&bindingGeneration===sessionBindingGeneration
|
|
2947
3074
|
&&internalStructuralToolFailure(error)
|
|
2948
3075
|
){
|
|
2949
|
-
if(
|
|
3076
|
+
if(sessionRequestMessages.every(message=>message.role==='tool')){
|
|
2950
3077
|
const response=[...chatOutput.children].find(
|
|
2951
3078
|
candidate=>candidate.id===`message-${messageId}`
|
|
2952
3079
|
)||null;
|
|
2953
|
-
const
|
|
3080
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2954
3081
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2955
|
-
)
|
|
3082
|
+
);
|
|
2956
3083
|
response?.remove();
|
|
2957
|
-
requestMessage
|
|
3084
|
+
for(const requestMessage of requestMessages){
|
|
3085
|
+
requestMessage.remove();
|
|
3086
|
+
}
|
|
2958
3087
|
scrollTranscriptToBottom();
|
|
2959
3088
|
}else{
|
|
2960
3089
|
restoreRejectedStructuralDraft(messageId,context.operationId,text);
|
|
2961
3090
|
}
|
|
2962
3091
|
console.error('Arcane structural tool protocol failure.',error);
|
|
2963
3092
|
setSessionStatus(
|
|
2964
|
-
|
|
3093
|
+
pendingStructuralToolCalls.length?'tool':'ready',
|
|
2965
3094
|
pendingStructuralToolMessage||'Chat ready.'
|
|
2966
3095
|
);
|
|
2967
3096
|
}else if(!destroyed&&bindingGeneration===sessionBindingGeneration){
|
|
2968
3097
|
console.error('Arcane chat request failed.',error);
|
|
2969
|
-
if(
|
|
2970
|
-
const
|
|
3098
|
+
if(sessionRequestMessages.every(message=>message.role==='tool')){
|
|
3099
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2971
3100
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2972
|
-
)
|
|
2973
|
-
requestMessage
|
|
3101
|
+
);
|
|
3102
|
+
for(const requestMessage of requestMessages){
|
|
3103
|
+
requestMessage.remove();
|
|
3104
|
+
}
|
|
2974
3105
|
}
|
|
2975
3106
|
renderSessionMessageFailure(messageId,error);
|
|
2976
3107
|
setSessionStatus(
|
|
@@ -3010,18 +3141,18 @@
|
|
|
3010
3141
|
}
|
|
3011
3142
|
}
|
|
3012
3143
|
|
|
3013
|
-
async function
|
|
3144
|
+
async function submitToolResults(options={},context={}){
|
|
3014
3145
|
if(destroyed||host.conversationComplete){
|
|
3015
3146
|
return false;
|
|
3016
3147
|
}
|
|
3017
3148
|
if(!isPlainRecord(options)||!isPlainRecord(context)){
|
|
3018
|
-
throw new TypeError('Tool-
|
|
3149
|
+
throw new TypeError('Tool-results options and context must be plain objects.');
|
|
3019
3150
|
}
|
|
3020
3151
|
const unsupportedOption=Object.keys(options).find(
|
|
3021
|
-
key=>!['
|
|
3152
|
+
key=>!['request','results'].includes(key)
|
|
3022
3153
|
);
|
|
3023
3154
|
if(unsupportedOption){
|
|
3024
|
-
throw new TypeError(`Unsupported tool-
|
|
3155
|
+
throw new TypeError(`Unsupported tool-results option: ${unsupportedOption}.`);
|
|
3025
3156
|
}
|
|
3026
3157
|
const unsupportedContext=Object.keys(context).find(
|
|
3027
3158
|
key=>!['operationId','signal'].includes(key)
|
|
@@ -3035,53 +3166,102 @@
|
|
|
3035
3166
|
if(!host.aiAvailability.llm){
|
|
3036
3167
|
return false;
|
|
3037
3168
|
}
|
|
3038
|
-
if(!
|
|
3039
|
-
const error=new TypeError('There
|
|
3169
|
+
if(!pendingStructuralToolCalls.length){
|
|
3170
|
+
const error=new TypeError('There are no pending structural tool calls to settle.');
|
|
3040
3171
|
error.code='AI_CHAT_TOOL_RESULT_NOT_PENDING';
|
|
3041
3172
|
throw error;
|
|
3042
3173
|
}
|
|
3043
|
-
|
|
3174
|
+
if(!Array.isArray(options.results)||options.results.length===0){
|
|
3175
|
+
throw new TypeError('Tool results must be a nonempty array.');
|
|
3176
|
+
}
|
|
3044
3177
|
const dispositions=new Map([
|
|
3045
3178
|
['executed','Executed'],
|
|
3046
3179
|
['declined','Declined'],
|
|
3047
3180
|
['cancelled','Cancelled'],
|
|
3048
3181
|
['not-executed','Not executed']
|
|
3049
3182
|
]);
|
|
3050
|
-
if(!dispositions.has(disposition)){
|
|
3051
|
-
throw new TypeError(
|
|
3052
|
-
'Tool-result disposition must be executed, declined, cancelled, or not-executed.'
|
|
3053
|
-
);
|
|
3054
|
-
}
|
|
3055
|
-
if(typeof options.message!=='string'||!options.message.trim()){
|
|
3056
|
-
throw new TypeError('Tool-result message must contain user-facing text.');
|
|
3057
|
-
}
|
|
3058
3183
|
const perTurnRequest=options.request??null;
|
|
3059
3184
|
if(perTurnRequest!==null&&!isPlainRecord(perTurnRequest)){
|
|
3060
3185
|
throw new TypeError('Tool-result request options must be a plain object.');
|
|
3061
3186
|
}
|
|
3062
3187
|
const managedRequestField=perTurnRequest&&Object.keys(perTurnRequest).find(
|
|
3063
|
-
key=>[
|
|
3188
|
+
key=>[
|
|
3189
|
+
'message','messages','onChunk','onComplete','onDataChunk','onDataResult',
|
|
3190
|
+
'onResponse','onToolCall','signal','stream'
|
|
3191
|
+
].includes(key)
|
|
3064
3192
|
);
|
|
3065
3193
|
if(managedRequestField){
|
|
3066
3194
|
throw new TypeError(
|
|
3067
3195
|
`Tool-result request.${managedRequestField} is managed by the chat session.`
|
|
3068
3196
|
);
|
|
3069
3197
|
}
|
|
3070
|
-
const
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3198
|
+
const pendingById=new Map(
|
|
3199
|
+
pendingStructuralToolCalls.map(call=>[call.id,call])
|
|
3200
|
+
);
|
|
3201
|
+
const resultsById=new Map();
|
|
3202
|
+
for(const [index,result] of options.results.entries()){
|
|
3203
|
+
if(!isPlainRecord(result)){
|
|
3204
|
+
throw new TypeError(`Tool result ${index+1} must be a plain object.`);
|
|
3205
|
+
}
|
|
3206
|
+
const unsupportedResultField=Object.keys(result).find(
|
|
3207
|
+
key=>!['disposition','message','persist','toolCallId'].includes(key)
|
|
3208
|
+
);
|
|
3209
|
+
if(unsupportedResultField){
|
|
3210
|
+
throw new TypeError(
|
|
3211
|
+
`Unsupported tool result ${index+1} field: ${unsupportedResultField}.`
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
const toolCallId=result.toolCallId;
|
|
3215
|
+
if(
|
|
3216
|
+
typeof toolCallId!=='string'
|
|
3217
|
+
||!pendingById.has(toolCallId)
|
|
3218
|
+
||resultsById.has(toolCallId)
|
|
3219
|
+
){
|
|
3220
|
+
const error=new TypeError(
|
|
3221
|
+
`Tool result ${index+1} does not match a pending structural tool call.`
|
|
3222
|
+
);
|
|
3223
|
+
error.code='AI_CHAT_INVALID_TOOL_MESSAGE';
|
|
3224
|
+
throw error;
|
|
3225
|
+
}
|
|
3226
|
+
if(!dispositions.has(result.disposition)){
|
|
3227
|
+
throw new TypeError(
|
|
3228
|
+
'Tool-result disposition must be executed, declined, cancelled, or not-executed.'
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
if(typeof result.message!=='string'||!result.message.trim()){
|
|
3232
|
+
throw new TypeError('Tool-result message must contain user-facing text.');
|
|
3233
|
+
}
|
|
3234
|
+
const persist=result.persist??true;
|
|
3235
|
+
if(typeof persist!=='boolean'){
|
|
3236
|
+
throw new TypeError('Tool-result persist must be boolean.');
|
|
3237
|
+
}
|
|
3238
|
+
resultsById.set(toolCallId,{
|
|
3239
|
+
content:`${dispositions.get(result.disposition)} — ${result.message}`,
|
|
3240
|
+
persist,
|
|
3241
|
+
role:'tool',
|
|
3242
|
+
tool_call_id:toolCallId
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
if(resultsById.size!==pendingById.size){
|
|
3246
|
+
const error=new TypeError(
|
|
3247
|
+
'Every pending structural tool call must receive one matching result.'
|
|
3248
|
+
);
|
|
3249
|
+
error.code='AI_CHAT_TOOL_RESULT_REQUIRED';
|
|
3074
3250
|
throw error;
|
|
3075
3251
|
}
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3252
|
+
const orderedResults=pendingStructuralToolCalls.map(
|
|
3253
|
+
call=>resultsById.get(call.id)
|
|
3254
|
+
);
|
|
3255
|
+
if(new Set(orderedResults.map(result=>result.persist)).size>1){
|
|
3256
|
+
const error=new TypeError(
|
|
3257
|
+
'All tool results in one atomic settlement must use the same persistence choice.'
|
|
3258
|
+
);
|
|
3259
|
+
error.code='AI_CHAT_INCOHERENT_PERSISTENCE';
|
|
3260
|
+
throw error;
|
|
3079
3261
|
}
|
|
3080
|
-
|
|
3081
|
-
const text=`${dispositions.get(disposition)} — ${options.message}`;
|
|
3082
3262
|
const operationId=typeof context.operationId==='string'&&context.operationId
|
|
3083
3263
|
?context.operationId
|
|
3084
|
-
:nextChatOperationId('tool-
|
|
3264
|
+
:nextChatOperationId('tool-results');
|
|
3085
3265
|
const ownership=createChatSubmissionOwnership(context.signal??null);
|
|
3086
3266
|
const eventContext={
|
|
3087
3267
|
source:'tool',
|
|
@@ -3094,17 +3274,15 @@
|
|
|
3094
3274
|
return false;
|
|
3095
3275
|
}
|
|
3096
3276
|
try{
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3277
|
+
for(const result of orderedResults){
|
|
3278
|
+
const visibleMessage=appendTranscriptMessage('tool',result.content,'Tool');
|
|
3279
|
+
visibleMessage.dataset.operationId=operationId;
|
|
3280
|
+
visibleMessage.dataset.toolCallId=result.tool_call_id;
|
|
3281
|
+
}
|
|
3099
3282
|
const result=sendMessageThroughBoundSession(
|
|
3100
|
-
|
|
3283
|
+
orderedResults.map(message=>message.content).join('\n'),
|
|
3101
3284
|
eventContext,
|
|
3102
|
-
|
|
3103
|
-
content:text,
|
|
3104
|
-
persist,
|
|
3105
|
-
role:'tool',
|
|
3106
|
-
tool_call_id:toolCallId
|
|
3107
|
-
},
|
|
3285
|
+
orderedResults,
|
|
3108
3286
|
perTurnRequest
|
|
3109
3287
|
);
|
|
3110
3288
|
return observeHostSubmission(result,eventContext,ownership);
|
|
@@ -3114,6 +3292,50 @@
|
|
|
3114
3292
|
}
|
|
3115
3293
|
}
|
|
3116
3294
|
|
|
3295
|
+
async function submitToolResult(options={},context={}){
|
|
3296
|
+
if(
|
|
3297
|
+
destroyed
|
|
3298
|
+
||host.conversationComplete
|
|
3299
|
+
||!boundChatSession
|
|
3300
|
+
||sessionBindingPending
|
|
3301
|
+
||sessionMessagePending
|
|
3302
|
+
||!host.aiAvailability.llm
|
|
3303
|
+
){
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
if(!isPlainRecord(options)){
|
|
3307
|
+
throw new TypeError('Tool-result options must be a plain object.');
|
|
3308
|
+
}
|
|
3309
|
+
const unsupportedOption=Object.keys(options).find(
|
|
3310
|
+
key=>!['disposition','message','persist','request','toolCallId'].includes(key)
|
|
3311
|
+
);
|
|
3312
|
+
if(unsupportedOption){
|
|
3313
|
+
throw new TypeError(`Unsupported tool-result option: ${unsupportedOption}.`);
|
|
3314
|
+
}
|
|
3315
|
+
if(pendingStructuralToolCalls.length!==1){
|
|
3316
|
+
const error=new TypeError(
|
|
3317
|
+
pendingStructuralToolCalls.length
|
|
3318
|
+
?'Parallel structural tool calls must be settled together with submitToolResults().'
|
|
3319
|
+
:'There is no pending structural tool call to settle.'
|
|
3320
|
+
);
|
|
3321
|
+
error.code=pendingStructuralToolCalls.length
|
|
3322
|
+
?'AI_CHAT_TOOL_RESULT_BATCH_REQUIRED'
|
|
3323
|
+
:'AI_CHAT_TOOL_RESULT_NOT_PENDING';
|
|
3324
|
+
throw error;
|
|
3325
|
+
}
|
|
3326
|
+
const {request,...result}=options;
|
|
3327
|
+
return submitToolResults(
|
|
3328
|
+
{
|
|
3329
|
+
results:[{
|
|
3330
|
+
...result,
|
|
3331
|
+
toolCallId:result.toolCallId??pendingStructuralToolCalls[0].id
|
|
3332
|
+
}],
|
|
3333
|
+
...(request===undefined?{}:{request})
|
|
3334
|
+
},
|
|
3335
|
+
context
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3117
3339
|
function observeHostSubmission(result,context,ownership){
|
|
3118
3340
|
const observed=hostSubmissionBarrier.track(result).then(
|
|
3119
3341
|
function settleHostSubmission(value){
|
|
@@ -3126,9 +3348,12 @@
|
|
|
3126
3348
|
if(destroyed||context.signal.aborted){
|
|
3127
3349
|
return false;
|
|
3128
3350
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3351
|
+
console.error(
|
|
3352
|
+
internalStructuralToolFailure(error)
|
|
3353
|
+
?'Arcane structural tool protocol failure.'
|
|
3354
|
+
:'The chat message could not be submitted.',
|
|
3355
|
+
error
|
|
3356
|
+
);
|
|
3132
3357
|
dispatchChatEvent(
|
|
3133
3358
|
'chat-send-error',
|
|
3134
3359
|
{error,context:{...context}},
|
|
@@ -3146,7 +3371,6 @@
|
|
|
3146
3371
|
}
|
|
3147
3372
|
}
|
|
3148
3373
|
);
|
|
3149
|
-
console.error('The chat message could not be submitted.',error);
|
|
3150
3374
|
return false;
|
|
3151
3375
|
}
|
|
3152
3376
|
).finally(
|
|
@@ -3173,7 +3397,12 @@
|
|
|
3173
3397
|
if(!host.aiAvailability.llm){
|
|
3174
3398
|
return false;
|
|
3175
3399
|
}
|
|
3176
|
-
if(
|
|
3400
|
+
if(
|
|
3401
|
+
sessionBindingPending
|
|
3402
|
+
||sessionMessagePending
|
|
3403
|
+
||pendingStructuralToolMessage
|
|
3404
|
+
||sessionHistoryRecoveryMessage
|
|
3405
|
+
){
|
|
3177
3406
|
return false;
|
|
3178
3407
|
}
|
|
3179
3408
|
const submissionContext={
|
|
@@ -3333,6 +3562,25 @@
|
|
|
3333
3562
|
return false;
|
|
3334
3563
|
}
|
|
3335
3564
|
|
|
3565
|
+
function handlePageHide(event){
|
|
3566
|
+
if(event.persisted===true){
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
destroy();
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
function handlePageShow(event){
|
|
3573
|
+
if(event.persisted!==true||destroyed){
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
restoreFromPageCache();
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
function restoreFromPageCache(){
|
|
3580
|
+
setAIAvailability();
|
|
3581
|
+
scrollTranscriptToBottom();
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3336
3584
|
function destroy(){
|
|
3337
3585
|
if(destroyed){
|
|
3338
3586
|
return false;
|
|
@@ -3349,7 +3597,8 @@
|
|
|
3349
3597
|
}
|
|
3350
3598
|
aiRuntimeStateAbortController.abort(destroyReason);
|
|
3351
3599
|
aiActivationController.destroy();
|
|
3352
|
-
window.removeEventListener('pagehide',
|
|
3600
|
+
window.removeEventListener('pagehide',handlePageHide);
|
|
3601
|
+
window.removeEventListener('pageshow',handlePageShow);
|
|
3353
3602
|
conversationTimeboxUnsubscribe?.();
|
|
3354
3603
|
conversationTimeboxUnsubscribe=null;
|
|
3355
3604
|
conversationTimeboxController=null;
|
|
@@ -3357,7 +3606,7 @@
|
|
|
3357
3606
|
sessionBindingPending=false;
|
|
3358
3607
|
sessionMessagePending=false;
|
|
3359
3608
|
activeSessionMessageToken=null;
|
|
3360
|
-
|
|
3609
|
+
setPendingStructuralToolCalls();
|
|
3361
3610
|
boundChatSession=null;
|
|
3362
3611
|
boundChatAI=null;
|
|
3363
3612
|
host.session=null;
|