arcane-os 0.3.4 → 0.3.5
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 +12 -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 +466 -206
- 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 +765 -135
- 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 +34 -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.';
|
|
@@ -2727,6 +2861,7 @@
|
|
|
2727
2861
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
2728
2862
|
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
2729
2863
|
'AI_CHAT_TOOL_RESULT_NOT_PENDING',
|
|
2864
|
+
'AI_CHAT_TOOL_RESULT_BATCH_REQUIRED',
|
|
2730
2865
|
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
2731
2866
|
'AI_CHAT_TRANSACTION_SETTLED'
|
|
2732
2867
|
].includes(error?.code);
|
|
@@ -2756,7 +2891,7 @@
|
|
|
2756
2891
|
async function sendMessageThroughBoundSession(
|
|
2757
2892
|
text,
|
|
2758
2893
|
context,
|
|
2759
|
-
|
|
2894
|
+
sessionRequest={content:text,role:'user'},
|
|
2760
2895
|
perTurnRequest=null
|
|
2761
2896
|
){
|
|
2762
2897
|
const session=boundChatSession;
|
|
@@ -2774,13 +2909,21 @@
|
|
|
2774
2909
|
);
|
|
2775
2910
|
}
|
|
2776
2911
|
|
|
2912
|
+
const sessionRequestMessages=Array.isArray(sessionRequest)
|
|
2913
|
+
?sessionRequest
|
|
2914
|
+
:[sessionRequest];
|
|
2915
|
+
if(!sessionRequestMessages.length){
|
|
2916
|
+
throw new TypeError('A chat session request must contain at least one message.');
|
|
2917
|
+
}
|
|
2777
2918
|
const request={
|
|
2778
|
-
|
|
2919
|
+
...(sessionRequestMessages.length===1
|
|
2920
|
+
?{message:sessionRequestMessages[0]}
|
|
2921
|
+
:{messages:sessionRequestMessages}),
|
|
2779
2922
|
signal:context.signal,
|
|
2780
2923
|
...(perTurnRequest===null?{}:{request:perTurnRequest})
|
|
2781
2924
|
};
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2925
|
+
const previousPendingToolCalls=[...pendingStructuralToolCalls];
|
|
2926
|
+
const streamedStructuralToolCalls=[];
|
|
2784
2927
|
const messageId=`session-${++sessionMessageSequence}`;
|
|
2785
2928
|
const sessionMessageToken=Symbol(messageId);
|
|
2786
2929
|
activeSessionMessageToken=sessionMessageToken;
|
|
@@ -2821,20 +2964,20 @@
|
|
|
2821
2964
|
)||null;
|
|
2822
2965
|
if(!message) return false;
|
|
2823
2966
|
const normalized=normalizeVisibleToolCalls([call])[0];
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
){
|
|
2967
|
+
const existing=streamedStructuralToolCalls.find(
|
|
2968
|
+
candidate=>candidate.id===normalized.id
|
|
2969
|
+
);
|
|
2970
|
+
if(existing&&!sameStructuralToolCall(existing,normalized)){
|
|
2828
2971
|
throw chatError(
|
|
2829
2972
|
'The streamed structural tool call changed before completion.',
|
|
2830
2973
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH'
|
|
2831
2974
|
);
|
|
2832
2975
|
}
|
|
2833
|
-
if(
|
|
2976
|
+
if(existing){
|
|
2834
2977
|
return true;
|
|
2835
2978
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2979
|
+
streamedStructuralToolCalls.push(normalized);
|
|
2980
|
+
setPendingStructuralToolCalls(streamedStructuralToolCalls);
|
|
2838
2981
|
appendVisibleToolCall(message,normalized);
|
|
2839
2982
|
setSessionStatus('tool',pendingStructuralToolMessage);
|
|
2840
2983
|
return true;
|
|
@@ -2858,7 +3001,7 @@
|
|
|
2858
3001
|
const committedTurn=typeof session.transcript==='function'
|
|
2859
3002
|
?latestCommittedTranscriptTurn(
|
|
2860
3003
|
await session.transcript(),
|
|
2861
|
-
|
|
3004
|
+
sessionRequestMessages
|
|
2862
3005
|
)
|
|
2863
3006
|
:null;
|
|
2864
3007
|
const message=[...chatOutput.children].find(
|
|
@@ -2872,41 +3015,32 @@
|
|
|
2872
3015
|
result.message.content,
|
|
2873
3016
|
committedTurn?.response?.timestamp??result.message.timestamp
|
|
2874
3017
|
);
|
|
2875
|
-
const
|
|
3018
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2876
3019
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2877
|
-
)||null;
|
|
2878
|
-
setTranscriptMessageTimestamp(
|
|
2879
|
-
requestMessage,
|
|
2880
|
-
committedTurn?.request?.timestamp
|
|
2881
3020
|
);
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3021
|
+
for(const [index,requestMessage] of requestMessages.entries()){
|
|
3022
|
+
setTranscriptMessageTimestamp(
|
|
3023
|
+
requestMessage,
|
|
3024
|
+
committedTurn?.requests?.[index]?.timestamp
|
|
3025
|
+
??committedTurn?.request?.timestamp
|
|
2887
3026
|
);
|
|
2888
3027
|
}
|
|
3028
|
+
const terminalToolCalls=normalizeVisibleToolCalls(result.message.tool_calls);
|
|
2889
3029
|
if(
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
terminalToolCalls.length!==1
|
|
2893
|
-
||!sameStructuralToolCall(
|
|
2894
|
-
streamedStructuralToolCall,
|
|
2895
|
-
terminalToolCalls[0]
|
|
2896
|
-
)
|
|
2897
|
-
)
|
|
3030
|
+
streamedStructuralToolCalls.length
|
|
3031
|
+
&&!sameStructuralToolCalls(streamedStructuralToolCalls,terminalToolCalls)
|
|
2898
3032
|
){
|
|
2899
3033
|
throw chatError(
|
|
2900
|
-
'The terminal structural tool
|
|
3034
|
+
'The terminal structural tool calls do not match the streamed calls.',
|
|
2901
3035
|
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH'
|
|
2902
3036
|
);
|
|
2903
3037
|
}
|
|
2904
|
-
if(!
|
|
2905
|
-
|
|
3038
|
+
if(!streamedStructuralToolCalls.length){
|
|
3039
|
+
for(const call of terminalToolCalls){
|
|
3040
|
+
appendVisibleToolCall(message,call);
|
|
3041
|
+
}
|
|
2906
3042
|
}
|
|
2907
|
-
|
|
2908
|
-
terminalToolCalls.length?terminalToolCalls.at(-1):null
|
|
2909
|
-
);
|
|
3043
|
+
setPendingStructuralToolCalls(terminalToolCalls);
|
|
2910
3044
|
setSessionStatus(
|
|
2911
3045
|
pendingStructuralToolMessage?'tool':'ready',
|
|
2912
3046
|
pendingStructuralToolMessage||'Chat ready.'
|
|
@@ -2916,7 +3050,11 @@
|
|
|
2916
3050
|
request,
|
|
2917
3051
|
result,
|
|
2918
3052
|
message:result.message,
|
|
2919
|
-
context
|
|
3053
|
+
context,
|
|
3054
|
+
pendingTool:pendingStructuralToolSummary(),
|
|
3055
|
+
pendingTools:pendingStructuralToolSummaries(),
|
|
3056
|
+
pendingToolCall:pendingStructuralToolCallComplete(),
|
|
3057
|
+
pendingToolCalls:pendingStructuralToolCallsComplete()
|
|
2920
3058
|
};
|
|
2921
3059
|
if(activeSessionMessageToken===sessionMessageToken){
|
|
2922
3060
|
activeSessionMessageToken=null;
|
|
@@ -2939,38 +3077,42 @@
|
|
|
2939
3077
|
return result;
|
|
2940
3078
|
}catch(error){
|
|
2941
3079
|
if(!destroyed&&bindingGeneration===sessionBindingGeneration){
|
|
2942
|
-
|
|
3080
|
+
setPendingStructuralToolCalls(previousPendingToolCalls);
|
|
2943
3081
|
}
|
|
2944
3082
|
if(
|
|
2945
3083
|
!destroyed
|
|
2946
3084
|
&&bindingGeneration===sessionBindingGeneration
|
|
2947
3085
|
&&internalStructuralToolFailure(error)
|
|
2948
3086
|
){
|
|
2949
|
-
if(
|
|
3087
|
+
if(sessionRequestMessages.every(message=>message.role==='tool')){
|
|
2950
3088
|
const response=[...chatOutput.children].find(
|
|
2951
3089
|
candidate=>candidate.id===`message-${messageId}`
|
|
2952
3090
|
)||null;
|
|
2953
|
-
const
|
|
3091
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2954
3092
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2955
|
-
)
|
|
3093
|
+
);
|
|
2956
3094
|
response?.remove();
|
|
2957
|
-
requestMessage
|
|
3095
|
+
for(const requestMessage of requestMessages){
|
|
3096
|
+
requestMessage.remove();
|
|
3097
|
+
}
|
|
2958
3098
|
scrollTranscriptToBottom();
|
|
2959
3099
|
}else{
|
|
2960
3100
|
restoreRejectedStructuralDraft(messageId,context.operationId,text);
|
|
2961
3101
|
}
|
|
2962
3102
|
console.error('Arcane structural tool protocol failure.',error);
|
|
2963
3103
|
setSessionStatus(
|
|
2964
|
-
|
|
3104
|
+
pendingStructuralToolCalls.length?'tool':'ready',
|
|
2965
3105
|
pendingStructuralToolMessage||'Chat ready.'
|
|
2966
3106
|
);
|
|
2967
3107
|
}else if(!destroyed&&bindingGeneration===sessionBindingGeneration){
|
|
2968
3108
|
console.error('Arcane chat request failed.',error);
|
|
2969
|
-
if(
|
|
2970
|
-
const
|
|
3109
|
+
if(sessionRequestMessages.every(message=>message.role==='tool')){
|
|
3110
|
+
const requestMessages=[...chatOutput.children].filter(
|
|
2971
3111
|
candidate=>candidate.dataset.operationId===context.operationId
|
|
2972
|
-
)
|
|
2973
|
-
requestMessage
|
|
3112
|
+
);
|
|
3113
|
+
for(const requestMessage of requestMessages){
|
|
3114
|
+
requestMessage.remove();
|
|
3115
|
+
}
|
|
2974
3116
|
}
|
|
2975
3117
|
renderSessionMessageFailure(messageId,error);
|
|
2976
3118
|
setSessionStatus(
|
|
@@ -3010,18 +3152,18 @@
|
|
|
3010
3152
|
}
|
|
3011
3153
|
}
|
|
3012
3154
|
|
|
3013
|
-
async function
|
|
3155
|
+
async function submitToolResults(options={},context={}){
|
|
3014
3156
|
if(destroyed||host.conversationComplete){
|
|
3015
3157
|
return false;
|
|
3016
3158
|
}
|
|
3017
3159
|
if(!isPlainRecord(options)||!isPlainRecord(context)){
|
|
3018
|
-
throw new TypeError('Tool-
|
|
3160
|
+
throw new TypeError('Tool-results options and context must be plain objects.');
|
|
3019
3161
|
}
|
|
3020
3162
|
const unsupportedOption=Object.keys(options).find(
|
|
3021
|
-
key=>!['
|
|
3163
|
+
key=>!['request','results'].includes(key)
|
|
3022
3164
|
);
|
|
3023
3165
|
if(unsupportedOption){
|
|
3024
|
-
throw new TypeError(`Unsupported tool-
|
|
3166
|
+
throw new TypeError(`Unsupported tool-results option: ${unsupportedOption}.`);
|
|
3025
3167
|
}
|
|
3026
3168
|
const unsupportedContext=Object.keys(context).find(
|
|
3027
3169
|
key=>!['operationId','signal'].includes(key)
|
|
@@ -3035,53 +3177,102 @@
|
|
|
3035
3177
|
if(!host.aiAvailability.llm){
|
|
3036
3178
|
return false;
|
|
3037
3179
|
}
|
|
3038
|
-
if(!
|
|
3039
|
-
const error=new TypeError('There
|
|
3180
|
+
if(!pendingStructuralToolCalls.length){
|
|
3181
|
+
const error=new TypeError('There are no pending structural tool calls to settle.');
|
|
3040
3182
|
error.code='AI_CHAT_TOOL_RESULT_NOT_PENDING';
|
|
3041
3183
|
throw error;
|
|
3042
3184
|
}
|
|
3043
|
-
|
|
3185
|
+
if(!Array.isArray(options.results)||options.results.length===0){
|
|
3186
|
+
throw new TypeError('Tool results must be a nonempty array.');
|
|
3187
|
+
}
|
|
3044
3188
|
const dispositions=new Map([
|
|
3045
3189
|
['executed','Executed'],
|
|
3046
3190
|
['declined','Declined'],
|
|
3047
3191
|
['cancelled','Cancelled'],
|
|
3048
3192
|
['not-executed','Not executed']
|
|
3049
3193
|
]);
|
|
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
3194
|
const perTurnRequest=options.request??null;
|
|
3059
3195
|
if(perTurnRequest!==null&&!isPlainRecord(perTurnRequest)){
|
|
3060
3196
|
throw new TypeError('Tool-result request options must be a plain object.');
|
|
3061
3197
|
}
|
|
3062
3198
|
const managedRequestField=perTurnRequest&&Object.keys(perTurnRequest).find(
|
|
3063
|
-
key=>[
|
|
3199
|
+
key=>[
|
|
3200
|
+
'message','messages','onChunk','onComplete','onDataChunk','onDataResult',
|
|
3201
|
+
'onResponse','onToolCall','signal','stream'
|
|
3202
|
+
].includes(key)
|
|
3064
3203
|
);
|
|
3065
3204
|
if(managedRequestField){
|
|
3066
3205
|
throw new TypeError(
|
|
3067
3206
|
`Tool-result request.${managedRequestField} is managed by the chat session.`
|
|
3068
3207
|
);
|
|
3069
3208
|
}
|
|
3070
|
-
const
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3209
|
+
const pendingById=new Map(
|
|
3210
|
+
pendingStructuralToolCalls.map(call=>[call.id,call])
|
|
3211
|
+
);
|
|
3212
|
+
const resultsById=new Map();
|
|
3213
|
+
for(const [index,result] of options.results.entries()){
|
|
3214
|
+
if(!isPlainRecord(result)){
|
|
3215
|
+
throw new TypeError(`Tool result ${index+1} must be a plain object.`);
|
|
3216
|
+
}
|
|
3217
|
+
const unsupportedResultField=Object.keys(result).find(
|
|
3218
|
+
key=>!['disposition','message','persist','toolCallId'].includes(key)
|
|
3219
|
+
);
|
|
3220
|
+
if(unsupportedResultField){
|
|
3221
|
+
throw new TypeError(
|
|
3222
|
+
`Unsupported tool result ${index+1} field: ${unsupportedResultField}.`
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
const toolCallId=result.toolCallId;
|
|
3226
|
+
if(
|
|
3227
|
+
typeof toolCallId!=='string'
|
|
3228
|
+
||!pendingById.has(toolCallId)
|
|
3229
|
+
||resultsById.has(toolCallId)
|
|
3230
|
+
){
|
|
3231
|
+
const error=new TypeError(
|
|
3232
|
+
`Tool result ${index+1} does not match a pending structural tool call.`
|
|
3233
|
+
);
|
|
3234
|
+
error.code='AI_CHAT_INVALID_TOOL_MESSAGE';
|
|
3235
|
+
throw error;
|
|
3236
|
+
}
|
|
3237
|
+
if(!dispositions.has(result.disposition)){
|
|
3238
|
+
throw new TypeError(
|
|
3239
|
+
'Tool-result disposition must be executed, declined, cancelled, or not-executed.'
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
if(typeof result.message!=='string'||!result.message.trim()){
|
|
3243
|
+
throw new TypeError('Tool-result message must contain user-facing text.');
|
|
3244
|
+
}
|
|
3245
|
+
const persist=result.persist??true;
|
|
3246
|
+
if(typeof persist!=='boolean'){
|
|
3247
|
+
throw new TypeError('Tool-result persist must be boolean.');
|
|
3248
|
+
}
|
|
3249
|
+
resultsById.set(toolCallId,{
|
|
3250
|
+
content:`${dispositions.get(result.disposition)} — ${result.message}`,
|
|
3251
|
+
persist,
|
|
3252
|
+
role:'tool',
|
|
3253
|
+
tool_call_id:toolCallId
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
if(resultsById.size!==pendingById.size){
|
|
3257
|
+
const error=new TypeError(
|
|
3258
|
+
'Every pending structural tool call must receive one matching result.'
|
|
3259
|
+
);
|
|
3260
|
+
error.code='AI_CHAT_TOOL_RESULT_REQUIRED';
|
|
3074
3261
|
throw error;
|
|
3075
3262
|
}
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3263
|
+
const orderedResults=pendingStructuralToolCalls.map(
|
|
3264
|
+
call=>resultsById.get(call.id)
|
|
3265
|
+
);
|
|
3266
|
+
if(new Set(orderedResults.map(result=>result.persist)).size>1){
|
|
3267
|
+
const error=new TypeError(
|
|
3268
|
+
'All tool results in one atomic settlement must use the same persistence choice.'
|
|
3269
|
+
);
|
|
3270
|
+
error.code='AI_CHAT_INCOHERENT_PERSISTENCE';
|
|
3271
|
+
throw error;
|
|
3079
3272
|
}
|
|
3080
|
-
|
|
3081
|
-
const text=`${dispositions.get(disposition)} — ${options.message}`;
|
|
3082
3273
|
const operationId=typeof context.operationId==='string'&&context.operationId
|
|
3083
3274
|
?context.operationId
|
|
3084
|
-
:nextChatOperationId('tool-
|
|
3275
|
+
:nextChatOperationId('tool-results');
|
|
3085
3276
|
const ownership=createChatSubmissionOwnership(context.signal??null);
|
|
3086
3277
|
const eventContext={
|
|
3087
3278
|
source:'tool',
|
|
@@ -3094,17 +3285,15 @@
|
|
|
3094
3285
|
return false;
|
|
3095
3286
|
}
|
|
3096
3287
|
try{
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3288
|
+
for(const result of orderedResults){
|
|
3289
|
+
const visibleMessage=appendTranscriptMessage('tool',result.content,'Tool');
|
|
3290
|
+
visibleMessage.dataset.operationId=operationId;
|
|
3291
|
+
visibleMessage.dataset.toolCallId=result.tool_call_id;
|
|
3292
|
+
}
|
|
3099
3293
|
const result=sendMessageThroughBoundSession(
|
|
3100
|
-
|
|
3294
|
+
orderedResults.map(message=>message.content).join('\n'),
|
|
3101
3295
|
eventContext,
|
|
3102
|
-
|
|
3103
|
-
content:text,
|
|
3104
|
-
persist,
|
|
3105
|
-
role:'tool',
|
|
3106
|
-
tool_call_id:toolCallId
|
|
3107
|
-
},
|
|
3296
|
+
orderedResults,
|
|
3108
3297
|
perTurnRequest
|
|
3109
3298
|
);
|
|
3110
3299
|
return observeHostSubmission(result,eventContext,ownership);
|
|
@@ -3114,6 +3303,50 @@
|
|
|
3114
3303
|
}
|
|
3115
3304
|
}
|
|
3116
3305
|
|
|
3306
|
+
async function submitToolResult(options={},context={}){
|
|
3307
|
+
if(
|
|
3308
|
+
destroyed
|
|
3309
|
+
||host.conversationComplete
|
|
3310
|
+
||!boundChatSession
|
|
3311
|
+
||sessionBindingPending
|
|
3312
|
+
||sessionMessagePending
|
|
3313
|
+
||!host.aiAvailability.llm
|
|
3314
|
+
){
|
|
3315
|
+
return false;
|
|
3316
|
+
}
|
|
3317
|
+
if(!isPlainRecord(options)){
|
|
3318
|
+
throw new TypeError('Tool-result options must be a plain object.');
|
|
3319
|
+
}
|
|
3320
|
+
const unsupportedOption=Object.keys(options).find(
|
|
3321
|
+
key=>!['disposition','message','persist','request','toolCallId'].includes(key)
|
|
3322
|
+
);
|
|
3323
|
+
if(unsupportedOption){
|
|
3324
|
+
throw new TypeError(`Unsupported tool-result option: ${unsupportedOption}.`);
|
|
3325
|
+
}
|
|
3326
|
+
if(pendingStructuralToolCalls.length!==1){
|
|
3327
|
+
const error=new TypeError(
|
|
3328
|
+
pendingStructuralToolCalls.length
|
|
3329
|
+
?'Parallel structural tool calls must be settled together with submitToolResults().'
|
|
3330
|
+
:'There is no pending structural tool call to settle.'
|
|
3331
|
+
);
|
|
3332
|
+
error.code=pendingStructuralToolCalls.length
|
|
3333
|
+
?'AI_CHAT_TOOL_RESULT_BATCH_REQUIRED'
|
|
3334
|
+
:'AI_CHAT_TOOL_RESULT_NOT_PENDING';
|
|
3335
|
+
throw error;
|
|
3336
|
+
}
|
|
3337
|
+
const {request,...result}=options;
|
|
3338
|
+
return submitToolResults(
|
|
3339
|
+
{
|
|
3340
|
+
results:[{
|
|
3341
|
+
...result,
|
|
3342
|
+
toolCallId:result.toolCallId??pendingStructuralToolCalls[0].id
|
|
3343
|
+
}],
|
|
3344
|
+
...(request===undefined?{}:{request})
|
|
3345
|
+
},
|
|
3346
|
+
context
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3117
3350
|
function observeHostSubmission(result,context,ownership){
|
|
3118
3351
|
const observed=hostSubmissionBarrier.track(result).then(
|
|
3119
3352
|
function settleHostSubmission(value){
|
|
@@ -3126,9 +3359,12 @@
|
|
|
3126
3359
|
if(destroyed||context.signal.aborted){
|
|
3127
3360
|
return false;
|
|
3128
3361
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3362
|
+
console.error(
|
|
3363
|
+
internalStructuralToolFailure(error)
|
|
3364
|
+
?'Arcane structural tool protocol failure.'
|
|
3365
|
+
:'The chat message could not be submitted.',
|
|
3366
|
+
error
|
|
3367
|
+
);
|
|
3132
3368
|
dispatchChatEvent(
|
|
3133
3369
|
'chat-send-error',
|
|
3134
3370
|
{error,context:{...context}},
|
|
@@ -3146,7 +3382,6 @@
|
|
|
3146
3382
|
}
|
|
3147
3383
|
}
|
|
3148
3384
|
);
|
|
3149
|
-
console.error('The chat message could not be submitted.',error);
|
|
3150
3385
|
return false;
|
|
3151
3386
|
}
|
|
3152
3387
|
).finally(
|
|
@@ -3173,7 +3408,12 @@
|
|
|
3173
3408
|
if(!host.aiAvailability.llm){
|
|
3174
3409
|
return false;
|
|
3175
3410
|
}
|
|
3176
|
-
if(
|
|
3411
|
+
if(
|
|
3412
|
+
sessionBindingPending
|
|
3413
|
+
||sessionMessagePending
|
|
3414
|
+
||pendingStructuralToolMessage
|
|
3415
|
+
||sessionHistoryRecoveryMessage
|
|
3416
|
+
){
|
|
3177
3417
|
return false;
|
|
3178
3418
|
}
|
|
3179
3419
|
const submissionContext={
|
|
@@ -3333,6 +3573,25 @@
|
|
|
3333
3573
|
return false;
|
|
3334
3574
|
}
|
|
3335
3575
|
|
|
3576
|
+
function handlePageHide(event){
|
|
3577
|
+
if(event.persisted===true){
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
destroy();
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
function handlePageShow(event){
|
|
3584
|
+
if(event.persisted!==true||destroyed){
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
restoreFromPageCache();
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
function restoreFromPageCache(){
|
|
3591
|
+
setAIAvailability();
|
|
3592
|
+
scrollTranscriptToBottom();
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3336
3595
|
function destroy(){
|
|
3337
3596
|
if(destroyed){
|
|
3338
3597
|
return false;
|
|
@@ -3349,7 +3608,8 @@
|
|
|
3349
3608
|
}
|
|
3350
3609
|
aiRuntimeStateAbortController.abort(destroyReason);
|
|
3351
3610
|
aiActivationController.destroy();
|
|
3352
|
-
window.removeEventListener('pagehide',
|
|
3611
|
+
window.removeEventListener('pagehide',handlePageHide);
|
|
3612
|
+
window.removeEventListener('pageshow',handlePageShow);
|
|
3353
3613
|
conversationTimeboxUnsubscribe?.();
|
|
3354
3614
|
conversationTimeboxUnsubscribe=null;
|
|
3355
3615
|
conversationTimeboxController=null;
|
|
@@ -3357,7 +3617,7 @@
|
|
|
3357
3617
|
sessionBindingPending=false;
|
|
3358
3618
|
sessionMessagePending=false;
|
|
3359
3619
|
activeSessionMessageToken=null;
|
|
3360
|
-
|
|
3620
|
+
setPendingStructuralToolCalls();
|
|
3361
3621
|
boundChatSession=null;
|
|
3362
3622
|
boundChatAI=null;
|
|
3363
3623
|
host.session=null;
|