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
|
@@ -13,30 +13,33 @@ import {
|
|
|
13
13
|
} from './AIProviderRuntime.js';
|
|
14
14
|
import {normalizeOllamaModelIdentifier} from './OllamaModelIdentifier.js';
|
|
15
15
|
|
|
16
|
+
const completeValue=(value)=>value;
|
|
17
|
+
|
|
16
18
|
let credentials='include';
|
|
17
19
|
const LEGACY_TTS_RESPONSE_FORMAT='opus';
|
|
18
20
|
credentials='omit';
|
|
19
21
|
|
|
20
22
|
const LEGACY_AI_SERVICES=new Set(['OPENAI','OLLAMA','LOCAL_SPEACH']);
|
|
21
23
|
export const AI_READY_EVENT='ai-ready';
|
|
22
|
-
|
|
24
|
+
const AI_TTS_FAILURE_EVENT='ai-tts-failure';
|
|
25
|
+
export const AI_INITIALIZATION_ERROR_CODES=completeValue({
|
|
23
26
|
userReadyRegistrationCollision:
|
|
24
27
|
'ARCANE_AI_USER_READY_REGISTRATION_COLLISION'
|
|
25
28
|
});
|
|
26
|
-
export const AI_INITIALIZATION_REASONS=
|
|
29
|
+
export const AI_INITIALIZATION_REASONS=completeValue({
|
|
27
30
|
initialized:'ai-initialized',
|
|
28
31
|
userReadyRegistrationCollision:'ai-user-ready-registration-collision'
|
|
29
32
|
});
|
|
30
33
|
export const AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL=
|
|
31
34
|
'arcane-ai-browser-speech-configuration/1';
|
|
32
|
-
export const AI_BROWSER_SPEECH_EVENT_TYPES=
|
|
35
|
+
export const AI_BROWSER_SPEECH_EVENT_TYPES=completeValue({
|
|
33
36
|
configurationCancelled:'ai-browser-speech-configuration-cancelled',
|
|
34
37
|
configurationError:'ai-browser-speech-configuration-error',
|
|
35
38
|
configurationStarted:'ai-browser-speech-configuration-started',
|
|
36
39
|
configured:'ai-browser-speech-configured',
|
|
37
40
|
disposed:'ai-browser-speech-disposed'
|
|
38
41
|
});
|
|
39
|
-
export const AI_BROWSER_SPEECH_ERROR_CODES=
|
|
42
|
+
export const AI_BROWSER_SPEECH_ERROR_CODES=completeValue({
|
|
40
43
|
artifactStoreConstructionRejected:
|
|
41
44
|
'ARCANE_AI_BROWSER_SPEECH_ARTIFACT_STORE_CONSTRUCTION_REJECTED',
|
|
42
45
|
asyncTransitionRequired:
|
|
@@ -64,7 +67,7 @@ export const AI_BROWSER_SPEECH_ERROR_CODES=Object.freeze({
|
|
|
64
67
|
routeViewUpdateRejected:
|
|
65
68
|
'ARCANE_AI_BROWSER_SPEECH_ROUTE_VIEW_UPDATE_REJECTED'
|
|
66
69
|
});
|
|
67
|
-
export const AI_BROWSER_SPEECH_REASONS=
|
|
70
|
+
export const AI_BROWSER_SPEECH_REASONS=completeValue({
|
|
68
71
|
artifactStoreConstructionRejected:'speech-artifact-store-construction-rejected',
|
|
69
72
|
asyncTransitionRequired:'speech-configuration-async-transition-required',
|
|
70
73
|
configurationAdded:'speech-configuration-added',
|
|
@@ -123,6 +126,282 @@ function legacyAIProviderError(message,code,cause){
|
|
|
123
126
|
return error;
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
function aiStructuralError(code,message,cause){
|
|
130
|
+
const error=cause===undefined
|
|
131
|
+
?new TypeError(message)
|
|
132
|
+
:new TypeError(message,{cause});
|
|
133
|
+
error.code=code;
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isPlainAIRecord(value){
|
|
138
|
+
if(!value||typeof value!=='object'||Array.isArray(value)){
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const prototype=Object.getPrototypeOf(value);
|
|
142
|
+
return prototype===Object.prototype||prototype===null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function requireAIToolMessageSchemas(value,label='AI tools'){
|
|
146
|
+
if(!Array.isArray(value)){
|
|
147
|
+
throw aiStructuralError(
|
|
148
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
149
|
+
`${label} must be an array.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
for(let index=0;index<value.length;index+=1){
|
|
153
|
+
const tool=value[index];
|
|
154
|
+
const parameters=tool?.function?.parameters;
|
|
155
|
+
const messageSchema=parameters?.properties?.message;
|
|
156
|
+
if(
|
|
157
|
+
!isPlainAIRecord(tool)
|
|
158
|
+
||tool.type!=='function'
|
|
159
|
+
||!isPlainAIRecord(tool.function)
|
|
160
|
+
||!isPlainAIRecord(parameters)
|
|
161
|
+
||parameters.type!=='object'
|
|
162
|
+
||!isPlainAIRecord(parameters.properties)
|
|
163
|
+
||!isPlainAIRecord(messageSchema)
|
|
164
|
+
||messageSchema.type!=='string'
|
|
165
|
+
||!Number.isInteger(messageSchema.minLength)
|
|
166
|
+
||messageSchema.minLength<1
|
|
167
|
+
||!Array.isArray(parameters.required)
|
|
168
|
+
||!parameters.required.includes('message')
|
|
169
|
+
){
|
|
170
|
+
throw aiStructuralError(
|
|
171
|
+
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
172
|
+
`${label}[${index}] must require a nonempty string parameters.properties.message.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeAIStructuralToolCall(call,label='Structural tool call'){
|
|
179
|
+
if(
|
|
180
|
+
!isPlainAIRecord(call)
|
|
181
|
+
||typeof call.id!=='string'
|
|
182
|
+
||!call.id.trim()
|
|
183
|
+
||call.type!=='function'
|
|
184
|
+
||!isPlainAIRecord(call.function)
|
|
185
|
+
||typeof call.function.name!=='string'
|
|
186
|
+
||!call.function.name.trim()
|
|
187
|
+
||typeof call.function.arguments!=='string'
|
|
188
|
+
){
|
|
189
|
+
throw aiStructuralError(
|
|
190
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
191
|
+
`${label} is not a complete structural function call.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
let argumentsRecord;
|
|
195
|
+
try{
|
|
196
|
+
argumentsRecord=JSON.parse(call.function.arguments);
|
|
197
|
+
}catch(cause){
|
|
198
|
+
throw aiStructuralError(
|
|
199
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
200
|
+
`${label} arguments must encode a JSON object.`,
|
|
201
|
+
cause
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if(!isPlainAIRecord(argumentsRecord)){
|
|
205
|
+
throw aiStructuralError(
|
|
206
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
207
|
+
`${label} arguments must encode a JSON object.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if(typeof argumentsRecord.message!=='string'||!argumentsRecord.message.trim()){
|
|
211
|
+
throw aiStructuralError(
|
|
212
|
+
'AI_CHAT_TOOL_MESSAGE_REQUIRED',
|
|
213
|
+
`${label} arguments must include a nonempty user-facing message.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
id:call.id,
|
|
218
|
+
type:'function',
|
|
219
|
+
function:{
|
|
220
|
+
name:call.function.name,
|
|
221
|
+
arguments:call.function.arguments
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function validateAIRequestMessages(messages=[]){
|
|
227
|
+
if(!Array.isArray(messages)){
|
|
228
|
+
throw new TypeError('AI messages must be an array.');
|
|
229
|
+
}
|
|
230
|
+
let pendingToolCallId=null;
|
|
231
|
+
for(let messageIndex=0;messageIndex<messages.length;messageIndex+=1){
|
|
232
|
+
const message=messages[messageIndex];
|
|
233
|
+
const calls=message?.tool_calls;
|
|
234
|
+
let openedToolCall=false;
|
|
235
|
+
if(calls!==undefined){
|
|
236
|
+
if(message?.role!=='assistant'||!Array.isArray(calls)){
|
|
237
|
+
throw aiStructuralError(
|
|
238
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
239
|
+
`AI messages[${messageIndex}].tool_calls is invalid.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if(calls.length>1||(pendingToolCallId!==null&&calls.length)){
|
|
243
|
+
throw aiStructuralError(
|
|
244
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
245
|
+
'The Arcane chat session accepts one structural tool call at a time.'
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if(calls.length){
|
|
249
|
+
pendingToolCallId=normalizeAIStructuralToolCall(
|
|
250
|
+
calls[0],
|
|
251
|
+
`AI messages[${messageIndex}].tool_calls[0]`
|
|
252
|
+
).id;
|
|
253
|
+
openedToolCall=true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if(message?.role==='tool'){
|
|
257
|
+
if(typeof message.content!=='string'||!message.content.trim()){
|
|
258
|
+
throw aiStructuralError(
|
|
259
|
+
'AI_CHAT_INVALID_TOOL_MESSAGE',
|
|
260
|
+
`AI messages[${messageIndex}] must contain a nonblank user-facing tool result.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if(
|
|
264
|
+
pendingToolCallId===null
|
|
265
|
+
||typeof message.tool_call_id!=='string'
|
|
266
|
+
||message.tool_call_id!==pendingToolCallId
|
|
267
|
+
){
|
|
268
|
+
throw aiStructuralError(
|
|
269
|
+
'AI_CHAT_INVALID_TOOL_MESSAGE',
|
|
270
|
+
`AI messages[${messageIndex}] does not settle the pending structural tool call.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
pendingToolCallId=null;
|
|
274
|
+
}else if(pendingToolCallId!==null&&!openedToolCall){
|
|
275
|
+
throw aiStructuralError(
|
|
276
|
+
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
277
|
+
`AI messages[${messageIndex}] precedes the pending structural tool result.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if(pendingToolCallId!==null){
|
|
282
|
+
throw aiStructuralError(
|
|
283
|
+
'AI_CHAT_TOOL_RESULT_REQUIRED',
|
|
284
|
+
'The pending structural tool call must be settled before requesting another response.'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function validateAIStructuralRequest(messages,tools,parallelToolCalls){
|
|
290
|
+
validateAIRequestMessages(messages);
|
|
291
|
+
requireAIToolMessageSchemas(tools);
|
|
292
|
+
if(parallelToolCalls===true){
|
|
293
|
+
throw aiStructuralError(
|
|
294
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
295
|
+
'The Arcane chat session accepts one structural tool call at a time.'
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeAICompletionToolCalls(completion){
|
|
301
|
+
const calls=[];
|
|
302
|
+
const hasMessage=Boolean(
|
|
303
|
+
completion
|
|
304
|
+
&&typeof completion==='object'
|
|
305
|
+
&&Object.hasOwn(completion,'message')
|
|
306
|
+
);
|
|
307
|
+
const hasChoices=Boolean(
|
|
308
|
+
completion
|
|
309
|
+
&&typeof completion==='object'
|
|
310
|
+
&&Object.hasOwn(completion,'choices')
|
|
311
|
+
);
|
|
312
|
+
if(hasMessage&&hasChoices){
|
|
313
|
+
throw aiStructuralError(
|
|
314
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
315
|
+
'The AI completion must not mix message and choices envelopes.'
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if(hasChoices&&!Array.isArray(completion.choices)){
|
|
319
|
+
throw aiStructuralError(
|
|
320
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
321
|
+
'The AI completion choices envelope must be an array.'
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const messages=hasChoices
|
|
325
|
+
?completion.choices.map(choice=>choice?.message)
|
|
326
|
+
:hasMessage
|
|
327
|
+
?[completion.message]
|
|
328
|
+
:[];
|
|
329
|
+
for(let messageIndex=0;messageIndex<messages.length;messageIndex+=1){
|
|
330
|
+
const toolCalls=messages[messageIndex]?.tool_calls;
|
|
331
|
+
if(toolCalls!==undefined&&!Array.isArray(toolCalls)){
|
|
332
|
+
throw aiStructuralError(
|
|
333
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
334
|
+
`AI response message ${messageIndex+1} contains invalid structural tool calls.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if(calls.length+(toolCalls?.length??0)>1){
|
|
338
|
+
throw aiStructuralError(
|
|
339
|
+
'AI_CHAT_PARALLEL_TOOLS_UNSUPPORTED',
|
|
340
|
+
'The Arcane chat session accepts one structural tool call at a time.'
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if(messageIndex>0&&toolCalls?.length){
|
|
344
|
+
throw aiStructuralError(
|
|
345
|
+
'AI_CHAT_INVALID_RESPONSE',
|
|
346
|
+
'The AI completion placed a structural tool call outside the selected choice.'
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
for(let callIndex=0;callIndex<(toolCalls??[]).length;callIndex+=1){
|
|
350
|
+
calls.push(normalizeAIStructuralToolCall(
|
|
351
|
+
toolCalls[callIndex],
|
|
352
|
+
`AI response structural tool call ${messageIndex+1}.${callIndex+1}`
|
|
353
|
+
));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return calls;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function sameAIStructuralToolCall(left,right){
|
|
360
|
+
return left?.id===right?.id
|
|
361
|
+
&&left?.type===right?.type
|
|
362
|
+
&&left?.function?.name===right?.function?.name
|
|
363
|
+
&&left?.function?.arguments===right?.function?.arguments;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function assertAIStreamToolCallCorrelation(
|
|
367
|
+
streamedCalls,
|
|
368
|
+
terminalCalls,
|
|
369
|
+
label='AI stream'
|
|
370
|
+
){
|
|
371
|
+
if(!streamedCalls.length){
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if(
|
|
375
|
+
streamedCalls.length!==terminalCalls.length
|
|
376
|
+
||streamedCalls.some(
|
|
377
|
+
(call,index)=>!sameAIStructuralToolCall(call,terminalCalls[index])
|
|
378
|
+
)
|
|
379
|
+
){
|
|
380
|
+
throw aiStructuralError(
|
|
381
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
382
|
+
`${label} changed or omitted its terminal structural tool call.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function normalizeAIStreamToolCallObservation(completion,label){
|
|
388
|
+
try{
|
|
389
|
+
return normalizeAICompletionToolCalls(completion);
|
|
390
|
+
}catch(cause){
|
|
391
|
+
if(
|
|
392
|
+
cause?.code==='AI_CHAT_INVALID_TOOL_CALL'
|
|
393
|
+
||cause?.code==='AI_CHAT_TOOL_MESSAGE_REQUIRED'
|
|
394
|
+
){
|
|
395
|
+
throw aiStructuralError(
|
|
396
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
397
|
+
`${label} did not retain a complete structural tool call.`,
|
|
398
|
+
cause
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
throw cause;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
126
405
|
function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
127
406
|
const controller=new AbortController();
|
|
128
407
|
const queue=[];
|
|
@@ -254,12 +533,13 @@ function createLegacyAIStreamBridge(execute,sourceSignal){
|
|
|
254
533
|
return this;
|
|
255
534
|
}
|
|
256
535
|
};
|
|
257
|
-
return
|
|
536
|
+
return completeValue(handle);
|
|
258
537
|
}
|
|
259
538
|
|
|
260
539
|
function normalizeAIStartupOptions(options){
|
|
261
540
|
if(options===undefined){
|
|
262
|
-
return
|
|
541
|
+
return completeValue({
|
|
542
|
+
startLanguageModel:true,
|
|
263
543
|
startMuted:true,
|
|
264
544
|
startTranscription:false,
|
|
265
545
|
signal:null
|
|
@@ -275,7 +555,8 @@ function normalizeAIStartupOptions(options){
|
|
|
275
555
|
const descriptors=Object.getOwnPropertyDescriptors(options);
|
|
276
556
|
for(const key of Reflect.ownKeys(descriptors)){
|
|
277
557
|
if(typeof key==='symbol'||(
|
|
278
|
-
key!=='
|
|
558
|
+
key!=='startLanguageModel'
|
|
559
|
+
&&key!=='startMuted'
|
|
279
560
|
&&key!=='startTranscription'
|
|
280
561
|
&&key!=='signal'
|
|
281
562
|
)){
|
|
@@ -285,6 +566,9 @@ function normalizeAIStartupOptions(options){
|
|
|
285
566
|
throw new TypeError(`AI startup options.${key} must be a data property.`);
|
|
286
567
|
}
|
|
287
568
|
}
|
|
569
|
+
const startLanguageModel=Object.hasOwn(descriptors,'startLanguageModel')
|
|
570
|
+
?descriptors.startLanguageModel.value
|
|
571
|
+
:true;
|
|
288
572
|
const startMuted=Object.hasOwn(descriptors,'startMuted')
|
|
289
573
|
?descriptors.startMuted.value
|
|
290
574
|
:true;
|
|
@@ -294,6 +578,9 @@ function normalizeAIStartupOptions(options){
|
|
|
294
578
|
const signal=Object.hasOwn(descriptors,'signal')
|
|
295
579
|
?descriptors.signal.value
|
|
296
580
|
:null;
|
|
581
|
+
if(typeof startLanguageModel!=='boolean'){
|
|
582
|
+
throw new TypeError('AI startup startLanguageModel must be a boolean.');
|
|
583
|
+
}
|
|
297
584
|
if(typeof startMuted!=='boolean'){
|
|
298
585
|
throw new TypeError('AI startup startMuted must be a boolean.');
|
|
299
586
|
}
|
|
@@ -308,7 +595,12 @@ function normalizeAIStartupOptions(options){
|
|
|
308
595
|
)){
|
|
309
596
|
throw new TypeError('AI startup signal must be an AbortSignal.');
|
|
310
597
|
}
|
|
311
|
-
return
|
|
598
|
+
return completeValue({
|
|
599
|
+
startLanguageModel,
|
|
600
|
+
startMuted,
|
|
601
|
+
startTranscription,
|
|
602
|
+
signal
|
|
603
|
+
});
|
|
312
604
|
}
|
|
313
605
|
|
|
314
606
|
function aiBrowserSpeechError(code,reason,message,cause,{committed=false,name='Error'}={}){
|
|
@@ -330,16 +622,15 @@ function isAbortSignal(value){
|
|
|
330
622
|
&&typeof value.removeEventListener==='function';
|
|
331
623
|
}
|
|
332
624
|
|
|
333
|
-
function
|
|
625
|
+
function closedRecord(value,keys,required,label){
|
|
334
626
|
if(!value
|
|
335
627
|
||typeof value!=='object'
|
|
336
628
|
||Array.isArray(value)
|
|
337
|
-
||![Object.prototype,null].includes(Object.getPrototypeOf(value))
|
|
338
|
-
||!Object.isFrozen(value)){
|
|
629
|
+
||![Object.prototype,null].includes(Object.getPrototypeOf(value))){
|
|
339
630
|
throw aiBrowserSpeechError(
|
|
340
631
|
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
341
632
|
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
342
|
-
`${label} must be a
|
|
633
|
+
`${label} must be a plain data record.`
|
|
343
634
|
);
|
|
344
635
|
}
|
|
345
636
|
const descriptors=Object.getOwnPropertyDescriptors(value);
|
|
@@ -369,12 +660,11 @@ function frozenClosedRecord(value,keys,required,label){
|
|
|
369
660
|
function browserSpeechIdentifier(value,label){
|
|
370
661
|
if(typeof value!=='string'
|
|
371
662
|
||value.trim()!==value
|
|
372
|
-
||value.length<1
|
|
373
|
-
||value.length>128){
|
|
663
|
+
||value.length<1){
|
|
374
664
|
throw aiBrowserSpeechError(
|
|
375
665
|
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
376
666
|
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
377
|
-
`${label} must be a trimmed
|
|
667
|
+
`${label} must be a nonempty trimmed string.`
|
|
378
668
|
);
|
|
379
669
|
}
|
|
380
670
|
return value;
|
|
@@ -382,7 +672,7 @@ function browserSpeechIdentifier(value,label){
|
|
|
382
672
|
|
|
383
673
|
function normalizeBrowserSpeechRole(value,role){
|
|
384
674
|
const label=`AI browser speech ${role}`;
|
|
385
|
-
const descriptors=
|
|
675
|
+
const descriptors=closedRecord(
|
|
386
676
|
value,
|
|
387
677
|
['providerId','graph','model','runtime','security','offline'],
|
|
388
678
|
['providerId','offline'],
|
|
@@ -396,6 +686,7 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
396
686
|
const hasModel=Object.hasOwn(descriptors,'model');
|
|
397
687
|
const hasRuntime=Object.hasOwn(descriptors,'runtime');
|
|
398
688
|
const hasSecurity=Object.hasOwn(descriptors,'security');
|
|
689
|
+
let secure=false;
|
|
399
690
|
if(hasGraph&&(hasModel||hasRuntime)){
|
|
400
691
|
throw aiBrowserSpeechError(
|
|
401
692
|
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
@@ -412,31 +703,11 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
412
703
|
}
|
|
413
704
|
if(hasGraph){
|
|
414
705
|
const graph=descriptors.graph.value;
|
|
415
|
-
if(!graph||typeof graph!=='object'
|
|
706
|
+
if(!graph||typeof graph!=='object'){
|
|
416
707
|
throw aiBrowserSpeechError(
|
|
417
708
|
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
418
709
|
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
419
|
-
`${label}.graph must be an SDK-created
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
if(!hasSecurity){
|
|
423
|
-
throw aiBrowserSpeechError(
|
|
424
|
-
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
425
|
-
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
426
|
-
`${label}.security with secure:true is required for an artifact graph.`
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
const security=frozenClosedRecord(
|
|
430
|
-
descriptors.security.value,
|
|
431
|
-
['secure','checks'],
|
|
432
|
-
['secure'],
|
|
433
|
-
`${label}.security`
|
|
434
|
-
);
|
|
435
|
-
if(security.secure.value!==true){
|
|
436
|
-
throw aiBrowserSpeechError(
|
|
437
|
-
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
438
|
-
AI_BROWSER_SPEECH_REASONS.configurationContractMismatch,
|
|
439
|
-
`${label}.security.secure must be true for an artifact graph.`
|
|
710
|
+
`${label}.graph must be an SDK-created artifact graph.`
|
|
440
711
|
);
|
|
441
712
|
}
|
|
442
713
|
}else{
|
|
@@ -451,6 +722,12 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
451
722
|
}
|
|
452
723
|
}
|
|
453
724
|
}
|
|
725
|
+
if(hasSecurity){
|
|
726
|
+
// Security is an intent-only seam. Stale or future fields do not run
|
|
727
|
+
// checks in ordinary mode, and secure mode requires user review before
|
|
728
|
+
// an implementation may be enabled.
|
|
729
|
+
secure=descriptors.security.value?.secure===true;
|
|
730
|
+
}
|
|
454
731
|
if(typeof descriptors.offline.value!=='boolean'){
|
|
455
732
|
throw aiBrowserSpeechError(
|
|
456
733
|
AI_BROWSER_SPEECH_ERROR_CODES.configurationContractMismatch,
|
|
@@ -458,24 +735,24 @@ function normalizeBrowserSpeechRole(value,role){
|
|
|
458
735
|
`${label}.offline must be a boolean.`
|
|
459
736
|
);
|
|
460
737
|
}
|
|
461
|
-
return
|
|
738
|
+
return completeValue({
|
|
462
739
|
providerId,
|
|
463
740
|
...(hasGraph
|
|
464
741
|
?{
|
|
465
742
|
graph:descriptors.graph.value,
|
|
466
|
-
security:
|
|
743
|
+
...(secure?{security:{secure:true}}:{})
|
|
467
744
|
}
|
|
468
745
|
:{
|
|
469
746
|
model:descriptors.model.value,
|
|
470
747
|
runtime:descriptors.runtime.value,
|
|
471
|
-
...(
|
|
748
|
+
...(secure?{security:{secure:true}}:{})
|
|
472
749
|
}),
|
|
473
750
|
offline:descriptors.offline.value
|
|
474
751
|
});
|
|
475
752
|
}
|
|
476
753
|
|
|
477
754
|
function normalizeBrowserSpeechConfiguration(value){
|
|
478
|
-
const descriptors=
|
|
755
|
+
const descriptors=closedRecord(
|
|
479
756
|
value,
|
|
480
757
|
['protocol','id','dbopfs','tableName','stt','tts'],
|
|
481
758
|
['protocol','id','dbopfs'],
|
|
@@ -514,12 +791,12 @@ function normalizeBrowserSpeechConfiguration(value){
|
|
|
514
791
|
'AI browser speech configuration must provide stt, tts, or both.'
|
|
515
792
|
);
|
|
516
793
|
}
|
|
517
|
-
return
|
|
794
|
+
return completeValue({
|
|
518
795
|
configuration:value,
|
|
519
796
|
id,
|
|
520
797
|
dbopfs,
|
|
521
798
|
...(tableName?{tableName}:{}),
|
|
522
|
-
roles:
|
|
799
|
+
roles:completeValue(roles),
|
|
523
800
|
...Object.fromEntries(roles.map(role=>[
|
|
524
801
|
role,
|
|
525
802
|
normalizeBrowserSpeechRole(descriptors[role].value,role)
|
|
@@ -528,7 +805,7 @@ function normalizeBrowserSpeechConfiguration(value){
|
|
|
528
805
|
}
|
|
529
806
|
|
|
530
807
|
function normalizeBrowserSpeechOperationOptions(value,label){
|
|
531
|
-
if(value===undefined)return
|
|
808
|
+
if(value===undefined)return completeValue({signal:null});
|
|
532
809
|
if(!value
|
|
533
810
|
||typeof value!=='object'
|
|
534
811
|
||Array.isArray(value)
|
|
@@ -557,7 +834,7 @@ function normalizeBrowserSpeechOperationOptions(value,label){
|
|
|
557
834
|
`${label} signal must be an AbortSignal.`
|
|
558
835
|
);
|
|
559
836
|
}
|
|
560
|
-
return
|
|
837
|
+
return completeValue({signal});
|
|
561
838
|
}
|
|
562
839
|
|
|
563
840
|
class AI {
|
|
@@ -676,9 +953,10 @@ class AI {
|
|
|
676
953
|
this,
|
|
677
954
|
{
|
|
678
955
|
source:'ai',
|
|
679
|
-
eventTypes:
|
|
956
|
+
eventTypes:completeValue(
|
|
680
957
|
[
|
|
681
958
|
AI_READY_EVENT,
|
|
959
|
+
AI_TTS_FAILURE_EVENT,
|
|
682
960
|
...Object.values(AI_BROWSER_SPEECH_EVENT_TYPES)
|
|
683
961
|
]
|
|
684
962
|
)
|
|
@@ -712,7 +990,7 @@ class AI {
|
|
|
712
990
|
#events=null;
|
|
713
991
|
#browserSpeechConfigurationRecord=null;
|
|
714
992
|
#browserSpeechController=null;
|
|
715
|
-
#browserSpeechControllerRoles=
|
|
993
|
+
#browserSpeechControllerRoles=completeValue([]);
|
|
716
994
|
#browserSpeechGeneration=0;
|
|
717
995
|
#browserSpeechModulePromise=null;
|
|
718
996
|
#browserSpeechOperationSequence=0;
|
|
@@ -724,8 +1002,9 @@ class AI {
|
|
|
724
1002
|
#legacySpeechProviders=new Map();
|
|
725
1003
|
#legacySpeechReadiness=Promise.resolve(null);
|
|
726
1004
|
#speechControlGeneration=0;
|
|
1005
|
+
#speechFailureSequence=0;
|
|
727
1006
|
#stopOllamaReady=null;
|
|
728
|
-
#preferenceTuple=
|
|
1007
|
+
#preferenceTuple=completeValue([
|
|
729
1008
|
'OPENAI',
|
|
730
1009
|
'OPENAI',
|
|
731
1010
|
'OPENAI',
|
|
@@ -743,10 +1022,10 @@ class AI {
|
|
|
743
1022
|
const reason=AI_INITIALIZATION_REASONS.initialized;
|
|
744
1023
|
const {occurrence}=this.#events.dispatch(
|
|
745
1024
|
AI_READY_EVENT,
|
|
746
|
-
|
|
1025
|
+
completeValue({db:this,operationId,reason}),
|
|
747
1026
|
{
|
|
748
1027
|
operationId,
|
|
749
|
-
publicDetail:
|
|
1028
|
+
publicDetail:completeValue({
|
|
750
1029
|
ready:true,
|
|
751
1030
|
reason
|
|
752
1031
|
})
|
|
@@ -837,14 +1116,14 @@ class AI {
|
|
|
837
1116
|
||selection.modelId!==this.model
|
|
838
1117
|
||selection.localOnly!==localOnly
|
|
839
1118
|
||this.llmService!==providerId){
|
|
840
|
-
return
|
|
1119
|
+
return completeValue({
|
|
841
1120
|
available:false,
|
|
842
1121
|
code:'ARCANE_AI_MODEL_AUTHORITY_REQUIRED',
|
|
843
1122
|
message:'The selected legacy LLM route does not match the active AI configuration.'
|
|
844
1123
|
});
|
|
845
1124
|
}
|
|
846
1125
|
if(!this.#legacyLLMCapability(providerId)){
|
|
847
|
-
return
|
|
1126
|
+
return completeValue({
|
|
848
1127
|
available:false,
|
|
849
1128
|
code:providerId==='OLLAMA'
|
|
850
1129
|
?'AI_NATIVE_LOCAL_REQUIRED'
|
|
@@ -854,13 +1133,12 @@ class AI {
|
|
|
854
1133
|
:'AI provider is not configured.'
|
|
855
1134
|
});
|
|
856
1135
|
}
|
|
857
|
-
return
|
|
1136
|
+
return completeValue({
|
|
858
1137
|
available:true,
|
|
859
|
-
authority:
|
|
1138
|
+
authority:completeValue({
|
|
860
1139
|
protocol:AI_MODEL_AUTHORITY_PROTOCOL,
|
|
861
1140
|
providerId,
|
|
862
1141
|
modelId:selection.modelId,
|
|
863
|
-
admitted:true
|
|
864
1142
|
})
|
|
865
1143
|
});
|
|
866
1144
|
}
|
|
@@ -877,7 +1155,7 @@ class AI {
|
|
|
877
1155
|
&&!runtime.#legacyLLMCapability(providerId)){
|
|
878
1156
|
state='unloaded';
|
|
879
1157
|
}
|
|
880
|
-
return
|
|
1158
|
+
return completeValue({
|
|
881
1159
|
state,
|
|
882
1160
|
loaded:state==='ready',
|
|
883
1161
|
busy
|
|
@@ -902,17 +1180,17 @@ class AI {
|
|
|
902
1180
|
busy=false;
|
|
903
1181
|
}
|
|
904
1182
|
|
|
905
|
-
return
|
|
1183
|
+
return completeValue({
|
|
906
1184
|
protocol:AI_PROVIDER_PROTOCOL,
|
|
907
1185
|
role:'llm',
|
|
908
1186
|
id:providerId,
|
|
909
1187
|
localOnly,
|
|
910
1188
|
catalog:function catalogLegacyLLMProvider(){
|
|
911
1189
|
if(runtime.llmService!==providerId||!runtime.model){
|
|
912
|
-
return
|
|
1190
|
+
return completeValue([]);
|
|
913
1191
|
}
|
|
914
|
-
return
|
|
915
|
-
|
|
1192
|
+
return completeValue([
|
|
1193
|
+
completeValue({id:runtime.model})
|
|
916
1194
|
]);
|
|
917
1195
|
},
|
|
918
1196
|
inspect:function inspectLegacyLLMProvider(selection,{signal}={}){
|
|
@@ -964,7 +1242,7 @@ class AI {
|
|
|
964
1242
|
unit:'items',
|
|
965
1243
|
heartbeat:false
|
|
966
1244
|
});
|
|
967
|
-
return
|
|
1245
|
+
return completeValue({
|
|
968
1246
|
authority:inspection.authority,
|
|
969
1247
|
status:statusLegacyLLMProvider()
|
|
970
1248
|
});
|
|
@@ -1087,14 +1365,14 @@ class AI {
|
|
|
1087
1365
|
||selection.modelId!==this.#legacySpeechModel(role)
|
|
1088
1366
|
||selection.localOnly!==localOnly
|
|
1089
1367
|
||this.#legacySpeechService(role)!==providerId){
|
|
1090
|
-
return
|
|
1368
|
+
return completeValue({
|
|
1091
1369
|
available:false,
|
|
1092
1370
|
code:'ARCANE_AI_MODEL_AUTHORITY_REQUIRED',
|
|
1093
1371
|
message:`The selected legacy ${role.toUpperCase()} route does not match the active AI configuration.`
|
|
1094
1372
|
});
|
|
1095
1373
|
}
|
|
1096
1374
|
if(!this.#legacySpeechCapability(role,providerId)){
|
|
1097
|
-
return
|
|
1375
|
+
return completeValue({
|
|
1098
1376
|
available:false,
|
|
1099
1377
|
code:providerId==='LOCAL_SPEACH'
|
|
1100
1378
|
?'AI_NATIVE_LOCAL_REQUIRED'
|
|
@@ -1104,13 +1382,12 @@ class AI {
|
|
|
1104
1382
|
:'AI provider is not configured.'
|
|
1105
1383
|
});
|
|
1106
1384
|
}
|
|
1107
|
-
return
|
|
1385
|
+
return completeValue({
|
|
1108
1386
|
available:true,
|
|
1109
|
-
authority:
|
|
1387
|
+
authority:completeValue({
|
|
1110
1388
|
protocol:AI_MODEL_AUTHORITY_PROTOCOL,
|
|
1111
1389
|
providerId,
|
|
1112
1390
|
modelId:selection.modelId,
|
|
1113
|
-
admitted:true
|
|
1114
1391
|
})
|
|
1115
1392
|
});
|
|
1116
1393
|
}
|
|
@@ -1128,7 +1405,7 @@ class AI {
|
|
|
1128
1405
|
&&!runtime.#legacySpeechCapability(role,providerId)){
|
|
1129
1406
|
state='unloaded';
|
|
1130
1407
|
}
|
|
1131
|
-
return
|
|
1408
|
+
return completeValue({
|
|
1132
1409
|
state,
|
|
1133
1410
|
loaded:state==='ready',
|
|
1134
1411
|
busy
|
|
@@ -1154,7 +1431,7 @@ class AI {
|
|
|
1154
1431
|
busy=false;
|
|
1155
1432
|
}
|
|
1156
1433
|
|
|
1157
|
-
return
|
|
1434
|
+
return completeValue({
|
|
1158
1435
|
protocol:AI_PROVIDER_PROTOCOL,
|
|
1159
1436
|
role,
|
|
1160
1437
|
id:providerId,
|
|
@@ -1162,14 +1439,14 @@ class AI {
|
|
|
1162
1439
|
catalog:function catalogLegacySpeechProvider(){
|
|
1163
1440
|
const model=runtime.#legacySpeechModel(role);
|
|
1164
1441
|
if(runtime.#legacySpeechService(role)!==providerId||!model){
|
|
1165
|
-
return
|
|
1442
|
+
return completeValue([]);
|
|
1166
1443
|
}
|
|
1167
1444
|
const defaultVoice=runtime.#legacySpeechDefaultVoice(
|
|
1168
1445
|
role,
|
|
1169
1446
|
providerId
|
|
1170
1447
|
);
|
|
1171
|
-
return
|
|
1172
|
-
|
|
1448
|
+
return completeValue([
|
|
1449
|
+
completeValue({
|
|
1173
1450
|
id:model,
|
|
1174
1451
|
...(defaultVoice?{defaultVoice}:{})
|
|
1175
1452
|
})
|
|
@@ -1224,7 +1501,7 @@ class AI {
|
|
|
1224
1501
|
unit:'items',
|
|
1225
1502
|
heartbeat:false
|
|
1226
1503
|
});
|
|
1227
|
-
return
|
|
1504
|
+
return completeValue({
|
|
1228
1505
|
authority:inspection.authority,
|
|
1229
1506
|
status:statusLegacySpeechProvider()
|
|
1230
1507
|
});
|
|
@@ -1305,7 +1582,7 @@ class AI {
|
|
|
1305
1582
|
const unregister=this.#providerRuntime.register(provider);
|
|
1306
1583
|
this.#legacyLLMProviders.set(
|
|
1307
1584
|
providerId,
|
|
1308
|
-
|
|
1585
|
+
completeValue({provider,unregister})
|
|
1309
1586
|
);
|
|
1310
1587
|
return true;
|
|
1311
1588
|
}
|
|
@@ -1322,7 +1599,7 @@ class AI {
|
|
|
1322
1599
|
const unregister=this.#providerRuntime.register(provider);
|
|
1323
1600
|
this.#legacySpeechProviders.set(
|
|
1324
1601
|
this.#legacySpeechProviderKey(role,providerId),
|
|
1325
|
-
|
|
1602
|
+
completeValue({role,providerId,provider,unregister})
|
|
1326
1603
|
);
|
|
1327
1604
|
return true;
|
|
1328
1605
|
}
|
|
@@ -1569,7 +1846,7 @@ class AI {
|
|
|
1569
1846
|
}
|
|
1570
1847
|
return value;
|
|
1571
1848
|
});
|
|
1572
|
-
return
|
|
1849
|
+
return completeValue(next);
|
|
1573
1850
|
}
|
|
1574
1851
|
|
|
1575
1852
|
#assertValidProviderTuple(tuple){
|
|
@@ -1616,7 +1893,7 @@ class AI {
|
|
|
1616
1893
|
this.modelTTS=this.#ttsModels[modelTTS]||modelTTS;
|
|
1617
1894
|
this.modelSTT=this.#sttModels[modelSTT]||modelSTT;
|
|
1618
1895
|
this.reasoningEffort='';
|
|
1619
|
-
this.#preferenceTuple=
|
|
1896
|
+
this.#preferenceTuple=completeValue(tuple.slice());
|
|
1620
1897
|
}
|
|
1621
1898
|
|
|
1622
1899
|
#applySpeechPreferenceTuple(tuple){
|
|
@@ -1624,14 +1901,14 @@ class AI {
|
|
|
1624
1901
|
this.ttsService=tuple[2];
|
|
1625
1902
|
this.modelTTS=this.#ttsModels[tuple[4]]||tuple[4];
|
|
1626
1903
|
this.modelSTT=this.#sttModels[tuple[5]]||tuple[5];
|
|
1627
|
-
this.#preferenceTuple=
|
|
1904
|
+
this.#preferenceTuple=completeValue(tuple.slice());
|
|
1628
1905
|
}
|
|
1629
1906
|
|
|
1630
1907
|
#tupleFromProviderRoutes(selections){
|
|
1631
1908
|
const llm=selections.llm.default;
|
|
1632
1909
|
const stt=selections.stt.default;
|
|
1633
1910
|
const tts=selections.tts.default;
|
|
1634
|
-
return
|
|
1911
|
+
return completeValue([
|
|
1635
1912
|
llm?.providerId||'',
|
|
1636
1913
|
stt?.providerId||'',
|
|
1637
1914
|
tts?.providerId||'',
|
|
@@ -1645,7 +1922,7 @@ class AI {
|
|
|
1645
1922
|
const current=this.#preferenceTuple;
|
|
1646
1923
|
const stt=selections.stt.default;
|
|
1647
1924
|
const tts=selections.tts.default;
|
|
1648
|
-
return
|
|
1925
|
+
return completeValue([
|
|
1649
1926
|
current[0],
|
|
1650
1927
|
stt?.providerId||'',
|
|
1651
1928
|
tts?.providerId||'',
|
|
@@ -1966,14 +2243,12 @@ class AI {
|
|
|
1966
2243
|
}
|
|
1967
2244
|
|
|
1968
2245
|
#browserSpeechOperationId(action){
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
}
|
|
1976
|
-
this.#browserSpeechOperationSequence+=1;
|
|
2246
|
+
this.#browserSpeechOperationSequence=
|
|
2247
|
+
typeof this.#browserSpeechOperationSequence==='bigint'
|
|
2248
|
+
? this.#browserSpeechOperationSequence+1n
|
|
2249
|
+
: this.#browserSpeechOperationSequence===Number.MAX_SAFE_INTEGER
|
|
2250
|
+
? BigInt(this.#browserSpeechOperationSequence)+1n
|
|
2251
|
+
: this.#browserSpeechOperationSequence+1;
|
|
1977
2252
|
return `${this.#events.instanceId}:${action}:${this.#browserSpeechOperationSequence.toString(36)}`;
|
|
1978
2253
|
}
|
|
1979
2254
|
|
|
@@ -2089,6 +2364,14 @@ class AI {
|
|
|
2089
2364
|
);
|
|
2090
2365
|
if(!record
|
|
2091
2366
|
||!this.#providerRuntime.ownsProvider(role,record.provider)){
|
|
2367
|
+
const pendingIdentity=this.#providerRuntime.providerIdentity(
|
|
2368
|
+
role,
|
|
2369
|
+
selection.providerId
|
|
2370
|
+
);
|
|
2371
|
+
if(pendingIdentity===null&&selection.localOnly===null){
|
|
2372
|
+
expectedProviders[role]=null;
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2092
2375
|
throw this.#browserSpeechProviderRouteOwnershipError(
|
|
2093
2376
|
`The selected ${role} route is not owned by the replaceable AI legacy speech boundary.`
|
|
2094
2377
|
);
|
|
@@ -2096,9 +2379,9 @@ class AI {
|
|
|
2096
2379
|
expectedProviders[role]=record.provider;
|
|
2097
2380
|
legacyRecords.push(record);
|
|
2098
2381
|
}
|
|
2099
|
-
return
|
|
2100
|
-
expectedProviders:
|
|
2101
|
-
legacyRecords:
|
|
2382
|
+
return completeValue({
|
|
2383
|
+
expectedProviders:completeValue(expectedProviders),
|
|
2384
|
+
legacyRecords:completeValue(legacyRecords)
|
|
2102
2385
|
});
|
|
2103
2386
|
}
|
|
2104
2387
|
|
|
@@ -2167,7 +2450,7 @@ class AI {
|
|
|
2167
2450
|
}
|
|
2168
2451
|
|
|
2169
2452
|
#publishBrowserSpeechEvent(type,normalized,operationId,reason,{descriptor=null,error=null}={}){
|
|
2170
|
-
const compatibilityDetail=
|
|
2453
|
+
const compatibilityDetail=completeValue({
|
|
2171
2454
|
configuration:normalized.configuration,
|
|
2172
2455
|
configurationId:normalized.id,
|
|
2173
2456
|
...(descriptor?{descriptor}:{}),
|
|
@@ -2179,7 +2462,7 @@ class AI {
|
|
|
2179
2462
|
compatibilityDetail,
|
|
2180
2463
|
{
|
|
2181
2464
|
operationId,
|
|
2182
|
-
publicDetail:
|
|
2465
|
+
publicDetail:completeValue({
|
|
2183
2466
|
configurationId:normalized.id,
|
|
2184
2467
|
...(descriptor?{descriptor}:{}),
|
|
2185
2468
|
...(typeof error?.code==='string'?{code:error.code}:{}),
|
|
@@ -2191,12 +2474,12 @@ class AI {
|
|
|
2191
2474
|
|
|
2192
2475
|
#browserSpeechRoutes(normalized,providers,previousRecord){
|
|
2193
2476
|
function roleRoutes(provider,catalog){
|
|
2194
|
-
const selection=
|
|
2477
|
+
const selection=completeValue({
|
|
2195
2478
|
providerId:provider.id,
|
|
2196
2479
|
modelId:catalog.id,
|
|
2197
2480
|
localOnly:true
|
|
2198
2481
|
});
|
|
2199
|
-
return
|
|
2482
|
+
return completeValue({default:selection,localOnly:selection});
|
|
2200
2483
|
}
|
|
2201
2484
|
const currentRoutes=this.#currentSpeechRoutes();
|
|
2202
2485
|
const routes={};
|
|
@@ -2220,15 +2503,15 @@ class AI {
|
|
|
2220
2503
|
catalogs[role]=catalog[0];
|
|
2221
2504
|
routes[role]=roleRoutes(providers[role],catalog[0]);
|
|
2222
2505
|
}
|
|
2223
|
-
return
|
|
2224
|
-
routes:
|
|
2225
|
-
catalogs:
|
|
2506
|
+
return completeValue({
|
|
2507
|
+
routes:completeValue(routes),
|
|
2508
|
+
catalogs:completeValue(catalogs)
|
|
2226
2509
|
});
|
|
2227
2510
|
}
|
|
2228
2511
|
|
|
2229
2512
|
#browserSpeechDescriptor(normalized,catalogs,previousRecord){
|
|
2230
2513
|
function roleDescriptor(role,configured,catalog){
|
|
2231
|
-
return
|
|
2514
|
+
return completeValue({
|
|
2232
2515
|
role,
|
|
2233
2516
|
providerId:configured.providerId,
|
|
2234
2517
|
modelId:catalog.id,
|
|
@@ -2245,7 +2528,7 @@ class AI {
|
|
|
2245
2528
|
?roleDescriptor(role,normalized[role],catalogs[role])
|
|
2246
2529
|
:previousRecord?.descriptor[role]??null;
|
|
2247
2530
|
}
|
|
2248
|
-
return
|
|
2531
|
+
return completeValue({
|
|
2249
2532
|
protocol:AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL,
|
|
2250
2533
|
configurationId:normalized.id,
|
|
2251
2534
|
...roles
|
|
@@ -2267,7 +2550,7 @@ class AI {
|
|
|
2267
2550
|
role=>!normalized.roles.includes(role)
|
|
2268
2551
|
);
|
|
2269
2552
|
if(carriedRoles.length===0)return normalized.configuration;
|
|
2270
|
-
return
|
|
2553
|
+
return completeValue({
|
|
2271
2554
|
protocol:AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL,
|
|
2272
2555
|
id:normalized.id,
|
|
2273
2556
|
dbopfs:normalized.dbopfs,
|
|
@@ -2286,12 +2569,12 @@ class AI {
|
|
|
2286
2569
|
}
|
|
2287
2570
|
|
|
2288
2571
|
#currentSpeechRoutes(){
|
|
2289
|
-
return
|
|
2290
|
-
stt:
|
|
2572
|
+
return completeValue({
|
|
2573
|
+
stt:completeValue({
|
|
2291
2574
|
default:this.#providerRuntime.selection('stt'),
|
|
2292
2575
|
localOnly:this.#providerRuntime.selection('stt',{localOnly:true})
|
|
2293
2576
|
}),
|
|
2294
|
-
tts:
|
|
2577
|
+
tts:completeValue({
|
|
2295
2578
|
default:this.#providerRuntime.selection('tts'),
|
|
2296
2579
|
localOnly:this.#providerRuntime.selection('tts',{localOnly:true})
|
|
2297
2580
|
})
|
|
@@ -2299,9 +2582,9 @@ class AI {
|
|
|
2299
2582
|
}
|
|
2300
2583
|
|
|
2301
2584
|
#emptySpeechRoutes(){
|
|
2302
|
-
return
|
|
2303
|
-
stt:
|
|
2304
|
-
tts:
|
|
2585
|
+
return completeValue({
|
|
2586
|
+
stt:completeValue({default:null,localOnly:null}),
|
|
2587
|
+
tts:completeValue({default:null,localOnly:null})
|
|
2305
2588
|
});
|
|
2306
2589
|
}
|
|
2307
2590
|
|
|
@@ -2346,7 +2629,7 @@ class AI {
|
|
|
2346
2629
|
previousRecord
|
|
2347
2630
|
){
|
|
2348
2631
|
const configurationByRole={};
|
|
2349
|
-
const managedRoles=
|
|
2632
|
+
const managedRoles=completeValue(['stt','tts'].filter(role=>
|
|
2350
2633
|
normalized.roles.includes(role)
|
|
2351
2634
|
||previousRecord?.managedRoles.includes(role)
|
|
2352
2635
|
));
|
|
@@ -2357,7 +2640,7 @@ class AI {
|
|
|
2357
2640
|
}
|
|
2358
2641
|
return {
|
|
2359
2642
|
configuration,
|
|
2360
|
-
configurationByRole:
|
|
2643
|
+
configurationByRole:completeValue(configurationByRole),
|
|
2361
2644
|
dbopfs:normalized.dbopfs,
|
|
2362
2645
|
tableName:normalized.tableName,
|
|
2363
2646
|
descriptor,
|
|
@@ -2371,15 +2654,13 @@ class AI {
|
|
|
2371
2654
|
};
|
|
2372
2655
|
}
|
|
2373
2656
|
|
|
2374
|
-
#
|
|
2375
|
-
record.unregisters=
|
|
2376
|
-
|
|
2377
|
-
Object.seal(record.retirementState);
|
|
2378
|
-
return Object.freeze(record);
|
|
2657
|
+
#finalizeBrowserSpeechRecord(record){
|
|
2658
|
+
record.unregisters=completeValue({...record.unregisters});
|
|
2659
|
+
return completeValue(record);
|
|
2379
2660
|
}
|
|
2380
2661
|
|
|
2381
2662
|
#browserSpeechRecordFromReplacement(record,replacement){
|
|
2382
|
-
return this.#
|
|
2663
|
+
return this.#finalizeBrowserSpeechRecord({
|
|
2383
2664
|
configuration:record.configuration,
|
|
2384
2665
|
configurationByRole:record.configurationByRole,
|
|
2385
2666
|
dbopfs:record.dbopfs,
|
|
@@ -2601,22 +2882,16 @@ class AI {
|
|
|
2601
2882
|
candidateProviders[role]=factory({
|
|
2602
2883
|
id:configured.providerId,
|
|
2603
2884
|
...(configured.graph
|
|
2604
|
-
?{
|
|
2605
|
-
graph:configured.graph,
|
|
2606
|
-
security:configured.security
|
|
2607
|
-
}
|
|
2885
|
+
?{graph:configured.graph}
|
|
2608
2886
|
:{
|
|
2609
2887
|
model:configured.model,
|
|
2610
|
-
runtime:configured.runtime
|
|
2611
|
-
...(Object.hasOwn(configured,'security')
|
|
2612
|
-
?{security:configured.security}
|
|
2613
|
-
:{})
|
|
2888
|
+
runtime:configured.runtime
|
|
2614
2889
|
}),
|
|
2615
2890
|
store,
|
|
2616
2891
|
offline:configured.offline
|
|
2617
2892
|
});
|
|
2618
2893
|
}
|
|
2619
|
-
providers=
|
|
2894
|
+
providers=completeValue({...candidateProviders});
|
|
2620
2895
|
prepared=this.#browserSpeechRoutes(
|
|
2621
2896
|
normalized,
|
|
2622
2897
|
providers,
|
|
@@ -2638,7 +2913,7 @@ class AI {
|
|
|
2638
2913
|
const provider=candidateProviders[role];
|
|
2639
2914
|
const changed=normalized.roles.includes(role);
|
|
2640
2915
|
const selection=changed&&provider
|
|
2641
|
-
?
|
|
2916
|
+
?completeValue({
|
|
2642
2917
|
providerId:provider.id,
|
|
2643
2918
|
modelId:normalized[role].graph?.model.id
|
|
2644
2919
|
??normalized[role].model.id,
|
|
@@ -2646,7 +2921,7 @@ class AI {
|
|
|
2646
2921
|
})
|
|
2647
2922
|
:null;
|
|
2648
2923
|
partialRoutes[role]=changed
|
|
2649
|
-
?
|
|
2924
|
+
?completeValue({default:selection,localOnly:selection})
|
|
2650
2925
|
:previousRecord?.managedRoles.includes(role)
|
|
2651
2926
|
?previousRecord.routes[role]
|
|
2652
2927
|
:currentRoutes[role];
|
|
@@ -2655,8 +2930,8 @@ class AI {
|
|
|
2655
2930
|
normalized,
|
|
2656
2931
|
configuration,
|
|
2657
2932
|
null,
|
|
2658
|
-
|
|
2659
|
-
|
|
2933
|
+
completeValue({...candidateProviders}),
|
|
2934
|
+
completeValue(partialRoutes),
|
|
2660
2935
|
previousRecord
|
|
2661
2936
|
);
|
|
2662
2937
|
return this.#throwAfterBrowserSpeechCandidateCleanup(
|
|
@@ -2720,9 +2995,9 @@ class AI {
|
|
|
2720
2995
|
expectedProvider:replacementBoundary.expectedProviders[role]
|
|
2721
2996
|
}
|
|
2722
2997
|
);
|
|
2723
|
-
replacement=
|
|
2998
|
+
replacement=completeValue({
|
|
2724
2999
|
routes:prepared.routes,
|
|
2725
|
-
unregisters:
|
|
3000
|
+
unregisters:completeValue({
|
|
2726
3001
|
stt:role==='stt'
|
|
2727
3002
|
?roleReplacement.unregister
|
|
2728
3003
|
:previousRecord?.unregisters.stt??null,
|
|
@@ -2796,9 +3071,9 @@ class AI {
|
|
|
2796
3071
|
expectedProvider:record.providers[role]
|
|
2797
3072
|
}
|
|
2798
3073
|
);
|
|
2799
|
-
rollback=
|
|
3074
|
+
rollback=completeValue({
|
|
2800
3075
|
routes:previousRecord.routes,
|
|
2801
|
-
unregisters:
|
|
3076
|
+
unregisters:completeValue({
|
|
2802
3077
|
stt:role==='stt'
|
|
2803
3078
|
?roleRollback.unregister
|
|
2804
3079
|
:previousRecord.unregisters.stt,
|
|
@@ -2970,7 +3245,7 @@ class AI {
|
|
|
2970
3245
|
operation.signal?.removeEventListener('abort',forwardAbort);
|
|
2971
3246
|
if(runtime.#browserSpeechController===controller){
|
|
2972
3247
|
runtime.#browserSpeechController=null;
|
|
2973
|
-
runtime.#browserSpeechControllerRoles=
|
|
3248
|
+
runtime.#browserSpeechControllerRoles=completeValue([]);
|
|
2974
3249
|
}
|
|
2975
3250
|
}
|
|
2976
3251
|
}
|
|
@@ -3020,7 +3295,7 @@ class AI {
|
|
|
3020
3295
|
if(invalidatesSpeech)this.#invalidateSpeechControl();
|
|
3021
3296
|
const controller=new AbortController();
|
|
3022
3297
|
this.#browserSpeechController=controller;
|
|
3023
|
-
this.#browserSpeechControllerRoles=
|
|
3298
|
+
this.#browserSpeechControllerRoles=completeValue([]);
|
|
3024
3299
|
const forwardAbort=function cancelBrowserSpeechDisposal(){
|
|
3025
3300
|
if(!controller.signal.aborted)controller.abort(operation.signal.reason);
|
|
3026
3301
|
};
|
|
@@ -3071,7 +3346,7 @@ class AI {
|
|
|
3071
3346
|
}else{
|
|
3072
3347
|
const role=activeRecord.managedRoles[0];
|
|
3073
3348
|
const currentRoutes=runtime.#currentSpeechRoutes();
|
|
3074
|
-
const emptyRoleRoutes=
|
|
3349
|
+
const emptyRoleRoutes=completeValue({
|
|
3075
3350
|
default:null,
|
|
3076
3351
|
localOnly:null
|
|
3077
3352
|
});
|
|
@@ -3083,8 +3358,8 @@ class AI {
|
|
|
3083
3358
|
expectedProvider:activeRecord.providers[role]
|
|
3084
3359
|
}
|
|
3085
3360
|
);
|
|
3086
|
-
removed=
|
|
3087
|
-
routes:
|
|
3361
|
+
removed=completeValue({
|
|
3362
|
+
routes:completeValue({
|
|
3088
3363
|
stt:role==='stt'
|
|
3089
3364
|
?emptyRoleRoutes
|
|
3090
3365
|
:currentRoutes.stt,
|
|
@@ -3174,7 +3449,7 @@ class AI {
|
|
|
3174
3449
|
operation.signal?.removeEventListener('abort',forwardAbort);
|
|
3175
3450
|
if(runtime.#browserSpeechController===controller){
|
|
3176
3451
|
runtime.#browserSpeechController=null;
|
|
3177
|
-
runtime.#browserSpeechControllerRoles=
|
|
3452
|
+
runtime.#browserSpeechControllerRoles=completeValue([]);
|
|
3178
3453
|
}
|
|
3179
3454
|
}
|
|
3180
3455
|
}
|
|
@@ -3254,7 +3529,7 @@ class AI {
|
|
|
3254
3529
|
const errorText=await response.text();
|
|
3255
3530
|
|
|
3256
3531
|
if(errorText&&!errorText.trim().startsWith('<')){
|
|
3257
|
-
detail=errorText
|
|
3532
|
+
detail=errorText;
|
|
3258
3533
|
}
|
|
3259
3534
|
}
|
|
3260
3535
|
}catch{
|
|
@@ -3463,8 +3738,8 @@ class AI {
|
|
|
3463
3738
|
#arrayBufferToBase64(arrayBuffer){
|
|
3464
3739
|
const bytes=new Uint8Array(arrayBuffer);
|
|
3465
3740
|
|
|
3466
|
-
if(!bytes.length
|
|
3467
|
-
throw new RangeError('Microphone audio must be
|
|
3741
|
+
if(!bytes.length){
|
|
3742
|
+
throw new RangeError('Microphone audio must not be empty.');
|
|
3468
3743
|
}
|
|
3469
3744
|
|
|
3470
3745
|
const chunks=[];
|
|
@@ -3477,7 +3752,7 @@ class AI {
|
|
|
3477
3752
|
}
|
|
3478
3753
|
|
|
3479
3754
|
#base64ToBytes(value){
|
|
3480
|
-
if(typeof value!=='string'||!value
|
|
3755
|
+
if(typeof value!=='string'||!value){
|
|
3481
3756
|
throw new TypeError('Arcane returned invalid local speech audio.');
|
|
3482
3757
|
}
|
|
3483
3758
|
|
|
@@ -3524,8 +3799,13 @@ class AI {
|
|
|
3524
3799
|
if(typeof argumentValue==='string'){
|
|
3525
3800
|
try{
|
|
3526
3801
|
argumentValue=JSON.parse(argumentValue);
|
|
3527
|
-
}catch{
|
|
3528
|
-
|
|
3802
|
+
}catch(cause){
|
|
3803
|
+
const error=new TypeError(
|
|
3804
|
+
'Ollama tool call arguments must contain valid JSON.'
|
|
3805
|
+
);
|
|
3806
|
+
error.code='AI_TOOL_ARGUMENTS_INVALID';
|
|
3807
|
+
error.cause=cause;
|
|
3808
|
+
throw error;
|
|
3529
3809
|
}
|
|
3530
3810
|
}
|
|
3531
3811
|
if(
|
|
@@ -3533,7 +3813,11 @@ class AI {
|
|
|
3533
3813
|
||typeof argumentValue!=='object'
|
|
3534
3814
|
||Array.isArray(argumentValue)
|
|
3535
3815
|
){
|
|
3536
|
-
|
|
3816
|
+
const error=new TypeError(
|
|
3817
|
+
'Ollama tool call arguments must contain a JSON object.'
|
|
3818
|
+
);
|
|
3819
|
+
error.code='AI_TOOL_ARGUMENTS_INVALID';
|
|
3820
|
+
throw error;
|
|
3537
3821
|
}
|
|
3538
3822
|
if(callId&&name){
|
|
3539
3823
|
toolNamesByCallId.set(callId,name);
|
|
@@ -3569,7 +3853,7 @@ class AI {
|
|
|
3569
3853
|
});
|
|
3570
3854
|
const requiredName=toolChoice?.function?.name;
|
|
3571
3855
|
const instruction=requiredName
|
|
3572
|
-
?`Call the ${requiredName} function now with
|
|
3856
|
+
?`Call the ${requiredName} function now with complete values for every required field. Do not answer in prose.`
|
|
3573
3857
|
:toolChoice==='none'
|
|
3574
3858
|
?'Do not call a function in this request. Follow the response instructions and answer in the requested format.'
|
|
3575
3859
|
:'';
|
|
@@ -3637,16 +3921,15 @@ class AI {
|
|
|
3637
3921
|
return null;
|
|
3638
3922
|
}
|
|
3639
3923
|
|
|
3640
|
-
async #reportRequest(requestHandler,request,id){
|
|
3924
|
+
async #reportRequest(requestHandler,request,id,metadata){
|
|
3641
3925
|
if(typeof requestHandler!=='function'){
|
|
3642
3926
|
throw new TypeError('AI onRequest callback must be a function.');
|
|
3643
3927
|
}
|
|
3644
|
-
await requestHandler(request,id);
|
|
3928
|
+
await requestHandler(request,id,metadata);
|
|
3645
3929
|
}
|
|
3646
3930
|
|
|
3647
3931
|
#providerStreamEmissions(chunk,seeThinking){
|
|
3648
3932
|
const chunks=[];
|
|
3649
|
-
const toolNames=[];
|
|
3650
3933
|
const choices=Array.isArray(chunk?.choices)?chunk.choices:[];
|
|
3651
3934
|
for(const choice of choices){
|
|
3652
3935
|
const delta=choice?.delta||{};
|
|
@@ -3656,12 +3939,6 @@ class AI {
|
|
|
3656
3939
|
if(typeof delta.content==='string'){
|
|
3657
3940
|
chunks.push({text:delta.content,thinking:false});
|
|
3658
3941
|
}
|
|
3659
|
-
for(const call of Array.isArray(delta.tool_calls)?delta.tool_calls:[]){
|
|
3660
|
-
const name=call?.function?.name;
|
|
3661
|
-
if(typeof name==='string'&&name){
|
|
3662
|
-
toolNames.push(name);
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
3942
|
}
|
|
3666
3943
|
if(!choices.length){
|
|
3667
3944
|
if(seeThinking&&typeof chunk?.thinking==='string'){
|
|
@@ -3675,19 +3952,8 @@ class AI {
|
|
|
3675
3952
|
if(text){
|
|
3676
3953
|
chunks.push({text,thinking:false});
|
|
3677
3954
|
}
|
|
3678
|
-
const calls=Array.isArray(chunk?.toolCalls)
|
|
3679
|
-
?chunk.toolCalls
|
|
3680
|
-
:Array.isArray(chunk?.tool_calls)
|
|
3681
|
-
?chunk.tool_calls
|
|
3682
|
-
:[];
|
|
3683
|
-
for(const call of calls){
|
|
3684
|
-
const name=call?.function?.name||call?.name;
|
|
3685
|
-
if(typeof name==='string'&&name){
|
|
3686
|
-
toolNames.push(name);
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
3955
|
}
|
|
3690
|
-
return {chunks
|
|
3956
|
+
return {chunks};
|
|
3691
3957
|
}
|
|
3692
3958
|
|
|
3693
3959
|
#providerCompletionOutput(completion){
|
|
@@ -3721,7 +3987,7 @@ class AI {
|
|
|
3721
3987
|
payload.structuredOutput??false,
|
|
3722
3988
|
payload.tools??[],
|
|
3723
3989
|
payload.toolChoice??'auto',
|
|
3724
|
-
payload.parallelToolCalls??
|
|
3990
|
+
payload.parallelToolCalls??false,
|
|
3725
3991
|
payload.id??Date.now(),
|
|
3726
3992
|
function ignoreLegacyLLMProviderRequest(){},
|
|
3727
3993
|
signal
|
|
@@ -3740,26 +4006,21 @@ class AI {
|
|
|
3740
4006
|
);
|
|
3741
4007
|
}
|
|
3742
4008
|
|
|
3743
|
-
function emitLegacyLLMStreamTool(name){
|
|
3744
|
-
if(typeof name==='string'&&name){
|
|
3745
|
-
bridge.emit({toolCalls:[{name}]});
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
4009
|
return this.#streamLegacyMessage(
|
|
3750
4010
|
payload.messages??[],
|
|
3751
4011
|
emitLegacyLLMStreamText,
|
|
3752
4012
|
function ignoreLegacyLLMProviderCompletion(){},
|
|
3753
4013
|
payload.tools??[],
|
|
3754
4014
|
payload.toolChoice??'auto',
|
|
3755
|
-
|
|
3756
|
-
payload.parallelToolCalls??
|
|
4015
|
+
function retainLegacyLLMStreamToolUntilCompletion(){},
|
|
4016
|
+
payload.parallelToolCalls??false,
|
|
3757
4017
|
payload.id??Date.now(),
|
|
3758
4018
|
payload.seeThinking??false,
|
|
3759
4019
|
bridge.signal,
|
|
3760
4020
|
function ignoreLegacyLLMProviderRequest(){},
|
|
3761
4021
|
payload.structuredOutput??false,
|
|
3762
|
-
false
|
|
4022
|
+
false,
|
|
4023
|
+
true
|
|
3763
4024
|
);
|
|
3764
4025
|
}
|
|
3765
4026
|
|
|
@@ -3781,27 +4042,78 @@ class AI {
|
|
|
3781
4042
|
}
|
|
3782
4043
|
}
|
|
3783
4044
|
|
|
4045
|
+
#openAICompatibleOllamaToolCalls(value,id=Date.now()){
|
|
4046
|
+
if(value===undefined){
|
|
4047
|
+
return [];
|
|
4048
|
+
}
|
|
4049
|
+
if(!Array.isArray(value)){
|
|
4050
|
+
throw aiStructuralError(
|
|
4051
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
4052
|
+
'The native Ollama response contains invalid structural tool calls.'
|
|
4053
|
+
);
|
|
4054
|
+
}
|
|
4055
|
+
return value.map(function adaptNativeOllamaToolCall(call,index){
|
|
4056
|
+
if(!call||typeof call!=='object'||Array.isArray(call)){
|
|
4057
|
+
throw aiStructuralError(
|
|
4058
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
4059
|
+
`The native Ollama structural tool call ${index+1} is invalid.`
|
|
4060
|
+
);
|
|
4061
|
+
}
|
|
4062
|
+
const nativeFunction=call.function;
|
|
4063
|
+
if(
|
|
4064
|
+
!nativeFunction
|
|
4065
|
+
||typeof nativeFunction!=='object'
|
|
4066
|
+
||Array.isArray(nativeFunction)
|
|
4067
|
+
){
|
|
4068
|
+
throw aiStructuralError(
|
|
4069
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
4070
|
+
`The native Ollama structural tool call ${index+1} is invalid.`
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
let encodedArguments=nativeFunction.arguments;
|
|
4074
|
+
if(
|
|
4075
|
+
encodedArguments
|
|
4076
|
+
&&typeof encodedArguments==='object'
|
|
4077
|
+
&&!Array.isArray(encodedArguments)
|
|
4078
|
+
){
|
|
4079
|
+
try{
|
|
4080
|
+
encodedArguments=JSON.stringify(encodedArguments);
|
|
4081
|
+
}catch(cause){
|
|
4082
|
+
throw aiStructuralError(
|
|
4083
|
+
'AI_CHAT_INVALID_TOOL_CALL',
|
|
4084
|
+
`The native Ollama structural tool call ${index+1} arguments cannot be encoded.`,
|
|
4085
|
+
cause
|
|
4086
|
+
);
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
return {
|
|
4090
|
+
id:call.id===undefined
|
|
4091
|
+
?`ollama-${id}-tool-${index+1}`
|
|
4092
|
+
:call.id,
|
|
4093
|
+
type:call.type===undefined?'function':call.type,
|
|
4094
|
+
function:{
|
|
4095
|
+
name:nativeFunction.name,
|
|
4096
|
+
arguments:encodedArguments
|
|
4097
|
+
}
|
|
4098
|
+
};
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
|
|
3784
4102
|
#openAICompatibleOllamaResponse(response={},id=Date.now()){
|
|
3785
4103
|
const message=response?.message||{};
|
|
3786
|
-
const
|
|
3787
|
-
?
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
:JSON.stringify(call?.function?.arguments||{})
|
|
3797
|
-
}
|
|
3798
|
-
};
|
|
3799
|
-
}
|
|
3800
|
-
)
|
|
3801
|
-
:[];
|
|
4104
|
+
const responseId=typeof response?.id==='string'&&response.id
|
|
4105
|
+
?response.id
|
|
4106
|
+
:`ollama-${id}`;
|
|
4107
|
+
const adaptedToolCalls=this.#openAICompatibleOllamaToolCalls(
|
|
4108
|
+
message.tool_calls,
|
|
4109
|
+
id
|
|
4110
|
+
);
|
|
4111
|
+
const toolCalls=normalizeAICompletionToolCalls({
|
|
4112
|
+
message:{tool_calls:adaptedToolCalls}
|
|
4113
|
+
});
|
|
3802
4114
|
|
|
3803
4115
|
return {
|
|
3804
|
-
id:
|
|
4116
|
+
id:responseId,
|
|
3805
4117
|
object:'chat.completion',
|
|
3806
4118
|
created:Math.floor(Date.now()/1000),
|
|
3807
4119
|
model:response?.model||this.model,
|
|
@@ -3833,15 +4145,27 @@ class AI {
|
|
|
3833
4145
|
localOnly=false,
|
|
3834
4146
|
onChunk=function ignoreStreamChunk(){},
|
|
3835
4147
|
onComplete=function finishIgnoredStream(){},
|
|
4148
|
+
onResponse=function ignoreStreamResponse(){},
|
|
3836
4149
|
tools=[],
|
|
3837
4150
|
toolChoice='auto',
|
|
3838
4151
|
onToolCall=function ignoreEarlyFunction(){},
|
|
3839
4152
|
onRequest=function ignoreStreamRequest(){},
|
|
3840
|
-
parallelToolCalls=
|
|
4153
|
+
parallelToolCalls=false,
|
|
3841
4154
|
id=Date.now(),
|
|
3842
4155
|
seeThinking=false,
|
|
3843
|
-
signal=null
|
|
4156
|
+
signal=null,
|
|
4157
|
+
maxOutputTokens,
|
|
4158
|
+
maxTokens,
|
|
4159
|
+
temperature,
|
|
4160
|
+
topK,
|
|
4161
|
+
topP,
|
|
4162
|
+
repeatPenalty,
|
|
4163
|
+
minP,
|
|
4164
|
+
seed,
|
|
4165
|
+
stop,
|
|
4166
|
+
templateOptions
|
|
3844
4167
|
}={}){
|
|
4168
|
+
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
3845
4169
|
if(localOnly!==true&&localOnly!==false){
|
|
3846
4170
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
3847
4171
|
}
|
|
@@ -3860,10 +4184,20 @@ class AI {
|
|
|
3860
4184
|
toolChoice,
|
|
3861
4185
|
parallelToolCalls,
|
|
3862
4186
|
id,
|
|
3863
|
-
seeThinking
|
|
4187
|
+
seeThinking,
|
|
4188
|
+
...(maxOutputTokens!==undefined
|
|
4189
|
+
?{maxTokens:maxOutputTokens}
|
|
4190
|
+
:maxTokens!==undefined?{maxTokens}:{}),
|
|
4191
|
+
...(temperature!==undefined?{temperature}:{}),
|
|
4192
|
+
...(topK!==undefined?{topK}:{}),
|
|
4193
|
+
...(topP!==undefined?{topP}:{}),
|
|
4194
|
+
...(repeatPenalty!==undefined?{repeatPenalty}:{}),
|
|
4195
|
+
...(minP!==undefined?{minP}:{}),
|
|
4196
|
+
...(seed!==undefined?{seed}:{}),
|
|
4197
|
+
...(stop!==undefined?{stop}:{}),
|
|
4198
|
+
...(templateOptions!==undefined?{templateOptions}:{})
|
|
3864
4199
|
};
|
|
3865
4200
|
const displayId=`M-${id}`;
|
|
3866
|
-
const announcedTools=new Set();
|
|
3867
4201
|
let handle=null;
|
|
3868
4202
|
try{
|
|
3869
4203
|
if(signal?.aborted){
|
|
@@ -3900,40 +4234,22 @@ class AI {
|
|
|
3900
4234
|
throw normalizeAIRequestAbort();
|
|
3901
4235
|
}
|
|
3902
4236
|
}
|
|
3903
|
-
for(const name of emissions.toolNames){
|
|
3904
|
-
if(signal?.aborted){
|
|
3905
|
-
throw normalizeAIRequestAbort();
|
|
3906
|
-
}
|
|
3907
|
-
if(!announcedTools.has(name)){
|
|
3908
|
-
announcedTools.add(name);
|
|
3909
|
-
await onToolCall(name);
|
|
3910
|
-
if(signal?.aborted){
|
|
3911
|
-
throw normalizeAIRequestAbort();
|
|
3912
|
-
}
|
|
3913
|
-
}
|
|
3914
|
-
}
|
|
3915
4237
|
}
|
|
3916
4238
|
const completion=await handle.result;
|
|
3917
4239
|
if(signal?.aborted){
|
|
3918
4240
|
throw normalizeAIRequestAbort();
|
|
3919
4241
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
announcedTools.add(name);
|
|
3932
|
-
await onToolCall(name);
|
|
3933
|
-
if(signal?.aborted){
|
|
3934
|
-
throw normalizeAIRequestAbort();
|
|
3935
|
-
}
|
|
3936
|
-
}
|
|
4242
|
+
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4243
|
+
completion
|
|
4244
|
+
);
|
|
4245
|
+
await onResponse(completion,id,false);
|
|
4246
|
+
for(const call of structuralToolCalls){
|
|
4247
|
+
if(signal?.aborted){
|
|
4248
|
+
throw normalizeAIRequestAbort();
|
|
4249
|
+
}
|
|
4250
|
+
await onToolCall(call,displayId);
|
|
4251
|
+
if(signal?.aborted){
|
|
4252
|
+
throw normalizeAIRequestAbort();
|
|
3937
4253
|
}
|
|
3938
4254
|
}
|
|
3939
4255
|
const result=this.#providerCompletionOutput(completion);
|
|
@@ -3956,20 +4272,52 @@ class AI {
|
|
|
3956
4272
|
}
|
|
3957
4273
|
}
|
|
3958
4274
|
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
4275
|
+
try{
|
|
4276
|
+
const completion=await this.#streamLegacyMessage(
|
|
4277
|
+
messages,
|
|
4278
|
+
onChunk,
|
|
4279
|
+
function retainLegacyCompletionUntilResponse(){},
|
|
4280
|
+
tools,
|
|
4281
|
+
toolChoice,
|
|
4282
|
+
function retainLegacyToolCallUntilResponse(){},
|
|
4283
|
+
parallelToolCalls,
|
|
4284
|
+
id,
|
|
4285
|
+
seeThinking,
|
|
4286
|
+
signal,
|
|
4287
|
+
onRequest,
|
|
4288
|
+
structuredOutput,
|
|
4289
|
+
false,
|
|
4290
|
+
true
|
|
4291
|
+
);
|
|
4292
|
+
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4293
|
+
completion
|
|
4294
|
+
);
|
|
4295
|
+
if(signal?.aborted){
|
|
4296
|
+
throw normalizeAIRequestAbort();
|
|
4297
|
+
}
|
|
4298
|
+
await onResponse(completion,id,false);
|
|
4299
|
+
for(const call of structuralToolCalls){
|
|
4300
|
+
if(signal?.aborted){
|
|
4301
|
+
throw normalizeAIRequestAbort();
|
|
4302
|
+
}
|
|
4303
|
+
await onToolCall(call,`M-${id}`);
|
|
4304
|
+
}
|
|
4305
|
+
const result=this.#providerCompletionOutput(completion);
|
|
4306
|
+
if(signal?.aborted){
|
|
4307
|
+
throw normalizeAIRequestAbort();
|
|
4308
|
+
}
|
|
4309
|
+
await onComplete(result,`M-${id}`,false);
|
|
4310
|
+
if(signal?.aborted){
|
|
4311
|
+
throw normalizeAIRequestAbort();
|
|
4312
|
+
}
|
|
4313
|
+
this.finishTTS();
|
|
4314
|
+
return result;
|
|
4315
|
+
}catch(error){
|
|
4316
|
+
this.stopAudio();
|
|
4317
|
+
throw isAIRequestAbort(error,signal)
|
|
4318
|
+
?normalizeAIRequestAbort(error)
|
|
4319
|
+
:error;
|
|
4320
|
+
}
|
|
3973
4321
|
}
|
|
3974
4322
|
|
|
3975
4323
|
async streamMessage(
|
|
@@ -3979,12 +4327,13 @@ class AI {
|
|
|
3979
4327
|
tools=[],
|
|
3980
4328
|
tool_choice='auto',
|
|
3981
4329
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
3982
|
-
parallel_tool_calls=
|
|
4330
|
+
parallel_tool_calls=false,
|
|
3983
4331
|
id=Date.now(),
|
|
3984
4332
|
seeThinking=false,
|
|
3985
4333
|
signal=null,
|
|
3986
4334
|
requestHandler=function ignoreStreamRequest(){},
|
|
3987
|
-
structuredOutput=false
|
|
4335
|
+
structuredOutput=false,
|
|
4336
|
+
returnCompletion=false
|
|
3988
4337
|
){
|
|
3989
4338
|
if(this.#shouldUseProviderRuntime('llm',this.llmService,false)){
|
|
3990
4339
|
return this.streamRequest({
|
|
@@ -4016,7 +4365,9 @@ class AI {
|
|
|
4016
4365
|
seeThinking,
|
|
4017
4366
|
signal,
|
|
4018
4367
|
requestHandler,
|
|
4019
|
-
structuredOutput
|
|
4368
|
+
structuredOutput,
|
|
4369
|
+
true,
|
|
4370
|
+
returnCompletion
|
|
4020
4371
|
);
|
|
4021
4372
|
}
|
|
4022
4373
|
|
|
@@ -4027,17 +4378,19 @@ class AI {
|
|
|
4027
4378
|
tools=[],
|
|
4028
4379
|
tool_choice='auto',
|
|
4029
4380
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
4030
|
-
parallel_tool_calls=
|
|
4381
|
+
parallel_tool_calls=false,
|
|
4031
4382
|
id=Date.now(),
|
|
4032
4383
|
seeThinking=false,
|
|
4033
4384
|
signal=null,
|
|
4034
4385
|
requestHandler=function ignoreStreamRequest(){},
|
|
4035
4386
|
structuredOutput=false,
|
|
4036
|
-
finishSpeech=true
|
|
4387
|
+
finishSpeech=true,
|
|
4388
|
+
returnCompletion=false
|
|
4037
4389
|
){
|
|
4038
4390
|
let speechTurnCompleted=false;
|
|
4039
4391
|
|
|
4040
4392
|
try{
|
|
4393
|
+
validateAIStructuralRequest(messages,tools,parallel_tool_calls);
|
|
4041
4394
|
this.#assertServiceConfigured(this.llmService);
|
|
4042
4395
|
if(signal&&(
|
|
4043
4396
|
typeof signal.aborted!=='boolean'
|
|
@@ -4077,7 +4430,7 @@ class AI {
|
|
|
4077
4430
|
let isThinking=true;
|
|
4078
4431
|
let isWaiting=true;
|
|
4079
4432
|
|
|
4080
|
-
streamHandler('Thinking...',`M-${id}`,isThinking);
|
|
4433
|
+
await streamHandler('Thinking...',`M-${id}`,isThinking);
|
|
4081
4434
|
|
|
4082
4435
|
const nativeOllama=this.#nativeOllama();
|
|
4083
4436
|
|
|
@@ -4090,8 +4443,7 @@ class AI {
|
|
|
4090
4443
|
|
|
4091
4444
|
if(nativeOllama){
|
|
4092
4445
|
let nativeContent='';
|
|
4093
|
-
const
|
|
4094
|
-
const triggeredTools=new Set();
|
|
4446
|
+
const nativeStreamedToolCallFrames=[];
|
|
4095
4447
|
const ollamaTools=this.#ollamaTools(tools,tool_choice);
|
|
4096
4448
|
const ollamaMessages=this.#ollamaMessages(messages,tool_choice);
|
|
4097
4449
|
const ollamaRequest={
|
|
@@ -4103,46 +4455,19 @@ class AI {
|
|
|
4103
4455
|
...(ollamaTools.length?{tools:ollamaTools}:{})
|
|
4104
4456
|
};
|
|
4105
4457
|
|
|
4106
|
-
function reportEarlyFunctionFailure(error){
|
|
4107
|
-
console.error('Early tool trigger failed.');
|
|
4108
|
-
}
|
|
4109
|
-
|
|
4110
4458
|
function receiveNativeToolCalls(message={}){
|
|
4111
4459
|
const calls=Array.isArray(message.tool_calls)?message.tool_calls:[];
|
|
4112
4460
|
|
|
4113
4461
|
if(calls.length){
|
|
4114
4462
|
isThinking=false;
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
for(const call of calls){
|
|
4118
|
-
const name=call?.function?.name;
|
|
4119
|
-
|
|
4120
|
-
if(!name){
|
|
4121
|
-
continue;
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
nativeToolCalls[name]=typeof call.function.arguments==='string'
|
|
4125
|
-
?call.function.arguments
|
|
4126
|
-
:JSON.stringify(call.function.arguments||{});
|
|
4127
|
-
|
|
4128
|
-
if(!triggeredTools.has(name)){
|
|
4129
|
-
triggeredTools.add(name);
|
|
4130
|
-
Promise.resolve(earlyFunctionTrigger(name)).catch(
|
|
4131
|
-
reportEarlyFunctionFailure
|
|
4132
|
-
);
|
|
4133
|
-
}
|
|
4463
|
+
nativeStreamedToolCallFrames.push(calls.slice());
|
|
4134
4464
|
}
|
|
4135
4465
|
}
|
|
4136
4466
|
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
});
|
|
4142
|
-
const nativeResponse=await nativeOllama.chat(
|
|
4143
|
-
ollamaRequest,
|
|
4144
|
-
{
|
|
4145
|
-
onChunk:function receiveNativeOllamaChunk(chunk){
|
|
4467
|
+
let nativeChunkPipeline=Promise.resolve();
|
|
4468
|
+
function queueNativeOllamaChunk(chunk){
|
|
4469
|
+
nativeChunkPipeline=nativeChunkPipeline.then(
|
|
4470
|
+
async function processNativeOllamaChunk(){
|
|
4146
4471
|
if(signal?.aborted){
|
|
4147
4472
|
return;
|
|
4148
4473
|
}
|
|
@@ -4153,43 +4478,105 @@ class AI {
|
|
|
4153
4478
|
const content=String(message.content||'');
|
|
4154
4479
|
|
|
4155
4480
|
if(thinking){
|
|
4156
|
-
streamHandler(thinking,`M-${id}`,true);
|
|
4481
|
+
await streamHandler(thinking,`M-${id}`,true);
|
|
4157
4482
|
}
|
|
4158
4483
|
|
|
4159
4484
|
if(content){
|
|
4160
4485
|
isThinking=false;
|
|
4161
4486
|
nativeContent+=content;
|
|
4162
|
-
streamHandler(content,`M-${id}`,false);
|
|
4487
|
+
await streamHandler(content,`M-${id}`,false);
|
|
4163
4488
|
}
|
|
4164
4489
|
|
|
4165
4490
|
receiveNativeToolCalls(message);
|
|
4166
|
-
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4491
|
+
}
|
|
4492
|
+
);
|
|
4493
|
+
return nativeChunkPipeline;
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
await this.#reportRequest(requestHandler,ollamaRequest,id,{
|
|
4497
|
+
operation:'stream',
|
|
4498
|
+
transport:'native',
|
|
4499
|
+
destination:'Arcane.ollama.chat'
|
|
4500
|
+
});
|
|
4501
|
+
let nativeResponse;
|
|
4502
|
+
let nativeRequestFailure=null;
|
|
4503
|
+
try{
|
|
4504
|
+
nativeResponse=await nativeOllama.chat(
|
|
4505
|
+
ollamaRequest,
|
|
4506
|
+
{onChunk:queueNativeOllamaChunk,signal}
|
|
4507
|
+
);
|
|
4508
|
+
}catch(error){
|
|
4509
|
+
nativeRequestFailure=error;
|
|
4510
|
+
}
|
|
4511
|
+
try{
|
|
4512
|
+
await nativeChunkPipeline;
|
|
4513
|
+
}catch(error){
|
|
4514
|
+
nativeRequestFailure??=error;
|
|
4515
|
+
}
|
|
4516
|
+
if(nativeRequestFailure){
|
|
4517
|
+
throw nativeRequestFailure;
|
|
4518
|
+
}
|
|
4170
4519
|
if(signal?.aborted){
|
|
4171
4520
|
throw normalizeAIRequestAbort();
|
|
4172
4521
|
}
|
|
4173
|
-
|
|
4522
|
+
const nativeCompletion=this.#openAICompatibleOllamaResponse(
|
|
4523
|
+
{
|
|
4524
|
+
...nativeResponse,
|
|
4525
|
+
message:{
|
|
4526
|
+
...(nativeResponse?.message??{}),
|
|
4527
|
+
content:typeof nativeResponse?.message?.content==='string'
|
|
4528
|
+
&&nativeResponse.message.content
|
|
4529
|
+
?nativeResponse.message.content
|
|
4530
|
+
:nativeContent
|
|
4531
|
+
}
|
|
4532
|
+
},
|
|
4533
|
+
id
|
|
4534
|
+
);
|
|
4535
|
+
const structuralToolCalls=normalizeAICompletionToolCalls(
|
|
4536
|
+
nativeCompletion
|
|
4537
|
+
);
|
|
4538
|
+
for(let frameIndex=0;
|
|
4539
|
+
frameIndex<nativeStreamedToolCallFrames.length;
|
|
4540
|
+
frameIndex+=1
|
|
4541
|
+
){
|
|
4542
|
+
const streamedToolCalls=normalizeAIStreamToolCallObservation(
|
|
4543
|
+
{
|
|
4544
|
+
message:{
|
|
4545
|
+
tool_calls:this.#openAICompatibleOllamaToolCalls(
|
|
4546
|
+
nativeStreamedToolCallFrames[frameIndex],
|
|
4547
|
+
id
|
|
4548
|
+
)
|
|
4549
|
+
}
|
|
4550
|
+
},
|
|
4551
|
+
`The native Ollama stream frame ${frameIndex+1}`
|
|
4552
|
+
);
|
|
4553
|
+
assertAIStreamToolCallCorrelation(
|
|
4554
|
+
streamedToolCalls,
|
|
4555
|
+
structuralToolCalls,
|
|
4556
|
+
'The native Ollama stream'
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4174
4559
|
this.#assertRequiredOllamaToolCall(
|
|
4175
|
-
|
|
4176
|
-
return {function:{name}};
|
|
4177
|
-
}),
|
|
4560
|
+
structuralToolCalls,
|
|
4178
4561
|
tool_choice
|
|
4179
4562
|
);
|
|
4563
|
+
for(const call of structuralToolCalls){
|
|
4564
|
+
if(signal?.aborted){
|
|
4565
|
+
throw normalizeAIRequestAbort();
|
|
4566
|
+
}
|
|
4567
|
+
await earlyFunctionTrigger(call,`M-${id}`);
|
|
4568
|
+
}
|
|
4180
4569
|
|
|
4181
|
-
const nativeResult=
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
if(Object.keys(nativeToolCalls).length&&!nativeContent){
|
|
4185
|
-
streamHandler('',`M-${id}`,false);
|
|
4570
|
+
const nativeResult=this.#providerCompletionOutput(nativeCompletion);
|
|
4571
|
+
if(structuralToolCalls.length&&!nativeContent){
|
|
4572
|
+
await streamHandler('',`M-${id}`,false);
|
|
4186
4573
|
}
|
|
4187
4574
|
if(finishSpeech){
|
|
4188
4575
|
this.finishTTS();
|
|
4189
4576
|
}
|
|
4190
4577
|
await streamComplete(nativeResult,`M-${id}`,isThinking);
|
|
4191
4578
|
speechTurnCompleted=true;
|
|
4192
|
-
return nativeResult;
|
|
4579
|
+
return returnCompletion?nativeCompletion:nativeResult;
|
|
4193
4580
|
}
|
|
4194
4581
|
|
|
4195
4582
|
await this.#reportRequest(requestHandler,request,id,{
|
|
@@ -4230,8 +4617,13 @@ class AI {
|
|
|
4230
4617
|
|
|
4231
4618
|
let chunkString='';
|
|
4232
4619
|
let chunkCache='';
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4620
|
+
const streamedToolCallsByChoice=new Map();
|
|
4621
|
+
let streamCreated=null;
|
|
4622
|
+
let streamFinishReason=null;
|
|
4623
|
+
let streamId=null;
|
|
4624
|
+
let streamModel=null;
|
|
4625
|
+
let streamObject=null;
|
|
4626
|
+
let streamUsage=null;
|
|
4235
4627
|
const decoder = new TextDecoder('utf-8');
|
|
4236
4628
|
//alert(1)
|
|
4237
4629
|
const reader=response.body?.getReader?.();
|
|
@@ -4240,41 +4632,63 @@ class AI {
|
|
|
4240
4632
|
throw new TypeError('Streaming response body is not readable');
|
|
4241
4633
|
}
|
|
4242
4634
|
|
|
4243
|
-
function receiveStreamedToolCalls(toolCalls=[]){
|
|
4635
|
+
function receiveStreamedToolCalls(toolCalls=[],choicePosition=0){
|
|
4636
|
+
const streamedToolCalls=streamedToolCallsByChoice.get(choicePosition)
|
|
4637
|
+
||new Map();
|
|
4244
4638
|
for(let position=0;position<toolCalls.length;position++){
|
|
4245
4639
|
const toolCall=toolCalls[position]||{};
|
|
4246
4640
|
const toolFunction=toolCall.function||{};
|
|
4247
|
-
const key
|
|
4248
|
-
|
|
4249
|
-
:
|
|
4250
|
-
?`id:${toolCall.id}`
|
|
4251
|
-
:`position:${position}`;
|
|
4641
|
+
const key=`position:${Number.isInteger(toolCall.index)
|
|
4642
|
+
?toolCall.index
|
|
4643
|
+
:position}`;
|
|
4252
4644
|
const record=streamedToolCalls.get(key)||{
|
|
4253
4645
|
arguments:'',
|
|
4646
|
+
id:'',
|
|
4647
|
+
invalidArguments:false,
|
|
4648
|
+
invalidIdentity:false,
|
|
4649
|
+
invalidName:false,
|
|
4254
4650
|
name:'',
|
|
4255
|
-
order:streamedToolCalls.size
|
|
4651
|
+
order:streamedToolCalls.size,
|
|
4652
|
+
type:''
|
|
4256
4653
|
};
|
|
4257
4654
|
|
|
4258
|
-
if(
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4655
|
+
if(toolCall.id!==undefined){
|
|
4656
|
+
if(typeof toolCall.id!=='string'||!toolCall.id){
|
|
4657
|
+
record.invalidIdentity=true;
|
|
4658
|
+
}else if(record.id&&record.id!==toolCall.id){
|
|
4659
|
+
record.invalidIdentity=true;
|
|
4660
|
+
}else{
|
|
4661
|
+
record.id=toolCall.id;
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
if(toolCall.type!==undefined){
|
|
4666
|
+
if(typeof toolCall.type!=='string'||!toolCall.type){
|
|
4667
|
+
record.invalidIdentity=true;
|
|
4668
|
+
}else if(record.type&&record.type!==toolCall.type){
|
|
4669
|
+
record.invalidIdentity=true;
|
|
4670
|
+
}else{
|
|
4671
|
+
record.type=toolCall.type;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
if(toolFunction.name!==undefined){
|
|
4676
|
+
if(typeof toolFunction.name!=='string'){
|
|
4677
|
+
record.invalidName=true;
|
|
4678
|
+
}else{
|
|
4679
|
+
record.name+=toolFunction.name;
|
|
4267
4680
|
}
|
|
4268
4681
|
}
|
|
4269
4682
|
|
|
4270
4683
|
if(typeof toolFunction.arguments==='string'){
|
|
4271
4684
|
record.arguments+=toolFunction.arguments;
|
|
4272
|
-
}else if(toolFunction.arguments
|
|
4273
|
-
record.
|
|
4685
|
+
}else if(toolFunction.arguments!==undefined){
|
|
4686
|
+
record.invalidArguments=true;
|
|
4274
4687
|
}
|
|
4275
4688
|
|
|
4276
4689
|
streamedToolCalls.set(key,record);
|
|
4277
4690
|
}
|
|
4691
|
+
streamedToolCallsByChoice.set(choicePosition,streamedToolCalls);
|
|
4278
4692
|
}
|
|
4279
4693
|
|
|
4280
4694
|
try{
|
|
@@ -4295,64 +4709,88 @@ class AI {
|
|
|
4295
4709
|
//alert(3)
|
|
4296
4710
|
//console.log(lines);
|
|
4297
4711
|
|
|
4298
|
-
lines
|
|
4299
|
-
|
|
4300
|
-
chunkCache+=delta;
|
|
4712
|
+
for(const eventData of lines){
|
|
4713
|
+
chunkCache+=eventData;
|
|
4301
4714
|
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4715
|
+
if(chunkCache.trim()==='[DONE]'){
|
|
4716
|
+
chunkCache='';
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
let streamedResponse;
|
|
4721
|
+
try{
|
|
4722
|
+
streamedResponse=JSON.parse(chunkCache)||{};
|
|
4723
|
+
}catch{
|
|
4724
|
+
continue;
|
|
4725
|
+
}
|
|
4726
|
+
chunkCache='';
|
|
4727
|
+
if(streamedResponse.id!==undefined){
|
|
4728
|
+
streamId=streamedResponse.id;
|
|
4729
|
+
}
|
|
4730
|
+
if(streamedResponse.object!==undefined){
|
|
4731
|
+
streamObject=streamedResponse.object;
|
|
4732
|
+
}
|
|
4733
|
+
if(streamedResponse.created!==undefined){
|
|
4734
|
+
streamCreated=streamedResponse.created;
|
|
4735
|
+
}
|
|
4736
|
+
if(streamedResponse.model!==undefined){
|
|
4737
|
+
streamModel=streamedResponse.model;
|
|
4738
|
+
}
|
|
4739
|
+
if(streamedResponse.usage!==undefined){
|
|
4740
|
+
streamUsage=streamedResponse.usage;
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
const choices=Array.isArray(streamedResponse.choices)
|
|
4744
|
+
?streamedResponse.choices
|
|
4745
|
+
:[];
|
|
4746
|
+
for(let choicePosition=0;
|
|
4747
|
+
choicePosition<choices.length;
|
|
4748
|
+
choicePosition+=1
|
|
4749
|
+
){
|
|
4750
|
+
const choice=choices[choicePosition]||{};
|
|
4751
|
+
const choiceDelta=choice.delta||{};
|
|
4752
|
+
const toolCalls=choiceDelta.tool_calls;
|
|
4753
|
+
if(toolCalls!==undefined&&!Array.isArray(toolCalls)){
|
|
4754
|
+
throw aiStructuralError(
|
|
4755
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4756
|
+
`AI stream choice ${choicePosition+1} contains invalid structural tool-call data.`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
4759
|
+
if(toolCalls?.length){
|
|
4760
|
+
receiveStreamedToolCalls(
|
|
4761
|
+
toolCalls,
|
|
4762
|
+
choicePosition
|
|
4763
|
+
);
|
|
4764
|
+
}
|
|
4765
|
+
if(choicePosition!==0){
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
if(choice.finish_reason!==undefined){
|
|
4769
|
+
streamFinishReason=choice.finish_reason;
|
|
4305
4770
|
}
|
|
4306
4771
|
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
}
|
|
4327
|
-
|
|
4328
|
-
if (!reasoning && isThinking) {
|
|
4329
|
-
//remove thinking chunks
|
|
4330
|
-
chunkString='';
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
if (!reasoning) {
|
|
4334
|
-
isThinking = false;
|
|
4335
|
-
}
|
|
4336
|
-
|
|
4337
|
-
chunkCache='';
|
|
4338
|
-
|
|
4339
|
-
if(value==='' && !tool_calls.length){
|
|
4340
|
-
return;
|
|
4341
|
-
}
|
|
4342
|
-
|
|
4343
|
-
if(value){
|
|
4344
|
-
streamHandler(value,`M-${id}`, isThinking);
|
|
4345
|
-
chunkString+=value;
|
|
4346
|
-
}
|
|
4347
|
-
|
|
4348
|
-
if(tool_calls.length){
|
|
4349
|
-
receiveStreamedToolCalls(tool_calls);
|
|
4350
|
-
}
|
|
4351
|
-
} catch(err) {
|
|
4352
|
-
console.warn('AI stream callback failed.');
|
|
4772
|
+
const content=choiceDelta.content||'';
|
|
4773
|
+
let value=content;
|
|
4774
|
+
let reasoning='';
|
|
4775
|
+
if(seeThinking){
|
|
4776
|
+
reasoning=choiceDelta.reasoning||'';
|
|
4777
|
+
}
|
|
4778
|
+
if(reasoning){
|
|
4779
|
+
isThinking=true;
|
|
4780
|
+
value=reasoning;
|
|
4781
|
+
}
|
|
4782
|
+
if(!reasoning&&isThinking){
|
|
4783
|
+
chunkString='';
|
|
4784
|
+
}
|
|
4785
|
+
if(!reasoning){
|
|
4786
|
+
isThinking=false;
|
|
4787
|
+
}
|
|
4788
|
+
if(value){
|
|
4789
|
+
await streamHandler(value,`M-${id}`,isThinking);
|
|
4790
|
+
chunkString+=value;
|
|
4353
4791
|
}
|
|
4354
4792
|
}
|
|
4355
|
-
|
|
4793
|
+
}
|
|
4356
4794
|
}
|
|
4357
4795
|
}catch(error){
|
|
4358
4796
|
if(isAIRequestAbort(error,signal)){
|
|
@@ -4363,30 +4801,108 @@ class AI {
|
|
|
4363
4801
|
reader.releaseLock();
|
|
4364
4802
|
}
|
|
4365
4803
|
|
|
4366
|
-
const
|
|
4367
|
-
const
|
|
4368
|
-
function
|
|
4369
|
-
return a
|
|
4804
|
+
const structuralToolCallsByChoice=new Map();
|
|
4805
|
+
const orderedStreamChoices=[...streamedToolCallsByChoice.entries()].sort(
|
|
4806
|
+
function sortStreamChoices(a,b){
|
|
4807
|
+
return a[0]-b[0];
|
|
4370
4808
|
}
|
|
4371
4809
|
);
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4810
|
+
for(const [choicePosition,toolCallRecords] of orderedStreamChoices){
|
|
4811
|
+
const orderedToolCalls=[...toolCallRecords.values()].sort(
|
|
4812
|
+
function sortStreamedToolCalls(a,b){
|
|
4813
|
+
return a.order-b.order;
|
|
4814
|
+
}
|
|
4815
|
+
);
|
|
4816
|
+
const rawToolCalls=orderedToolCalls.map(
|
|
4817
|
+
function completeStreamedToolCall(toolCall,index){
|
|
4818
|
+
if(
|
|
4819
|
+
toolCall.invalidArguments
|
|
4820
|
+
||toolCall.invalidIdentity
|
|
4821
|
+
||toolCall.invalidName
|
|
4822
|
+
){
|
|
4823
|
+
throw aiStructuralError(
|
|
4824
|
+
'AI_CHAT_STREAM_TOOL_CALL_MISMATCH',
|
|
4825
|
+
`AI stream choice ${choicePosition+1} structural tool call ${index+1} changed an exact field.`
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
return {
|
|
4829
|
+
id:toolCall.id,
|
|
4830
|
+
type:toolCall.type,
|
|
4831
|
+
function:{
|
|
4832
|
+
name:toolCall.name,
|
|
4833
|
+
arguments:toolCall.arguments
|
|
4834
|
+
}
|
|
4835
|
+
};
|
|
4836
|
+
}
|
|
4837
|
+
);
|
|
4838
|
+
structuralToolCallsByChoice.set(
|
|
4839
|
+
choicePosition,
|
|
4840
|
+
normalizeAIStreamToolCallObservation(
|
|
4841
|
+
{message:{tool_calls:rawToolCalls}},
|
|
4842
|
+
`AI stream choice ${choicePosition+1}`
|
|
4843
|
+
)
|
|
4844
|
+
);
|
|
4845
|
+
}
|
|
4846
|
+
const structuralToolCalls=structuralToolCallsByChoice.get(0)||[];
|
|
4847
|
+
const completionChoices=[
|
|
4848
|
+
{
|
|
4849
|
+
index:0,
|
|
4850
|
+
message:{
|
|
4851
|
+
role:'assistant',
|
|
4852
|
+
content:chunkString,
|
|
4853
|
+
...(structuralToolCalls.length
|
|
4854
|
+
?{tool_calls:structuralToolCalls}
|
|
4855
|
+
:{})
|
|
4856
|
+
},
|
|
4857
|
+
finish_reason:typeof streamFinishReason==='string'
|
|
4858
|
+
&&streamFinishReason
|
|
4859
|
+
?streamFinishReason
|
|
4860
|
+
:structuralToolCalls.length?'tool_calls':'stop'
|
|
4376
4861
|
}
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4862
|
+
];
|
|
4863
|
+
for(const [choicePosition,toolCalls] of structuralToolCallsByChoice){
|
|
4864
|
+
if(choicePosition===0){
|
|
4865
|
+
continue;
|
|
4380
4866
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4867
|
+
completionChoices.push({
|
|
4868
|
+
index:choicePosition,
|
|
4869
|
+
message:{
|
|
4870
|
+
role:'assistant',
|
|
4871
|
+
content:'',
|
|
4872
|
+
...(toolCalls.length?{tool_calls:toolCalls}:{})
|
|
4873
|
+
},
|
|
4874
|
+
finish_reason:toolCalls.length?'tool_calls':'stop'
|
|
4875
|
+
});
|
|
4383
4876
|
}
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4877
|
+
const completion={
|
|
4878
|
+
id:typeof streamId==='string'&&streamId?streamId:`legacy-${id}`,
|
|
4879
|
+
object:typeof streamObject==='string'&&streamObject
|
|
4880
|
+
?streamObject
|
|
4881
|
+
:'chat.completion',
|
|
4882
|
+
created:Number.isSafeInteger(streamCreated)
|
|
4883
|
+
?streamCreated
|
|
4884
|
+
:Math.floor(Date.now()/1000),
|
|
4885
|
+
model:typeof streamModel==='string'&&streamModel
|
|
4886
|
+
?streamModel
|
|
4887
|
+
:this.model,
|
|
4888
|
+
choices:completionChoices,
|
|
4889
|
+
...(isPlainAIRecord(streamUsage)?{usage:streamUsage}:{})
|
|
4890
|
+
};
|
|
4891
|
+
const terminalToolCalls=normalizeAICompletionToolCalls(completion);
|
|
4892
|
+
assertAIStreamToolCallCorrelation(
|
|
4893
|
+
structuralToolCalls,
|
|
4894
|
+
terminalToolCalls,
|
|
4895
|
+
'The legacy HTTP stream'
|
|
4896
|
+
);
|
|
4897
|
+
for(const call of terminalToolCalls){
|
|
4898
|
+
if(signal?.aborted){
|
|
4899
|
+
throw normalizeAIRequestAbort();
|
|
4900
|
+
}
|
|
4901
|
+
await earlyFunctionTrigger(call,`M-${id}`);
|
|
4902
|
+
}
|
|
4903
|
+
const streamResult=this.#providerCompletionOutput(completion);
|
|
4904
|
+
if(structuralToolCalls.length&&!chunkString){
|
|
4905
|
+
await streamHandler('',`M-${id}`,false);
|
|
4390
4906
|
}
|
|
4391
4907
|
if(finishSpeech){
|
|
4392
4908
|
this.finishTTS();
|
|
@@ -4395,7 +4911,7 @@ class AI {
|
|
|
4395
4911
|
|
|
4396
4912
|
//sync
|
|
4397
4913
|
speechTurnCompleted=true;
|
|
4398
|
-
return streamResult;
|
|
4914
|
+
return returnCompletion?completion:streamResult;
|
|
4399
4915
|
}catch(error){
|
|
4400
4916
|
if(isAIRequestAbort(error,signal)){
|
|
4401
4917
|
throw normalizeAIRequestAbort(error);
|
|
@@ -4414,12 +4930,23 @@ class AI {
|
|
|
4414
4930
|
localOnly=false,
|
|
4415
4931
|
tools=[],
|
|
4416
4932
|
toolChoice='auto',
|
|
4417
|
-
parallelToolCalls=
|
|
4933
|
+
parallelToolCalls=false,
|
|
4418
4934
|
id=Date.now(),
|
|
4419
4935
|
signal=null,
|
|
4420
4936
|
onRequest=function ignoreFetchRequest(){},
|
|
4421
|
-
onResponse=function ignoreFetchResponse(){}
|
|
4937
|
+
onResponse=function ignoreFetchResponse(){},
|
|
4938
|
+
maxOutputTokens,
|
|
4939
|
+
maxTokens,
|
|
4940
|
+
temperature,
|
|
4941
|
+
topK,
|
|
4942
|
+
topP,
|
|
4943
|
+
repeatPenalty,
|
|
4944
|
+
minP,
|
|
4945
|
+
seed,
|
|
4946
|
+
stop,
|
|
4947
|
+
templateOptions
|
|
4422
4948
|
}={}){
|
|
4949
|
+
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
4423
4950
|
if(localOnly!==true&&localOnly!==false){
|
|
4424
4951
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
4425
4952
|
}
|
|
@@ -4440,7 +4967,18 @@ class AI {
|
|
|
4440
4967
|
tools,
|
|
4441
4968
|
toolChoice,
|
|
4442
4969
|
parallelToolCalls,
|
|
4443
|
-
id
|
|
4970
|
+
id,
|
|
4971
|
+
...(maxOutputTokens!==undefined
|
|
4972
|
+
?{maxTokens:maxOutputTokens}
|
|
4973
|
+
:maxTokens!==undefined?{maxTokens}:{}),
|
|
4974
|
+
...(temperature!==undefined?{temperature}:{}),
|
|
4975
|
+
...(topK!==undefined?{topK}:{}),
|
|
4976
|
+
...(topP!==undefined?{topP}:{}),
|
|
4977
|
+
...(repeatPenalty!==undefined?{repeatPenalty}:{}),
|
|
4978
|
+
...(minP!==undefined?{minP}:{}),
|
|
4979
|
+
...(seed!==undefined?{seed}:{}),
|
|
4980
|
+
...(stop!==undefined?{stop}:{}),
|
|
4981
|
+
...(templateOptions!==undefined?{templateOptions}:{})
|
|
4444
4982
|
};
|
|
4445
4983
|
await this.#reportRequest(onRequest,request,id);
|
|
4446
4984
|
if(signal?.aborted){
|
|
@@ -4458,6 +4996,7 @@ class AI {
|
|
|
4458
4996
|
if(signal?.aborted){
|
|
4459
4997
|
throw normalizeAIRequestAbort();
|
|
4460
4998
|
}
|
|
4999
|
+
normalizeAICompletionToolCalls(response);
|
|
4461
5000
|
await onResponse(response,id,false);
|
|
4462
5001
|
if(signal?.aborted){
|
|
4463
5002
|
throw normalizeAIRequestAbort();
|
|
@@ -4484,7 +5023,7 @@ class AI {
|
|
|
4484
5023
|
structuredOutput=false,
|
|
4485
5024
|
tools=[],
|
|
4486
5025
|
tool_choice='auto',
|
|
4487
|
-
parallel_tool_calls=
|
|
5026
|
+
parallel_tool_calls=false,
|
|
4488
5027
|
id=Date.now(),
|
|
4489
5028
|
requestHandler=function ignoreFetchRequest(){},
|
|
4490
5029
|
signal=null,
|
|
@@ -4523,11 +5062,12 @@ class AI {
|
|
|
4523
5062
|
structuredOutput=false,
|
|
4524
5063
|
tools=[],
|
|
4525
5064
|
tool_choice='auto',
|
|
4526
|
-
parallel_tool_calls=
|
|
5065
|
+
parallel_tool_calls=false,
|
|
4527
5066
|
id=Date.now(),
|
|
4528
5067
|
requestHandler=function ignoreFetchRequest(){},
|
|
4529
5068
|
signal=null,
|
|
4530
5069
|
){
|
|
5070
|
+
validateAIStructuralRequest(messages,tools,parallel_tool_calls);
|
|
4531
5071
|
this.#assertServiceConfigured(this.llmService);
|
|
4532
5072
|
if(signal&&(
|
|
4533
5073
|
typeof signal.aborted!=='boolean'
|
|
@@ -4602,16 +5142,14 @@ class AI {
|
|
|
4602
5142
|
if(signal?.aborted){
|
|
4603
5143
|
throw normalizeAIRequestAbort();
|
|
4604
5144
|
}
|
|
4605
|
-
this.#assertRequiredOllamaToolCall(
|
|
4606
|
-
Array.isArray(nativeResponse?.message?.tool_calls)
|
|
4607
|
-
?nativeResponse.message.tool_calls
|
|
4608
|
-
:[],
|
|
4609
|
-
tool_choice
|
|
4610
|
-
);
|
|
4611
5145
|
const responseJSON=this.#openAICompatibleOllamaResponse(
|
|
4612
5146
|
nativeResponse,
|
|
4613
5147
|
id
|
|
4614
5148
|
);
|
|
5149
|
+
this.#assertRequiredOllamaToolCall(
|
|
5150
|
+
normalizeAICompletionToolCalls(responseJSON),
|
|
5151
|
+
tool_choice
|
|
5152
|
+
);
|
|
4615
5153
|
|
|
4616
5154
|
if(signal?.aborted){
|
|
4617
5155
|
throw normalizeAIRequestAbort();
|
|
@@ -4681,6 +5219,7 @@ class AI {
|
|
|
4681
5219
|
|
|
4682
5220
|
//console.log(responseJSON);
|
|
4683
5221
|
//async
|
|
5222
|
+
normalizeAICompletionToolCalls(responseJSON);
|
|
4684
5223
|
await responseHandler(responseJSON,id,false);
|
|
4685
5224
|
//sync
|
|
4686
5225
|
return responseJSON;
|
|
@@ -4707,7 +5246,10 @@ class AI {
|
|
|
4707
5246
|
try{
|
|
4708
5247
|
this.#assertServiceConfigured(this.ttsService,'tts');
|
|
4709
5248
|
}catch(error){
|
|
4710
|
-
|
|
5249
|
+
this.#publishTTSFailure(error,{
|
|
5250
|
+
boundary:'synthesis',
|
|
5251
|
+
generation:this.speechGeneration
|
|
5252
|
+
});
|
|
4711
5253
|
return Promise.resolve(false);
|
|
4712
5254
|
}
|
|
4713
5255
|
|
|
@@ -4731,20 +5273,15 @@ class AI {
|
|
|
4731
5273
|
|
|
4732
5274
|
#extractSpeechSegments(end=false){
|
|
4733
5275
|
const segments=[];
|
|
4734
|
-
const maximumLength=220;
|
|
4735
5276
|
let remainder=this.audioMessageChunks;
|
|
4736
5277
|
|
|
4737
|
-
while(remainder.
|
|
5278
|
+
while(remainder.length>0){
|
|
4738
5279
|
const terminator=this.#findSpeechTerminator(remainder,end);
|
|
4739
5280
|
let boundary=terminator
|
|
4740
5281
|
?terminator.index+terminator[0].length
|
|
4741
5282
|
:-1;
|
|
4742
5283
|
|
|
4743
|
-
if(boundary<0&&
|
|
4744
|
-
const candidate=remainder.slice(0,maximumLength+1);
|
|
4745
|
-
const whitespace=candidate.lastIndexOf(' ');
|
|
4746
|
-
boundary=whitespace>=80?whitespace+1:maximumLength;
|
|
4747
|
-
}else if(boundary<0&&end){
|
|
5284
|
+
if(boundary<0&&end){
|
|
4748
5285
|
boundary=remainder.length;
|
|
4749
5286
|
}
|
|
4750
5287
|
|
|
@@ -4752,8 +5289,8 @@ class AI {
|
|
|
4752
5289
|
break;
|
|
4753
5290
|
}
|
|
4754
5291
|
|
|
4755
|
-
const segment=remainder.slice(0,boundary)
|
|
4756
|
-
remainder=remainder.slice(boundary)
|
|
5292
|
+
const segment=remainder.slice(0,boundary);
|
|
5293
|
+
remainder=remainder.slice(boundary);
|
|
4757
5294
|
|
|
4758
5295
|
if(segment){
|
|
4759
5296
|
segments.push(segment);
|
|
@@ -4819,7 +5356,11 @@ class AI {
|
|
|
4819
5356
|
}
|
|
4820
5357
|
).catch(
|
|
4821
5358
|
function discardFailedSpeechJob(error){
|
|
4822
|
-
return runtime.#failSpeechJob(
|
|
5359
|
+
return runtime.#failSpeechJob(
|
|
5360
|
+
job,
|
|
5361
|
+
error,
|
|
5362
|
+
job.state==='decoding'?'decode':'synthesis'
|
|
5363
|
+
);
|
|
4823
5364
|
}
|
|
4824
5365
|
);
|
|
4825
5366
|
|
|
@@ -4844,6 +5385,7 @@ class AI {
|
|
|
4844
5385
|
return this.#cancelSpeechJob(job);
|
|
4845
5386
|
}
|
|
4846
5387
|
|
|
5388
|
+
job.state='decoding';
|
|
4847
5389
|
const audioContext=this.#getSpeechAudioContext();
|
|
4848
5390
|
return this.playAudio(
|
|
4849
5391
|
audio.chunks,
|
|
@@ -5324,6 +5866,12 @@ class AI {
|
|
|
5324
5866
|
this.speechResumePending=false;
|
|
5325
5867
|
}
|
|
5326
5868
|
this.#waitForSpeechGesture(error);
|
|
5869
|
+
if(error?.name!=='NotAllowedError'){
|
|
5870
|
+
this.#publishTTSFailure(error,{
|
|
5871
|
+
boundary:'playback-resume',
|
|
5872
|
+
generation:this.speechGeneration
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5327
5875
|
return false;
|
|
5328
5876
|
}
|
|
5329
5877
|
|
|
@@ -5333,7 +5881,7 @@ class AI {
|
|
|
5333
5881
|
|
|
5334
5882
|
async playAudio(
|
|
5335
5883
|
audioChunks=[],
|
|
5336
|
-
audioContext=
|
|
5884
|
+
audioContext=null,
|
|
5337
5885
|
sourceNode=null,
|
|
5338
5886
|
audioType=this.audioType,
|
|
5339
5887
|
speechJob=null
|
|
@@ -5356,19 +5904,20 @@ class AI {
|
|
|
5356
5904
|
|
|
5357
5905
|
try{
|
|
5358
5906
|
job.state='decoding';
|
|
5907
|
+
const playbackContext=audioContext||this.#getSpeechAudioContext();
|
|
5359
5908
|
const audioBlob=new Blob(audioChunks,{type:audioType});
|
|
5360
5909
|
const arrayBuffer=await audioBlob.arrayBuffer();
|
|
5361
|
-
const audioBuffer=await
|
|
5910
|
+
const audioBuffer=await playbackContext.decodeAudioData(arrayBuffer);
|
|
5362
5911
|
|
|
5363
5912
|
if(this.muted||job.generation!==this.speechGeneration){
|
|
5364
5913
|
return this.#cancelSpeechJob(job);
|
|
5365
5914
|
}
|
|
5366
5915
|
|
|
5367
|
-
const preparedSource=sourceNode||
|
|
5916
|
+
const preparedSource=sourceNode||playbackContext.createBufferSource();
|
|
5368
5917
|
const runtime=this;
|
|
5369
5918
|
|
|
5370
5919
|
preparedSource.buffer=audioBuffer;
|
|
5371
|
-
preparedSource.connect(
|
|
5920
|
+
preparedSource.connect(playbackContext.destination);
|
|
5372
5921
|
preparedSource.__arcaneStarted=false;
|
|
5373
5922
|
preparedSource.onended=function finishQueuedSpeechSource(){
|
|
5374
5923
|
runtime.nextSentance(job);
|
|
@@ -5379,14 +5928,20 @@ class AI {
|
|
|
5379
5928
|
this.#requestSpeechPlayback();
|
|
5380
5929
|
return true;
|
|
5381
5930
|
}catch(error){
|
|
5382
|
-
return this.#failSpeechJob(job,error);
|
|
5931
|
+
return this.#failSpeechJob(job,error,'decode');
|
|
5383
5932
|
}
|
|
5384
5933
|
}
|
|
5385
5934
|
|
|
5386
5935
|
#requestSpeechPlayback(){
|
|
5936
|
+
const runtime=this;
|
|
5387
5937
|
this.#pumpSpeechPlayback().catch(
|
|
5388
5938
|
function reportSpeechPlaybackFailure(error){
|
|
5389
|
-
|
|
5939
|
+
const job=runtime.speechJobs[0];
|
|
5940
|
+
if(job){
|
|
5941
|
+
runtime.#failSpeechJob(job,error,'playback-start');
|
|
5942
|
+
return;
|
|
5943
|
+
}
|
|
5944
|
+
console.error('AI audio playback failed without an active speech job.',error);
|
|
5390
5945
|
}
|
|
5391
5946
|
);
|
|
5392
5947
|
}
|
|
@@ -5397,10 +5952,12 @@ class AI {
|
|
|
5397
5952
|
}
|
|
5398
5953
|
|
|
5399
5954
|
this.speechPlaybackStarting=true;
|
|
5955
|
+
let activeJob=null;
|
|
5400
5956
|
|
|
5401
5957
|
try{
|
|
5402
5958
|
while(!this.isSpeaking&&!this.muted){
|
|
5403
5959
|
const job=this.speechJobs[0];
|
|
5960
|
+
activeJob=job||null;
|
|
5404
5961
|
|
|
5405
5962
|
if(!job){
|
|
5406
5963
|
return false;
|
|
@@ -5441,14 +5998,21 @@ class AI {
|
|
|
5441
5998
|
job.sourceNode.__arcaneStarted=true;
|
|
5442
5999
|
this.currentSpeechJob=job;
|
|
5443
6000
|
this.isSpeaking=true;
|
|
5444
|
-
job.sourceNode.start(0);
|
|
6001
|
+
await job.sourceNode.start(0);
|
|
5445
6002
|
return true;
|
|
5446
6003
|
}catch(error){
|
|
5447
6004
|
this.currentSpeechJob=null;
|
|
5448
6005
|
this.isSpeaking=false;
|
|
5449
|
-
this.#failSpeechJob(job,error);
|
|
6006
|
+
this.#failSpeechJob(job,error,'playback-start');
|
|
5450
6007
|
}
|
|
5451
6008
|
}
|
|
6009
|
+
}catch(error){
|
|
6010
|
+
if(activeJob){
|
|
6011
|
+
this.#failSpeechJob(activeJob,error,'playback-start');
|
|
6012
|
+
}else if(!this.muted&&!isAIRequestAbort(error)){
|
|
6013
|
+
console.error('AI audio playback failed without an active speech job.',error);
|
|
6014
|
+
}
|
|
6015
|
+
return false;
|
|
5452
6016
|
}finally{
|
|
5453
6017
|
this.speechPlaybackStarting=false;
|
|
5454
6018
|
|
|
@@ -5494,13 +6058,76 @@ class AI {
|
|
|
5494
6058
|
return false;
|
|
5495
6059
|
}
|
|
5496
6060
|
|
|
5497
|
-
#
|
|
6061
|
+
#publishTTSFailure(error,{
|
|
6062
|
+
boundary='synthesis',
|
|
6063
|
+
generation=this.speechGeneration
|
|
6064
|
+
}={}){
|
|
6065
|
+
if(
|
|
6066
|
+
generation!==this.speechGeneration
|
|
6067
|
+
||this.muted
|
|
6068
|
+
||isAIRequestAbort(error)
|
|
6069
|
+
){
|
|
6070
|
+
return false;
|
|
6071
|
+
}
|
|
6072
|
+
|
|
6073
|
+
const boundaries=new Set([
|
|
6074
|
+
'synthesis',
|
|
6075
|
+
'decode',
|
|
6076
|
+
'playback-start',
|
|
6077
|
+
'playback-resume'
|
|
6078
|
+
]);
|
|
6079
|
+
const normalizedBoundary=boundaries.has(boundary)
|
|
6080
|
+
?boundary
|
|
6081
|
+
:'synthesis';
|
|
6082
|
+
const reason=`tts-${normalizedBoundary}-rejected`;
|
|
6083
|
+
const operationId=
|
|
6084
|
+
`${this.#events.instanceId}:tts-failure:${(++this.#speechFailureSequence).toString(36)}`;
|
|
6085
|
+
|
|
6086
|
+
console.error(`AI speech ${normalizedBoundary} failed.`,error);
|
|
6087
|
+
|
|
6088
|
+
try{
|
|
6089
|
+
const {occurrence}=this.#events.dispatch(
|
|
6090
|
+
AI_TTS_FAILURE_EVENT,
|
|
6091
|
+
completeValue({
|
|
6092
|
+
ai:this,
|
|
6093
|
+
boundary:normalizedBoundary,
|
|
6094
|
+
error,
|
|
6095
|
+
generation,
|
|
6096
|
+
reason
|
|
6097
|
+
}),
|
|
6098
|
+
{
|
|
6099
|
+
operationId,
|
|
6100
|
+
publicDetail:completeValue({
|
|
6101
|
+
boundary:normalizedBoundary,
|
|
6102
|
+
...(typeof error?.code==='string'?{code:error.code}:{}),
|
|
6103
|
+
generation,
|
|
6104
|
+
reason
|
|
6105
|
+
})
|
|
6106
|
+
}
|
|
6107
|
+
);
|
|
6108
|
+
projectArcaneDOMEvent(window,occurrence);
|
|
6109
|
+
}catch(reportingError){
|
|
6110
|
+
console.error(
|
|
6111
|
+
'AI speech failure could not be published to the runtime event boundary.',
|
|
6112
|
+
reportingError
|
|
6113
|
+
);
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6116
|
+
return true;
|
|
6117
|
+
}
|
|
6118
|
+
|
|
6119
|
+
#failSpeechJob(job,error,boundary='synthesis'){
|
|
5498
6120
|
if(job.state==='failed'||job.state==='cancelled'){
|
|
5499
6121
|
return false;
|
|
5500
6122
|
}
|
|
5501
6123
|
|
|
5502
6124
|
job.state='failed';
|
|
5503
6125
|
|
|
6126
|
+
this.#publishTTSFailure(error,{
|
|
6127
|
+
boundary,
|
|
6128
|
+
generation:job.generation
|
|
6129
|
+
});
|
|
6130
|
+
|
|
5504
6131
|
if(job.sourceNode){
|
|
5505
6132
|
job.sourceNode.onended=null;
|
|
5506
6133
|
}
|
|
@@ -5512,10 +6139,6 @@ class AI {
|
|
|
5512
6139
|
this.isSpeaking=false;
|
|
5513
6140
|
}
|
|
5514
6141
|
|
|
5515
|
-
if(job.generation===this.speechGeneration&&error?.name!=='AbortError'){
|
|
5516
|
-
console.warn('AI speech synthesis failed.');
|
|
5517
|
-
}
|
|
5518
|
-
|
|
5519
6142
|
this.#requestSpeechPlayback();
|
|
5520
6143
|
return false;
|
|
5521
6144
|
}
|
|
@@ -5627,7 +6250,7 @@ function installAIUserReadyRegistration(){
|
|
|
5627
6250
|
delete globalThis[AI_USER_READY_REGISTRATION_KEY];
|
|
5628
6251
|
}
|
|
5629
6252
|
}
|
|
5630
|
-
registration=
|
|
6253
|
+
registration=completeValue({
|
|
5631
6254
|
protocol:AI_USER_READY_REGISTRATION_PROTOCOL,
|
|
5632
6255
|
dispose:disposeAIUserReadyRegistration
|
|
5633
6256
|
});
|