arcane-os 0.3.0 → 0.3.2
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 +27 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
- package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -146
- package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1050 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +40 -62
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +112 -779
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -187
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1252
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -677
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2960
- package/docs/reference/sdk-api.md +0 -6694
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import ChatEntity from '../entities/Chat.js';
|
|
2
|
-
import ConfiguredAIChatSession
|
|
2
|
+
import ConfiguredAIChatSession,{
|
|
3
|
+
normalizeStructuralToolCall
|
|
4
|
+
} from './ConfiguredAIChatSession.js';
|
|
5
|
+
|
|
6
|
+
const SESSION_MANAGED_REQUEST_FIELDS=new Set([
|
|
7
|
+
'messages',
|
|
8
|
+
'onChunk',
|
|
9
|
+
'onResponse',
|
|
10
|
+
'onToolCall',
|
|
11
|
+
'signal',
|
|
12
|
+
'stream',
|
|
13
|
+
]);
|
|
14
|
+
const PROVIDER_LIFECYCLE_FIELDS=new Set(['onChunk','onResponse','onToolCall']);
|
|
3
15
|
|
|
4
16
|
function coded(error,code){
|
|
5
17
|
if(!error.code) error.code=code;
|
|
@@ -33,6 +45,12 @@ function signalLike(value){
|
|
|
33
45
|
);
|
|
34
46
|
}
|
|
35
47
|
|
|
48
|
+
function providerRequestWithoutLifecycleCallbacks(value){
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(value).filter(([key])=>!PROVIDER_LIFECYCLE_FIELDS.has(key))
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
async function configuredArcaneChat(request){
|
|
37
55
|
const api=globalThis.Arcane?.ai;
|
|
38
56
|
if(typeof api?.chat!=='function'){
|
|
@@ -44,9 +62,90 @@ async function configuredArcaneChat(request){
|
|
|
44
62
|
return api.chat(request);
|
|
45
63
|
}
|
|
46
64
|
|
|
65
|
+
function configuredAIChat(ai){
|
|
66
|
+
return function requestConfiguredAI(request){
|
|
67
|
+
return ai.fetchRequest(request);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeStreamHandlers(value){
|
|
72
|
+
if(value===undefined) return {};
|
|
73
|
+
if(!isPlainRecord(value)) throw new TypeError('Persistent chat stream handlers must be a plain object.');
|
|
74
|
+
assertKnownKeys(value,new Set(['onChunk','onToolCall']),'Persistent chat stream handlers');
|
|
75
|
+
for(const key of ['onChunk','onToolCall']){
|
|
76
|
+
if(value[key]!==undefined&&typeof value[key]!=='function'){
|
|
77
|
+
throw new TypeError(`${key} must be a function when provided.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
onChunk:value.onChunk??function ignorePersistentChatChunk(){},
|
|
82
|
+
onToolCall:value.onToolCall??function ignorePersistentChatToolCall(){},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeStreamResponse(terminal,output){
|
|
87
|
+
const value=terminal??output;
|
|
88
|
+
if(typeof value==='string'){
|
|
89
|
+
return {message:{role:'assistant',content:value}};
|
|
90
|
+
}
|
|
91
|
+
if(isPlainRecord(value)) return value;
|
|
92
|
+
throw coded(
|
|
93
|
+
new TypeError('The AI stream did not return a terminal response.'),
|
|
94
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function terminalStructuralToolCall(response){
|
|
99
|
+
const hasMessage=Object.hasOwn(response,'message');
|
|
100
|
+
const hasChoices=Object.hasOwn(response,'choices');
|
|
101
|
+
if(hasMessage&&hasChoices){
|
|
102
|
+
throw coded(
|
|
103
|
+
new TypeError('The AI stream terminal response cannot mix message and choices envelopes.'),
|
|
104
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const messages=hasMessage
|
|
108
|
+
?[response.message]
|
|
109
|
+
:hasChoices&&Array.isArray(response.choices)
|
|
110
|
+
?response.choices.map(choice=>choice?.message).filter(Boolean)
|
|
111
|
+
:[];
|
|
112
|
+
const calls=[];
|
|
113
|
+
for(let messageIndex=0;messageIndex<messages.length;messageIndex++){
|
|
114
|
+
const value=messages[messageIndex]?.tool_calls;
|
|
115
|
+
if(value===undefined) continue;
|
|
116
|
+
if(!Array.isArray(value)){
|
|
117
|
+
throw coded(
|
|
118
|
+
new TypeError('The AI stream terminal response contains invalid structural tool calls.'),
|
|
119
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
for(let callIndex=0;callIndex<value.length;callIndex++){
|
|
123
|
+
calls.push(normalizeStructuralToolCall(
|
|
124
|
+
value[callIndex],
|
|
125
|
+
`The terminal structural tool call ${messageIndex+1}.${callIndex+1}`,
|
|
126
|
+
));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if(calls.length>1){
|
|
130
|
+
throw coded(
|
|
131
|
+
new TypeError('The AI stream terminal response contains parallel structural tool calls.'),
|
|
132
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return calls[0]??null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sameStructuralToolCall(left,right){
|
|
139
|
+
return Boolean(left&&right)
|
|
140
|
+
&&left.id===right.id
|
|
141
|
+
&&left.type===right.type
|
|
142
|
+
&&left.function.name===right.function.name
|
|
143
|
+
&&left.function.arguments===right.function.arguments;
|
|
144
|
+
}
|
|
145
|
+
|
|
47
146
|
function normalizeSend(input){
|
|
48
147
|
if(!isPlainRecord(input)) throw new TypeError('Persistent chat input must be a plain object.');
|
|
49
|
-
assertKnownKeys(input,new Set(['message','response','signal']),'Persistent chat input');
|
|
148
|
+
assertKnownKeys(input,new Set(['message','request','response','signal']),'Persistent chat input');
|
|
50
149
|
if(!isPlainRecord(input.message)) throw new TypeError('message must be a plain object.');
|
|
51
150
|
assertKnownKeys(input.message,new Set(['content','persist','role','tool_call_id']),'message');
|
|
52
151
|
if(typeof input.message.content!=='string'||!input.message.content.trim()){
|
|
@@ -74,17 +173,26 @@ function normalizeSend(input){
|
|
|
74
173
|
'AI_CHAT_INCOHERENT_PERSISTENCE',
|
|
75
174
|
);
|
|
76
175
|
}
|
|
176
|
+
const request=input.request??{};
|
|
177
|
+
if(!isPlainRecord(request)) throw new TypeError('request must be a plain object.');
|
|
178
|
+
const managedRequestField=Object.keys(request).find(
|
|
179
|
+
key=>SESSION_MANAGED_REQUEST_FIELDS.has(key)
|
|
180
|
+
);
|
|
181
|
+
if(managedRequestField){
|
|
182
|
+
throw new TypeError(`request.${managedRequestField} is managed by the chat session.`);
|
|
183
|
+
}
|
|
77
184
|
if(!signalLike(input.signal)) throw new TypeError('signal must be an AbortSignal.');
|
|
78
|
-
return
|
|
185
|
+
return {
|
|
79
186
|
messagePersist,
|
|
80
|
-
requestMessage:
|
|
187
|
+
requestMessage:{
|
|
81
188
|
content:input.message.content,
|
|
82
189
|
role,
|
|
83
190
|
...(toolCallId?{tool_call_id:toolCallId}:{}),
|
|
84
|
-
}
|
|
191
|
+
},
|
|
85
192
|
responsePersist,
|
|
193
|
+
request:{...request},
|
|
86
194
|
signal:input.signal??null,
|
|
87
|
-
}
|
|
195
|
+
};
|
|
88
196
|
}
|
|
89
197
|
|
|
90
198
|
function fileName(value){
|
|
@@ -100,8 +208,10 @@ function fileName(value){
|
|
|
100
208
|
* per-turn persistence affects DBOPFS and memory, never the live model context.
|
|
101
209
|
*/
|
|
102
210
|
class PersistentAIChatSession{
|
|
211
|
+
#activeStream=null;
|
|
103
212
|
#configured=null;
|
|
104
213
|
#entity;
|
|
214
|
+
#fetchChat;
|
|
105
215
|
#memory;
|
|
106
216
|
#options;
|
|
107
217
|
#pending=false;
|
|
@@ -113,7 +223,7 @@ class PersistentAIChatSession{
|
|
|
113
223
|
assertKnownKeys(
|
|
114
224
|
options,
|
|
115
225
|
new Set([
|
|
116
|
-
'chat','chatEntity','chatFileName','contextBuilder','loadExisting','maxContextCharacters',
|
|
226
|
+
'ai','chat','chatEntity','chatFileName','contextBuilder','loadExisting','maxContextCharacters',
|
|
117
227
|
'maxMessageCharacters','maxMessages','memory','request','responseLength','systemPrompt'
|
|
118
228
|
]),
|
|
119
229
|
'Persistent chat options',
|
|
@@ -121,8 +231,48 @@ class PersistentAIChatSession{
|
|
|
121
231
|
if(options.chatEntity!==undefined&&!(options.chatEntity instanceof ChatEntity)){
|
|
122
232
|
throw new TypeError('chatEntity must be a ChatEntity.');
|
|
123
233
|
}
|
|
124
|
-
|
|
234
|
+
if(options.ai!==undefined&&(
|
|
235
|
+
!options.ai
|
|
236
|
+
||typeof options.ai!=='object'
|
|
237
|
+
||typeof options.ai.fetchRequest!=='function'
|
|
238
|
+
)){
|
|
239
|
+
throw new TypeError('ai must expose fetchRequest(request).');
|
|
240
|
+
}
|
|
241
|
+
if(options.ai!==undefined&&options.chat!==undefined){
|
|
242
|
+
throw coded(
|
|
243
|
+
new TypeError('Specify either ai or chat, not both.'),
|
|
244
|
+
'AI_CHAT_AMBIGUOUS_PROVIDER',
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const chat=options.ai===undefined
|
|
248
|
+
?options.chat??configuredArcaneChat
|
|
249
|
+
:configuredAIChat(options.ai);
|
|
125
250
|
if(typeof chat!=='function') throw new TypeError('chat must be a function.');
|
|
251
|
+
if(options.request!==undefined&&!isPlainRecord(options.request)){
|
|
252
|
+
throw new TypeError('request must be a plain object.');
|
|
253
|
+
}
|
|
254
|
+
const request={...(options.request??{})};
|
|
255
|
+
const managedRequestField=Object.keys(request).find(
|
|
256
|
+
key=>SESSION_MANAGED_REQUEST_FIELDS.has(key)
|
|
257
|
+
);
|
|
258
|
+
if(managedRequestField){
|
|
259
|
+
throw new TypeError(`request.${managedRequestField} is managed by the chat session.`);
|
|
260
|
+
}
|
|
261
|
+
if(
|
|
262
|
+
request.parallelToolCalls===true
|
|
263
|
+
||request.parallel_tool_calls===true
|
|
264
|
+
){
|
|
265
|
+
throw coded(
|
|
266
|
+
new TypeError('Persistent chat supports one structural tool call at a time.'),
|
|
267
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if(
|
|
271
|
+
request.parallelToolCalls===undefined
|
|
272
|
+
&&request.parallel_tool_calls===undefined
|
|
273
|
+
){
|
|
274
|
+
request.parallelToolCalls=false;
|
|
275
|
+
}
|
|
126
276
|
const systemPrompt=options.systemPrompt??'';
|
|
127
277
|
if(typeof systemPrompt!=='string') throw new TypeError('systemPrompt must be a string.');
|
|
128
278
|
this.#entity=options.chatEntity??new ChatEntity(systemPrompt);
|
|
@@ -138,7 +288,8 @@ class PersistentAIChatSession{
|
|
|
138
288
|
throw new TypeError('loadExisting requires chatFileName or chatEntity.');
|
|
139
289
|
}
|
|
140
290
|
this.#memory=boolean(options.memory,'memory',true);
|
|
141
|
-
this.#
|
|
291
|
+
this.#fetchChat=chat;
|
|
292
|
+
this.#options={...options,chat,loadExisting,request,systemPrompt};
|
|
142
293
|
this.#readyPromise=this.#initialize();
|
|
143
294
|
}
|
|
144
295
|
|
|
@@ -150,6 +301,54 @@ class PersistentAIChatSession{
|
|
|
150
301
|
|
|
151
302
|
get chatEntity(){return this.#entity;}
|
|
152
303
|
get fileName(){return this.#entity.fileName;}
|
|
304
|
+
get ai(){return this.#options.ai??null;}
|
|
305
|
+
|
|
306
|
+
async #requestConfiguredAI(request){
|
|
307
|
+
const providerRequest=providerRequestWithoutLifecycleCallbacks(request);
|
|
308
|
+
if(!this.#activeStream) return this.#fetchChat(providerRequest);
|
|
309
|
+
if(typeof this.#options.ai?.streamRequest!=='function'){
|
|
310
|
+
return this.#fetchChat(providerRequest);
|
|
311
|
+
}
|
|
312
|
+
const activeStream=this.#activeStream;
|
|
313
|
+
let terminal=null;
|
|
314
|
+
let streamedToolCall=null;
|
|
315
|
+
let streamedToolDetails=[];
|
|
316
|
+
const output=await this.#options.ai.streamRequest({
|
|
317
|
+
...providerRequest,
|
|
318
|
+
onChunk:activeStream.onChunk,
|
|
319
|
+
onToolCall:function retainStructuralToolCall(call,...details){
|
|
320
|
+
const normalized=normalizeStructuralToolCall(
|
|
321
|
+
call,
|
|
322
|
+
'The streamed structural tool call',
|
|
323
|
+
);
|
|
324
|
+
if(streamedToolCall&&!sameStructuralToolCall(streamedToolCall,normalized)){
|
|
325
|
+
throw coded(
|
|
326
|
+
new TypeError('The AI stream emitted divergent structural tool calls.'),
|
|
327
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
streamedToolCall=normalized;
|
|
331
|
+
streamedToolDetails=details;
|
|
332
|
+
},
|
|
333
|
+
onResponse:async function retainPersistentChatTerminal(response){
|
|
334
|
+
terminal=response;
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
const response=normalizeStreamResponse(terminal,output);
|
|
338
|
+
const terminalToolCall=terminalStructuralToolCall(response);
|
|
339
|
+
if(streamedToolCall&&!sameStructuralToolCall(streamedToolCall,terminalToolCall)){
|
|
340
|
+
throw coded(
|
|
341
|
+
new TypeError(
|
|
342
|
+
'The AI stream terminal response omitted or changed its structural tool call.'
|
|
343
|
+
),
|
|
344
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
activeStream.streamedToolCall=streamedToolCall;
|
|
348
|
+
activeStream.streamedToolDetails=streamedToolDetails;
|
|
349
|
+
activeStream.terminalToolCall=terminalToolCall;
|
|
350
|
+
return response;
|
|
351
|
+
}
|
|
153
352
|
|
|
154
353
|
async #initialize(){
|
|
155
354
|
if(this.#options.loadExisting) await this.#entity.load();
|
|
@@ -157,9 +356,12 @@ class PersistentAIChatSession{
|
|
|
157
356
|
const storedSystem=storedMessages.find(message=>message.role==='system');
|
|
158
357
|
const initialMessages=storedMessages
|
|
159
358
|
.filter(message=>['user','assistant','tool'].includes(message.role))
|
|
160
|
-
.map(message=>
|
|
359
|
+
.map(message=>({
|
|
161
360
|
role:message.role,
|
|
162
361
|
content:String(message.content??''),
|
|
362
|
+
...(message.role==='assistant'&&Object.hasOwn(message,'reasoning_content')
|
|
363
|
+
?{reasoning_content:message.reasoning_content}
|
|
364
|
+
:{}),
|
|
163
365
|
...(message.tool_calls?{tool_calls:message.tool_calls}:{}),
|
|
164
366
|
...(message.tool_call_id?{tool_call_id:message.tool_call_id}:{}),
|
|
165
367
|
}));
|
|
@@ -171,12 +373,9 @@ class PersistentAIChatSession{
|
|
|
171
373
|
}
|
|
172
374
|
}
|
|
173
375
|
const configuredOptions={
|
|
174
|
-
chat:this.#
|
|
376
|
+
chat:request=>this.#requestConfiguredAI(request),
|
|
175
377
|
contextBuilder:this.#options.contextBuilder,
|
|
176
378
|
initialMessages,
|
|
177
|
-
maxContextCharacters:this.#options.maxContextCharacters,
|
|
178
|
-
maxMessageCharacters:this.#options.maxMessageCharacters,
|
|
179
|
-
maxMessages:this.#options.maxMessages,
|
|
180
379
|
request:this.#options.request,
|
|
181
380
|
systemPrompt:this.#options.systemPrompt||String(storedSystem?.content??''),
|
|
182
381
|
};
|
|
@@ -200,12 +399,17 @@ class PersistentAIChatSession{
|
|
|
200
399
|
return this.#configured.history();
|
|
201
400
|
}
|
|
202
401
|
|
|
402
|
+
async transcript(){
|
|
403
|
+
await this.ready();
|
|
404
|
+
return this.#entity.transcript;
|
|
405
|
+
}
|
|
406
|
+
|
|
203
407
|
async settleMemory(){
|
|
204
408
|
await this.ready();
|
|
205
409
|
return this.#entity.settleMemory();
|
|
206
410
|
}
|
|
207
411
|
|
|
208
|
-
async
|
|
412
|
+
async #requestTurn(input,streamHandlers=null){
|
|
209
413
|
const settings=normalizeSend(input);
|
|
210
414
|
if(this.#pending){
|
|
211
415
|
throw coded(new Error('A chat request is already active for this session.'),'AI_CHAT_BUSY');
|
|
@@ -233,12 +437,51 @@ class PersistentAIChatSession{
|
|
|
233
437
|
);
|
|
234
438
|
}
|
|
235
439
|
}
|
|
236
|
-
|
|
440
|
+
const streamState=streamHandlers?{
|
|
441
|
+
...streamHandlers,
|
|
442
|
+
streamedToolCall:null,
|
|
443
|
+
streamedToolDetails:[],
|
|
444
|
+
terminalToolCall:null,
|
|
445
|
+
}:null;
|
|
446
|
+
if(streamState) this.#activeStream=streamState;
|
|
447
|
+
try{
|
|
448
|
+
prepared=await this.#configured.prepare(
|
|
449
|
+
settings.requestMessage,
|
|
450
|
+
{request:settings.request,signal:settings.signal},
|
|
451
|
+
);
|
|
452
|
+
}finally{
|
|
453
|
+
if(this.#activeStream===streamState) this.#activeStream=null;
|
|
454
|
+
}
|
|
237
455
|
const result=prepared.response;
|
|
456
|
+
if(streamState){
|
|
457
|
+
const validatedToolCall=result.message.tool_calls?.[0]??null;
|
|
458
|
+
if(
|
|
459
|
+
streamState.terminalToolCall
|
|
460
|
+
&&!sameStructuralToolCall(streamState.terminalToolCall,validatedToolCall)
|
|
461
|
+
){
|
|
462
|
+
throw coded(
|
|
463
|
+
new TypeError(
|
|
464
|
+
'The validated AI response changed its terminal structural tool call.'
|
|
465
|
+
),
|
|
466
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if(validatedToolCall){
|
|
470
|
+
await streamState.onToolCall(
|
|
471
|
+
validatedToolCall,
|
|
472
|
+
...streamState.streamedToolDetails,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
238
476
|
await this.#entity.addTurn({
|
|
239
477
|
assistantMessage:result.message,
|
|
240
478
|
extractMemory:this.#memory&&settings.messagePersist&&settings.responsePersist,
|
|
241
|
-
memoryRequest:messages=>this.#
|
|
479
|
+
memoryRequest:messages=>this.#fetchChat(
|
|
480
|
+
providerRequestWithoutLifecycleCallbacks({
|
|
481
|
+
...this.#options.request,
|
|
482
|
+
messages,
|
|
483
|
+
})
|
|
484
|
+
),
|
|
242
485
|
messagePersist:settings.messagePersist,
|
|
243
486
|
requestMessage:settings.requestMessage,
|
|
244
487
|
responsePersist:settings.responsePersist,
|
|
@@ -250,7 +493,19 @@ class PersistentAIChatSession{
|
|
|
250
493
|
for(const call of result.message.tool_calls??[]){
|
|
251
494
|
this.#toolCallPersistence.set(call.id,settings.responsePersist);
|
|
252
495
|
}
|
|
253
|
-
|
|
496
|
+
const transcript=this.#entity.transcript;
|
|
497
|
+
const assistantRecord=transcript.at(-1);
|
|
498
|
+
return assistantRecord?.role==='assistant'
|
|
499
|
+
?{
|
|
500
|
+
...committed,
|
|
501
|
+
message:{
|
|
502
|
+
...committed.message,
|
|
503
|
+
...(assistantRecord.timestamp!==undefined
|
|
504
|
+
?{timestamp:assistantRecord.timestamp}
|
|
505
|
+
:{}),
|
|
506
|
+
},
|
|
507
|
+
}
|
|
508
|
+
:committed;
|
|
254
509
|
}catch(error){
|
|
255
510
|
prepared?.rollback();
|
|
256
511
|
throw error;
|
|
@@ -258,6 +513,14 @@ class PersistentAIChatSession{
|
|
|
258
513
|
this.#pending=false;
|
|
259
514
|
}
|
|
260
515
|
}
|
|
516
|
+
|
|
517
|
+
async send(input){
|
|
518
|
+
return this.#requestTurn(input);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async stream(input,handlers={}){
|
|
522
|
+
return this.#requestTurn(input,normalizeStreamHandlers(handlers));
|
|
523
|
+
}
|
|
261
524
|
}
|
|
262
525
|
|
|
263
526
|
function createPersistentAIChatSession(options){
|