arcane-os 0.3.1 → 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 +14 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
- package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
- 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 -148
- package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
- 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 +1042 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
- 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 +27 -67
- 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 +109 -1558
- 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 -185
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1295
- 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 -719
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2965
- package/docs/reference/sdk-api.md +0 -6698
- 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
|
|
|
@@ -2104,9 +2379,9 @@ class AI {
|
|
|
2104
2379
|
expectedProviders[role]=record.provider;
|
|
2105
2380
|
legacyRecords.push(record);
|
|
2106
2381
|
}
|
|
2107
|
-
return
|
|
2108
|
-
expectedProviders:
|
|
2109
|
-
legacyRecords:
|
|
2382
|
+
return completeValue({
|
|
2383
|
+
expectedProviders:completeValue(expectedProviders),
|
|
2384
|
+
legacyRecords:completeValue(legacyRecords)
|
|
2110
2385
|
});
|
|
2111
2386
|
}
|
|
2112
2387
|
|
|
@@ -2175,7 +2450,7 @@ class AI {
|
|
|
2175
2450
|
}
|
|
2176
2451
|
|
|
2177
2452
|
#publishBrowserSpeechEvent(type,normalized,operationId,reason,{descriptor=null,error=null}={}){
|
|
2178
|
-
const compatibilityDetail=
|
|
2453
|
+
const compatibilityDetail=completeValue({
|
|
2179
2454
|
configuration:normalized.configuration,
|
|
2180
2455
|
configurationId:normalized.id,
|
|
2181
2456
|
...(descriptor?{descriptor}:{}),
|
|
@@ -2187,7 +2462,7 @@ class AI {
|
|
|
2187
2462
|
compatibilityDetail,
|
|
2188
2463
|
{
|
|
2189
2464
|
operationId,
|
|
2190
|
-
publicDetail:
|
|
2465
|
+
publicDetail:completeValue({
|
|
2191
2466
|
configurationId:normalized.id,
|
|
2192
2467
|
...(descriptor?{descriptor}:{}),
|
|
2193
2468
|
...(typeof error?.code==='string'?{code:error.code}:{}),
|
|
@@ -2199,12 +2474,12 @@ class AI {
|
|
|
2199
2474
|
|
|
2200
2475
|
#browserSpeechRoutes(normalized,providers,previousRecord){
|
|
2201
2476
|
function roleRoutes(provider,catalog){
|
|
2202
|
-
const selection=
|
|
2477
|
+
const selection=completeValue({
|
|
2203
2478
|
providerId:provider.id,
|
|
2204
2479
|
modelId:catalog.id,
|
|
2205
2480
|
localOnly:true
|
|
2206
2481
|
});
|
|
2207
|
-
return
|
|
2482
|
+
return completeValue({default:selection,localOnly:selection});
|
|
2208
2483
|
}
|
|
2209
2484
|
const currentRoutes=this.#currentSpeechRoutes();
|
|
2210
2485
|
const routes={};
|
|
@@ -2228,15 +2503,15 @@ class AI {
|
|
|
2228
2503
|
catalogs[role]=catalog[0];
|
|
2229
2504
|
routes[role]=roleRoutes(providers[role],catalog[0]);
|
|
2230
2505
|
}
|
|
2231
|
-
return
|
|
2232
|
-
routes:
|
|
2233
|
-
catalogs:
|
|
2506
|
+
return completeValue({
|
|
2507
|
+
routes:completeValue(routes),
|
|
2508
|
+
catalogs:completeValue(catalogs)
|
|
2234
2509
|
});
|
|
2235
2510
|
}
|
|
2236
2511
|
|
|
2237
2512
|
#browserSpeechDescriptor(normalized,catalogs,previousRecord){
|
|
2238
2513
|
function roleDescriptor(role,configured,catalog){
|
|
2239
|
-
return
|
|
2514
|
+
return completeValue({
|
|
2240
2515
|
role,
|
|
2241
2516
|
providerId:configured.providerId,
|
|
2242
2517
|
modelId:catalog.id,
|
|
@@ -2253,7 +2528,7 @@ class AI {
|
|
|
2253
2528
|
?roleDescriptor(role,normalized[role],catalogs[role])
|
|
2254
2529
|
:previousRecord?.descriptor[role]??null;
|
|
2255
2530
|
}
|
|
2256
|
-
return
|
|
2531
|
+
return completeValue({
|
|
2257
2532
|
protocol:AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL,
|
|
2258
2533
|
configurationId:normalized.id,
|
|
2259
2534
|
...roles
|
|
@@ -2275,7 +2550,7 @@ class AI {
|
|
|
2275
2550
|
role=>!normalized.roles.includes(role)
|
|
2276
2551
|
);
|
|
2277
2552
|
if(carriedRoles.length===0)return normalized.configuration;
|
|
2278
|
-
return
|
|
2553
|
+
return completeValue({
|
|
2279
2554
|
protocol:AI_BROWSER_SPEECH_CONFIGURATION_PROTOCOL,
|
|
2280
2555
|
id:normalized.id,
|
|
2281
2556
|
dbopfs:normalized.dbopfs,
|
|
@@ -2294,12 +2569,12 @@ class AI {
|
|
|
2294
2569
|
}
|
|
2295
2570
|
|
|
2296
2571
|
#currentSpeechRoutes(){
|
|
2297
|
-
return
|
|
2298
|
-
stt:
|
|
2572
|
+
return completeValue({
|
|
2573
|
+
stt:completeValue({
|
|
2299
2574
|
default:this.#providerRuntime.selection('stt'),
|
|
2300
2575
|
localOnly:this.#providerRuntime.selection('stt',{localOnly:true})
|
|
2301
2576
|
}),
|
|
2302
|
-
tts:
|
|
2577
|
+
tts:completeValue({
|
|
2303
2578
|
default:this.#providerRuntime.selection('tts'),
|
|
2304
2579
|
localOnly:this.#providerRuntime.selection('tts',{localOnly:true})
|
|
2305
2580
|
})
|
|
@@ -2307,9 +2582,9 @@ class AI {
|
|
|
2307
2582
|
}
|
|
2308
2583
|
|
|
2309
2584
|
#emptySpeechRoutes(){
|
|
2310
|
-
return
|
|
2311
|
-
stt:
|
|
2312
|
-
tts:
|
|
2585
|
+
return completeValue({
|
|
2586
|
+
stt:completeValue({default:null,localOnly:null}),
|
|
2587
|
+
tts:completeValue({default:null,localOnly:null})
|
|
2313
2588
|
});
|
|
2314
2589
|
}
|
|
2315
2590
|
|
|
@@ -2354,7 +2629,7 @@ class AI {
|
|
|
2354
2629
|
previousRecord
|
|
2355
2630
|
){
|
|
2356
2631
|
const configurationByRole={};
|
|
2357
|
-
const managedRoles=
|
|
2632
|
+
const managedRoles=completeValue(['stt','tts'].filter(role=>
|
|
2358
2633
|
normalized.roles.includes(role)
|
|
2359
2634
|
||previousRecord?.managedRoles.includes(role)
|
|
2360
2635
|
));
|
|
@@ -2365,7 +2640,7 @@ class AI {
|
|
|
2365
2640
|
}
|
|
2366
2641
|
return {
|
|
2367
2642
|
configuration,
|
|
2368
|
-
configurationByRole:
|
|
2643
|
+
configurationByRole:completeValue(configurationByRole),
|
|
2369
2644
|
dbopfs:normalized.dbopfs,
|
|
2370
2645
|
tableName:normalized.tableName,
|
|
2371
2646
|
descriptor,
|
|
@@ -2379,15 +2654,13 @@ class AI {
|
|
|
2379
2654
|
};
|
|
2380
2655
|
}
|
|
2381
2656
|
|
|
2382
|
-
#
|
|
2383
|
-
record.unregisters=
|
|
2384
|
-
|
|
2385
|
-
Object.seal(record.retirementState);
|
|
2386
|
-
return Object.freeze(record);
|
|
2657
|
+
#finalizeBrowserSpeechRecord(record){
|
|
2658
|
+
record.unregisters=completeValue({...record.unregisters});
|
|
2659
|
+
return completeValue(record);
|
|
2387
2660
|
}
|
|
2388
2661
|
|
|
2389
2662
|
#browserSpeechRecordFromReplacement(record,replacement){
|
|
2390
|
-
return this.#
|
|
2663
|
+
return this.#finalizeBrowserSpeechRecord({
|
|
2391
2664
|
configuration:record.configuration,
|
|
2392
2665
|
configurationByRole:record.configurationByRole,
|
|
2393
2666
|
dbopfs:record.dbopfs,
|
|
@@ -2609,22 +2882,16 @@ class AI {
|
|
|
2609
2882
|
candidateProviders[role]=factory({
|
|
2610
2883
|
id:configured.providerId,
|
|
2611
2884
|
...(configured.graph
|
|
2612
|
-
?{
|
|
2613
|
-
graph:configured.graph,
|
|
2614
|
-
security:configured.security
|
|
2615
|
-
}
|
|
2885
|
+
?{graph:configured.graph}
|
|
2616
2886
|
:{
|
|
2617
2887
|
model:configured.model,
|
|
2618
|
-
runtime:configured.runtime
|
|
2619
|
-
...(Object.hasOwn(configured,'security')
|
|
2620
|
-
?{security:configured.security}
|
|
2621
|
-
:{})
|
|
2888
|
+
runtime:configured.runtime
|
|
2622
2889
|
}),
|
|
2623
2890
|
store,
|
|
2624
2891
|
offline:configured.offline
|
|
2625
2892
|
});
|
|
2626
2893
|
}
|
|
2627
|
-
providers=
|
|
2894
|
+
providers=completeValue({...candidateProviders});
|
|
2628
2895
|
prepared=this.#browserSpeechRoutes(
|
|
2629
2896
|
normalized,
|
|
2630
2897
|
providers,
|
|
@@ -2646,7 +2913,7 @@ class AI {
|
|
|
2646
2913
|
const provider=candidateProviders[role];
|
|
2647
2914
|
const changed=normalized.roles.includes(role);
|
|
2648
2915
|
const selection=changed&&provider
|
|
2649
|
-
?
|
|
2916
|
+
?completeValue({
|
|
2650
2917
|
providerId:provider.id,
|
|
2651
2918
|
modelId:normalized[role].graph?.model.id
|
|
2652
2919
|
??normalized[role].model.id,
|
|
@@ -2654,7 +2921,7 @@ class AI {
|
|
|
2654
2921
|
})
|
|
2655
2922
|
:null;
|
|
2656
2923
|
partialRoutes[role]=changed
|
|
2657
|
-
?
|
|
2924
|
+
?completeValue({default:selection,localOnly:selection})
|
|
2658
2925
|
:previousRecord?.managedRoles.includes(role)
|
|
2659
2926
|
?previousRecord.routes[role]
|
|
2660
2927
|
:currentRoutes[role];
|
|
@@ -2663,8 +2930,8 @@ class AI {
|
|
|
2663
2930
|
normalized,
|
|
2664
2931
|
configuration,
|
|
2665
2932
|
null,
|
|
2666
|
-
|
|
2667
|
-
|
|
2933
|
+
completeValue({...candidateProviders}),
|
|
2934
|
+
completeValue(partialRoutes),
|
|
2668
2935
|
previousRecord
|
|
2669
2936
|
);
|
|
2670
2937
|
return this.#throwAfterBrowserSpeechCandidateCleanup(
|
|
@@ -2728,9 +2995,9 @@ class AI {
|
|
|
2728
2995
|
expectedProvider:replacementBoundary.expectedProviders[role]
|
|
2729
2996
|
}
|
|
2730
2997
|
);
|
|
2731
|
-
replacement=
|
|
2998
|
+
replacement=completeValue({
|
|
2732
2999
|
routes:prepared.routes,
|
|
2733
|
-
unregisters:
|
|
3000
|
+
unregisters:completeValue({
|
|
2734
3001
|
stt:role==='stt'
|
|
2735
3002
|
?roleReplacement.unregister
|
|
2736
3003
|
:previousRecord?.unregisters.stt??null,
|
|
@@ -2804,9 +3071,9 @@ class AI {
|
|
|
2804
3071
|
expectedProvider:record.providers[role]
|
|
2805
3072
|
}
|
|
2806
3073
|
);
|
|
2807
|
-
rollback=
|
|
3074
|
+
rollback=completeValue({
|
|
2808
3075
|
routes:previousRecord.routes,
|
|
2809
|
-
unregisters:
|
|
3076
|
+
unregisters:completeValue({
|
|
2810
3077
|
stt:role==='stt'
|
|
2811
3078
|
?roleRollback.unregister
|
|
2812
3079
|
:previousRecord.unregisters.stt,
|
|
@@ -2978,7 +3245,7 @@ class AI {
|
|
|
2978
3245
|
operation.signal?.removeEventListener('abort',forwardAbort);
|
|
2979
3246
|
if(runtime.#browserSpeechController===controller){
|
|
2980
3247
|
runtime.#browserSpeechController=null;
|
|
2981
|
-
runtime.#browserSpeechControllerRoles=
|
|
3248
|
+
runtime.#browserSpeechControllerRoles=completeValue([]);
|
|
2982
3249
|
}
|
|
2983
3250
|
}
|
|
2984
3251
|
}
|
|
@@ -3028,7 +3295,7 @@ class AI {
|
|
|
3028
3295
|
if(invalidatesSpeech)this.#invalidateSpeechControl();
|
|
3029
3296
|
const controller=new AbortController();
|
|
3030
3297
|
this.#browserSpeechController=controller;
|
|
3031
|
-
this.#browserSpeechControllerRoles=
|
|
3298
|
+
this.#browserSpeechControllerRoles=completeValue([]);
|
|
3032
3299
|
const forwardAbort=function cancelBrowserSpeechDisposal(){
|
|
3033
3300
|
if(!controller.signal.aborted)controller.abort(operation.signal.reason);
|
|
3034
3301
|
};
|
|
@@ -3079,7 +3346,7 @@ class AI {
|
|
|
3079
3346
|
}else{
|
|
3080
3347
|
const role=activeRecord.managedRoles[0];
|
|
3081
3348
|
const currentRoutes=runtime.#currentSpeechRoutes();
|
|
3082
|
-
const emptyRoleRoutes=
|
|
3349
|
+
const emptyRoleRoutes=completeValue({
|
|
3083
3350
|
default:null,
|
|
3084
3351
|
localOnly:null
|
|
3085
3352
|
});
|
|
@@ -3091,8 +3358,8 @@ class AI {
|
|
|
3091
3358
|
expectedProvider:activeRecord.providers[role]
|
|
3092
3359
|
}
|
|
3093
3360
|
);
|
|
3094
|
-
removed=
|
|
3095
|
-
routes:
|
|
3361
|
+
removed=completeValue({
|
|
3362
|
+
routes:completeValue({
|
|
3096
3363
|
stt:role==='stt'
|
|
3097
3364
|
?emptyRoleRoutes
|
|
3098
3365
|
:currentRoutes.stt,
|
|
@@ -3182,7 +3449,7 @@ class AI {
|
|
|
3182
3449
|
operation.signal?.removeEventListener('abort',forwardAbort);
|
|
3183
3450
|
if(runtime.#browserSpeechController===controller){
|
|
3184
3451
|
runtime.#browserSpeechController=null;
|
|
3185
|
-
runtime.#browserSpeechControllerRoles=
|
|
3452
|
+
runtime.#browserSpeechControllerRoles=completeValue([]);
|
|
3186
3453
|
}
|
|
3187
3454
|
}
|
|
3188
3455
|
}
|
|
@@ -3262,7 +3529,7 @@ class AI {
|
|
|
3262
3529
|
const errorText=await response.text();
|
|
3263
3530
|
|
|
3264
3531
|
if(errorText&&!errorText.trim().startsWith('<')){
|
|
3265
|
-
detail=errorText
|
|
3532
|
+
detail=errorText;
|
|
3266
3533
|
}
|
|
3267
3534
|
}
|
|
3268
3535
|
}catch{
|
|
@@ -3471,8 +3738,8 @@ class AI {
|
|
|
3471
3738
|
#arrayBufferToBase64(arrayBuffer){
|
|
3472
3739
|
const bytes=new Uint8Array(arrayBuffer);
|
|
3473
3740
|
|
|
3474
|
-
if(!bytes.length
|
|
3475
|
-
throw new RangeError('Microphone audio must be
|
|
3741
|
+
if(!bytes.length){
|
|
3742
|
+
throw new RangeError('Microphone audio must not be empty.');
|
|
3476
3743
|
}
|
|
3477
3744
|
|
|
3478
3745
|
const chunks=[];
|
|
@@ -3485,7 +3752,7 @@ class AI {
|
|
|
3485
3752
|
}
|
|
3486
3753
|
|
|
3487
3754
|
#base64ToBytes(value){
|
|
3488
|
-
if(typeof value!=='string'||!value
|
|
3755
|
+
if(typeof value!=='string'||!value){
|
|
3489
3756
|
throw new TypeError('Arcane returned invalid local speech audio.');
|
|
3490
3757
|
}
|
|
3491
3758
|
|
|
@@ -3532,8 +3799,13 @@ class AI {
|
|
|
3532
3799
|
if(typeof argumentValue==='string'){
|
|
3533
3800
|
try{
|
|
3534
3801
|
argumentValue=JSON.parse(argumentValue);
|
|
3535
|
-
}catch{
|
|
3536
|
-
|
|
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;
|
|
3537
3809
|
}
|
|
3538
3810
|
}
|
|
3539
3811
|
if(
|
|
@@ -3541,7 +3813,11 @@ class AI {
|
|
|
3541
3813
|
||typeof argumentValue!=='object'
|
|
3542
3814
|
||Array.isArray(argumentValue)
|
|
3543
3815
|
){
|
|
3544
|
-
|
|
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;
|
|
3545
3821
|
}
|
|
3546
3822
|
if(callId&&name){
|
|
3547
3823
|
toolNamesByCallId.set(callId,name);
|
|
@@ -3577,7 +3853,7 @@ class AI {
|
|
|
3577
3853
|
});
|
|
3578
3854
|
const requiredName=toolChoice?.function?.name;
|
|
3579
3855
|
const instruction=requiredName
|
|
3580
|
-
?`Call the ${requiredName} function now with
|
|
3856
|
+
?`Call the ${requiredName} function now with complete values for every required field. Do not answer in prose.`
|
|
3581
3857
|
:toolChoice==='none'
|
|
3582
3858
|
?'Do not call a function in this request. Follow the response instructions and answer in the requested format.'
|
|
3583
3859
|
:'';
|
|
@@ -3645,16 +3921,15 @@ class AI {
|
|
|
3645
3921
|
return null;
|
|
3646
3922
|
}
|
|
3647
3923
|
|
|
3648
|
-
async #reportRequest(requestHandler,request,id){
|
|
3924
|
+
async #reportRequest(requestHandler,request,id,metadata){
|
|
3649
3925
|
if(typeof requestHandler!=='function'){
|
|
3650
3926
|
throw new TypeError('AI onRequest callback must be a function.');
|
|
3651
3927
|
}
|
|
3652
|
-
await requestHandler(request,id);
|
|
3928
|
+
await requestHandler(request,id,metadata);
|
|
3653
3929
|
}
|
|
3654
3930
|
|
|
3655
3931
|
#providerStreamEmissions(chunk,seeThinking){
|
|
3656
3932
|
const chunks=[];
|
|
3657
|
-
const toolNames=[];
|
|
3658
3933
|
const choices=Array.isArray(chunk?.choices)?chunk.choices:[];
|
|
3659
3934
|
for(const choice of choices){
|
|
3660
3935
|
const delta=choice?.delta||{};
|
|
@@ -3664,12 +3939,6 @@ class AI {
|
|
|
3664
3939
|
if(typeof delta.content==='string'){
|
|
3665
3940
|
chunks.push({text:delta.content,thinking:false});
|
|
3666
3941
|
}
|
|
3667
|
-
for(const call of Array.isArray(delta.tool_calls)?delta.tool_calls:[]){
|
|
3668
|
-
const name=call?.function?.name;
|
|
3669
|
-
if(typeof name==='string'&&name){
|
|
3670
|
-
toolNames.push(name);
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
3942
|
}
|
|
3674
3943
|
if(!choices.length){
|
|
3675
3944
|
if(seeThinking&&typeof chunk?.thinking==='string'){
|
|
@@ -3683,19 +3952,8 @@ class AI {
|
|
|
3683
3952
|
if(text){
|
|
3684
3953
|
chunks.push({text,thinking:false});
|
|
3685
3954
|
}
|
|
3686
|
-
const calls=Array.isArray(chunk?.toolCalls)
|
|
3687
|
-
?chunk.toolCalls
|
|
3688
|
-
:Array.isArray(chunk?.tool_calls)
|
|
3689
|
-
?chunk.tool_calls
|
|
3690
|
-
:[];
|
|
3691
|
-
for(const call of calls){
|
|
3692
|
-
const name=call?.function?.name||call?.name;
|
|
3693
|
-
if(typeof name==='string'&&name){
|
|
3694
|
-
toolNames.push(name);
|
|
3695
|
-
}
|
|
3696
|
-
}
|
|
3697
3955
|
}
|
|
3698
|
-
return {chunks
|
|
3956
|
+
return {chunks};
|
|
3699
3957
|
}
|
|
3700
3958
|
|
|
3701
3959
|
#providerCompletionOutput(completion){
|
|
@@ -3729,7 +3987,7 @@ class AI {
|
|
|
3729
3987
|
payload.structuredOutput??false,
|
|
3730
3988
|
payload.tools??[],
|
|
3731
3989
|
payload.toolChoice??'auto',
|
|
3732
|
-
payload.parallelToolCalls??
|
|
3990
|
+
payload.parallelToolCalls??false,
|
|
3733
3991
|
payload.id??Date.now(),
|
|
3734
3992
|
function ignoreLegacyLLMProviderRequest(){},
|
|
3735
3993
|
signal
|
|
@@ -3748,26 +4006,21 @@ class AI {
|
|
|
3748
4006
|
);
|
|
3749
4007
|
}
|
|
3750
4008
|
|
|
3751
|
-
function emitLegacyLLMStreamTool(name){
|
|
3752
|
-
if(typeof name==='string'&&name){
|
|
3753
|
-
bridge.emit({toolCalls:[{name}]});
|
|
3754
|
-
}
|
|
3755
|
-
}
|
|
3756
|
-
|
|
3757
4009
|
return this.#streamLegacyMessage(
|
|
3758
4010
|
payload.messages??[],
|
|
3759
4011
|
emitLegacyLLMStreamText,
|
|
3760
4012
|
function ignoreLegacyLLMProviderCompletion(){},
|
|
3761
4013
|
payload.tools??[],
|
|
3762
4014
|
payload.toolChoice??'auto',
|
|
3763
|
-
|
|
3764
|
-
payload.parallelToolCalls??
|
|
4015
|
+
function retainLegacyLLMStreamToolUntilCompletion(){},
|
|
4016
|
+
payload.parallelToolCalls??false,
|
|
3765
4017
|
payload.id??Date.now(),
|
|
3766
4018
|
payload.seeThinking??false,
|
|
3767
4019
|
bridge.signal,
|
|
3768
4020
|
function ignoreLegacyLLMProviderRequest(){},
|
|
3769
4021
|
payload.structuredOutput??false,
|
|
3770
|
-
false
|
|
4022
|
+
false,
|
|
4023
|
+
true
|
|
3771
4024
|
);
|
|
3772
4025
|
}
|
|
3773
4026
|
|
|
@@ -3789,27 +4042,78 @@ class AI {
|
|
|
3789
4042
|
}
|
|
3790
4043
|
}
|
|
3791
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
|
+
|
|
3792
4102
|
#openAICompatibleOllamaResponse(response={},id=Date.now()){
|
|
3793
4103
|
const message=response?.message||{};
|
|
3794
|
-
const
|
|
3795
|
-
?
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
:JSON.stringify(call?.function?.arguments||{})
|
|
3805
|
-
}
|
|
3806
|
-
};
|
|
3807
|
-
}
|
|
3808
|
-
)
|
|
3809
|
-
:[];
|
|
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
|
+
});
|
|
3810
4114
|
|
|
3811
4115
|
return {
|
|
3812
|
-
id:
|
|
4116
|
+
id:responseId,
|
|
3813
4117
|
object:'chat.completion',
|
|
3814
4118
|
created:Math.floor(Date.now()/1000),
|
|
3815
4119
|
model:response?.model||this.model,
|
|
@@ -3841,15 +4145,27 @@ class AI {
|
|
|
3841
4145
|
localOnly=false,
|
|
3842
4146
|
onChunk=function ignoreStreamChunk(){},
|
|
3843
4147
|
onComplete=function finishIgnoredStream(){},
|
|
4148
|
+
onResponse=function ignoreStreamResponse(){},
|
|
3844
4149
|
tools=[],
|
|
3845
4150
|
toolChoice='auto',
|
|
3846
4151
|
onToolCall=function ignoreEarlyFunction(){},
|
|
3847
4152
|
onRequest=function ignoreStreamRequest(){},
|
|
3848
|
-
parallelToolCalls=
|
|
4153
|
+
parallelToolCalls=false,
|
|
3849
4154
|
id=Date.now(),
|
|
3850
4155
|
seeThinking=false,
|
|
3851
|
-
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
|
|
3852
4167
|
}={}){
|
|
4168
|
+
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
3853
4169
|
if(localOnly!==true&&localOnly!==false){
|
|
3854
4170
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
3855
4171
|
}
|
|
@@ -3868,10 +4184,20 @@ class AI {
|
|
|
3868
4184
|
toolChoice,
|
|
3869
4185
|
parallelToolCalls,
|
|
3870
4186
|
id,
|
|
3871
|
-
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}:{})
|
|
3872
4199
|
};
|
|
3873
4200
|
const displayId=`M-${id}`;
|
|
3874
|
-
const announcedTools=new Set();
|
|
3875
4201
|
let handle=null;
|
|
3876
4202
|
try{
|
|
3877
4203
|
if(signal?.aborted){
|
|
@@ -3908,40 +4234,22 @@ class AI {
|
|
|
3908
4234
|
throw normalizeAIRequestAbort();
|
|
3909
4235
|
}
|
|
3910
4236
|
}
|
|
3911
|
-
for(const name of emissions.toolNames){
|
|
3912
|
-
if(signal?.aborted){
|
|
3913
|
-
throw normalizeAIRequestAbort();
|
|
3914
|
-
}
|
|
3915
|
-
if(!announcedTools.has(name)){
|
|
3916
|
-
announcedTools.add(name);
|
|
3917
|
-
await onToolCall(name);
|
|
3918
|
-
if(signal?.aborted){
|
|
3919
|
-
throw normalizeAIRequestAbort();
|
|
3920
|
-
}
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
4237
|
}
|
|
3924
4238
|
const completion=await handle.result;
|
|
3925
4239
|
if(signal?.aborted){
|
|
3926
4240
|
throw normalizeAIRequestAbort();
|
|
3927
4241
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
announcedTools.add(name);
|
|
3940
|
-
await onToolCall(name);
|
|
3941
|
-
if(signal?.aborted){
|
|
3942
|
-
throw normalizeAIRequestAbort();
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
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();
|
|
3945
4253
|
}
|
|
3946
4254
|
}
|
|
3947
4255
|
const result=this.#providerCompletionOutput(completion);
|
|
@@ -3964,20 +4272,52 @@ class AI {
|
|
|
3964
4272
|
}
|
|
3965
4273
|
}
|
|
3966
4274
|
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
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
|
+
}
|
|
3981
4321
|
}
|
|
3982
4322
|
|
|
3983
4323
|
async streamMessage(
|
|
@@ -3987,12 +4327,13 @@ class AI {
|
|
|
3987
4327
|
tools=[],
|
|
3988
4328
|
tool_choice='auto',
|
|
3989
4329
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
3990
|
-
parallel_tool_calls=
|
|
4330
|
+
parallel_tool_calls=false,
|
|
3991
4331
|
id=Date.now(),
|
|
3992
4332
|
seeThinking=false,
|
|
3993
4333
|
signal=null,
|
|
3994
4334
|
requestHandler=function ignoreStreamRequest(){},
|
|
3995
|
-
structuredOutput=false
|
|
4335
|
+
structuredOutput=false,
|
|
4336
|
+
returnCompletion=false
|
|
3996
4337
|
){
|
|
3997
4338
|
if(this.#shouldUseProviderRuntime('llm',this.llmService,false)){
|
|
3998
4339
|
return this.streamRequest({
|
|
@@ -4024,7 +4365,9 @@ class AI {
|
|
|
4024
4365
|
seeThinking,
|
|
4025
4366
|
signal,
|
|
4026
4367
|
requestHandler,
|
|
4027
|
-
structuredOutput
|
|
4368
|
+
structuredOutput,
|
|
4369
|
+
true,
|
|
4370
|
+
returnCompletion
|
|
4028
4371
|
);
|
|
4029
4372
|
}
|
|
4030
4373
|
|
|
@@ -4035,17 +4378,19 @@ class AI {
|
|
|
4035
4378
|
tools=[],
|
|
4036
4379
|
tool_choice='auto',
|
|
4037
4380
|
earlyFunctionTrigger=function ignoreEarlyFunction(){},
|
|
4038
|
-
parallel_tool_calls=
|
|
4381
|
+
parallel_tool_calls=false,
|
|
4039
4382
|
id=Date.now(),
|
|
4040
4383
|
seeThinking=false,
|
|
4041
4384
|
signal=null,
|
|
4042
4385
|
requestHandler=function ignoreStreamRequest(){},
|
|
4043
4386
|
structuredOutput=false,
|
|
4044
|
-
finishSpeech=true
|
|
4387
|
+
finishSpeech=true,
|
|
4388
|
+
returnCompletion=false
|
|
4045
4389
|
){
|
|
4046
4390
|
let speechTurnCompleted=false;
|
|
4047
4391
|
|
|
4048
4392
|
try{
|
|
4393
|
+
validateAIStructuralRequest(messages,tools,parallel_tool_calls);
|
|
4049
4394
|
this.#assertServiceConfigured(this.llmService);
|
|
4050
4395
|
if(signal&&(
|
|
4051
4396
|
typeof signal.aborted!=='boolean'
|
|
@@ -4085,7 +4430,7 @@ class AI {
|
|
|
4085
4430
|
let isThinking=true;
|
|
4086
4431
|
let isWaiting=true;
|
|
4087
4432
|
|
|
4088
|
-
streamHandler('Thinking...',`M-${id}`,isThinking);
|
|
4433
|
+
await streamHandler('Thinking...',`M-${id}`,isThinking);
|
|
4089
4434
|
|
|
4090
4435
|
const nativeOllama=this.#nativeOllama();
|
|
4091
4436
|
|
|
@@ -4098,8 +4443,7 @@ class AI {
|
|
|
4098
4443
|
|
|
4099
4444
|
if(nativeOllama){
|
|
4100
4445
|
let nativeContent='';
|
|
4101
|
-
const
|
|
4102
|
-
const triggeredTools=new Set();
|
|
4446
|
+
const nativeStreamedToolCallFrames=[];
|
|
4103
4447
|
const ollamaTools=this.#ollamaTools(tools,tool_choice);
|
|
4104
4448
|
const ollamaMessages=this.#ollamaMessages(messages,tool_choice);
|
|
4105
4449
|
const ollamaRequest={
|
|
@@ -4111,46 +4455,19 @@ class AI {
|
|
|
4111
4455
|
...(ollamaTools.length?{tools:ollamaTools}:{})
|
|
4112
4456
|
};
|
|
4113
4457
|
|
|
4114
|
-
function reportEarlyFunctionFailure(error){
|
|
4115
|
-
console.error('Early tool trigger failed.');
|
|
4116
|
-
}
|
|
4117
|
-
|
|
4118
4458
|
function receiveNativeToolCalls(message={}){
|
|
4119
4459
|
const calls=Array.isArray(message.tool_calls)?message.tool_calls:[];
|
|
4120
4460
|
|
|
4121
4461
|
if(calls.length){
|
|
4122
4462
|
isThinking=false;
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
for(const call of calls){
|
|
4126
|
-
const name=call?.function?.name;
|
|
4127
|
-
|
|
4128
|
-
if(!name){
|
|
4129
|
-
continue;
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
nativeToolCalls[name]=typeof call.function.arguments==='string'
|
|
4133
|
-
?call.function.arguments
|
|
4134
|
-
:JSON.stringify(call.function.arguments||{});
|
|
4135
|
-
|
|
4136
|
-
if(!triggeredTools.has(name)){
|
|
4137
|
-
triggeredTools.add(name);
|
|
4138
|
-
Promise.resolve(earlyFunctionTrigger(name)).catch(
|
|
4139
|
-
reportEarlyFunctionFailure
|
|
4140
|
-
);
|
|
4141
|
-
}
|
|
4463
|
+
nativeStreamedToolCallFrames.push(calls.slice());
|
|
4142
4464
|
}
|
|
4143
4465
|
}
|
|
4144
4466
|
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
});
|
|
4150
|
-
const nativeResponse=await nativeOllama.chat(
|
|
4151
|
-
ollamaRequest,
|
|
4152
|
-
{
|
|
4153
|
-
onChunk:function receiveNativeOllamaChunk(chunk){
|
|
4467
|
+
let nativeChunkPipeline=Promise.resolve();
|
|
4468
|
+
function queueNativeOllamaChunk(chunk){
|
|
4469
|
+
nativeChunkPipeline=nativeChunkPipeline.then(
|
|
4470
|
+
async function processNativeOllamaChunk(){
|
|
4154
4471
|
if(signal?.aborted){
|
|
4155
4472
|
return;
|
|
4156
4473
|
}
|
|
@@ -4161,43 +4478,105 @@ class AI {
|
|
|
4161
4478
|
const content=String(message.content||'');
|
|
4162
4479
|
|
|
4163
4480
|
if(thinking){
|
|
4164
|
-
streamHandler(thinking,`M-${id}`,true);
|
|
4481
|
+
await streamHandler(thinking,`M-${id}`,true);
|
|
4165
4482
|
}
|
|
4166
4483
|
|
|
4167
4484
|
if(content){
|
|
4168
4485
|
isThinking=false;
|
|
4169
4486
|
nativeContent+=content;
|
|
4170
|
-
streamHandler(content,`M-${id}`,false);
|
|
4487
|
+
await streamHandler(content,`M-${id}`,false);
|
|
4171
4488
|
}
|
|
4172
4489
|
|
|
4173
4490
|
receiveNativeToolCalls(message);
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
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
|
+
}
|
|
4178
4519
|
if(signal?.aborted){
|
|
4179
4520
|
throw normalizeAIRequestAbort();
|
|
4180
4521
|
}
|
|
4181
|
-
|
|
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
|
+
}
|
|
4182
4559
|
this.#assertRequiredOllamaToolCall(
|
|
4183
|
-
|
|
4184
|
-
return {function:{name}};
|
|
4185
|
-
}),
|
|
4560
|
+
structuralToolCalls,
|
|
4186
4561
|
tool_choice
|
|
4187
4562
|
);
|
|
4563
|
+
for(const call of structuralToolCalls){
|
|
4564
|
+
if(signal?.aborted){
|
|
4565
|
+
throw normalizeAIRequestAbort();
|
|
4566
|
+
}
|
|
4567
|
+
await earlyFunctionTrigger(call,`M-${id}`);
|
|
4568
|
+
}
|
|
4188
4569
|
|
|
4189
|
-
const nativeResult=
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
if(Object.keys(nativeToolCalls).length&&!nativeContent){
|
|
4193
|
-
streamHandler('',`M-${id}`,false);
|
|
4570
|
+
const nativeResult=this.#providerCompletionOutput(nativeCompletion);
|
|
4571
|
+
if(structuralToolCalls.length&&!nativeContent){
|
|
4572
|
+
await streamHandler('',`M-${id}`,false);
|
|
4194
4573
|
}
|
|
4195
4574
|
if(finishSpeech){
|
|
4196
4575
|
this.finishTTS();
|
|
4197
4576
|
}
|
|
4198
4577
|
await streamComplete(nativeResult,`M-${id}`,isThinking);
|
|
4199
4578
|
speechTurnCompleted=true;
|
|
4200
|
-
return nativeResult;
|
|
4579
|
+
return returnCompletion?nativeCompletion:nativeResult;
|
|
4201
4580
|
}
|
|
4202
4581
|
|
|
4203
4582
|
await this.#reportRequest(requestHandler,request,id,{
|
|
@@ -4238,8 +4617,13 @@ class AI {
|
|
|
4238
4617
|
|
|
4239
4618
|
let chunkString='';
|
|
4240
4619
|
let chunkCache='';
|
|
4241
|
-
const
|
|
4242
|
-
|
|
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;
|
|
4243
4627
|
const decoder = new TextDecoder('utf-8');
|
|
4244
4628
|
//alert(1)
|
|
4245
4629
|
const reader=response.body?.getReader?.();
|
|
@@ -4248,41 +4632,63 @@ class AI {
|
|
|
4248
4632
|
throw new TypeError('Streaming response body is not readable');
|
|
4249
4633
|
}
|
|
4250
4634
|
|
|
4251
|
-
function receiveStreamedToolCalls(toolCalls=[]){
|
|
4635
|
+
function receiveStreamedToolCalls(toolCalls=[],choicePosition=0){
|
|
4636
|
+
const streamedToolCalls=streamedToolCallsByChoice.get(choicePosition)
|
|
4637
|
+
||new Map();
|
|
4252
4638
|
for(let position=0;position<toolCalls.length;position++){
|
|
4253
4639
|
const toolCall=toolCalls[position]||{};
|
|
4254
4640
|
const toolFunction=toolCall.function||{};
|
|
4255
|
-
const key
|
|
4256
|
-
|
|
4257
|
-
:
|
|
4258
|
-
?`id:${toolCall.id}`
|
|
4259
|
-
:`position:${position}`;
|
|
4641
|
+
const key=`position:${Number.isInteger(toolCall.index)
|
|
4642
|
+
?toolCall.index
|
|
4643
|
+
:position}`;
|
|
4260
4644
|
const record=streamedToolCalls.get(key)||{
|
|
4261
4645
|
arguments:'',
|
|
4646
|
+
id:'',
|
|
4647
|
+
invalidArguments:false,
|
|
4648
|
+
invalidIdentity:false,
|
|
4649
|
+
invalidName:false,
|
|
4262
4650
|
name:'',
|
|
4263
|
-
order:streamedToolCalls.size
|
|
4651
|
+
order:streamedToolCalls.size,
|
|
4652
|
+
type:''
|
|
4264
4653
|
};
|
|
4265
4654
|
|
|
4266
|
-
if(
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
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;
|
|
4275
4680
|
}
|
|
4276
4681
|
}
|
|
4277
4682
|
|
|
4278
4683
|
if(typeof toolFunction.arguments==='string'){
|
|
4279
4684
|
record.arguments+=toolFunction.arguments;
|
|
4280
|
-
}else if(toolFunction.arguments
|
|
4281
|
-
record.
|
|
4685
|
+
}else if(toolFunction.arguments!==undefined){
|
|
4686
|
+
record.invalidArguments=true;
|
|
4282
4687
|
}
|
|
4283
4688
|
|
|
4284
4689
|
streamedToolCalls.set(key,record);
|
|
4285
4690
|
}
|
|
4691
|
+
streamedToolCallsByChoice.set(choicePosition,streamedToolCalls);
|
|
4286
4692
|
}
|
|
4287
4693
|
|
|
4288
4694
|
try{
|
|
@@ -4303,64 +4709,88 @@ class AI {
|
|
|
4303
4709
|
//alert(3)
|
|
4304
4710
|
//console.log(lines);
|
|
4305
4711
|
|
|
4306
|
-
lines
|
|
4307
|
-
|
|
4308
|
-
chunkCache+=delta;
|
|
4712
|
+
for(const eventData of lines){
|
|
4713
|
+
chunkCache+=eventData;
|
|
4309
4714
|
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
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;
|
|
4313
4770
|
}
|
|
4314
4771
|
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
}
|
|
4335
|
-
|
|
4336
|
-
if (!reasoning && isThinking) {
|
|
4337
|
-
//remove thinking chunks
|
|
4338
|
-
chunkString='';
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
|
-
if (!reasoning) {
|
|
4342
|
-
isThinking = false;
|
|
4343
|
-
}
|
|
4344
|
-
|
|
4345
|
-
chunkCache='';
|
|
4346
|
-
|
|
4347
|
-
if(value==='' && !tool_calls.length){
|
|
4348
|
-
return;
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
|
-
if(value){
|
|
4352
|
-
streamHandler(value,`M-${id}`, isThinking);
|
|
4353
|
-
chunkString+=value;
|
|
4354
|
-
}
|
|
4355
|
-
|
|
4356
|
-
if(tool_calls.length){
|
|
4357
|
-
receiveStreamedToolCalls(tool_calls);
|
|
4358
|
-
}
|
|
4359
|
-
} catch(err) {
|
|
4360
|
-
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;
|
|
4361
4791
|
}
|
|
4362
4792
|
}
|
|
4363
|
-
|
|
4793
|
+
}
|
|
4364
4794
|
}
|
|
4365
4795
|
}catch(error){
|
|
4366
4796
|
if(isAIRequestAbort(error,signal)){
|
|
@@ -4371,30 +4801,108 @@ class AI {
|
|
|
4371
4801
|
reader.releaseLock();
|
|
4372
4802
|
}
|
|
4373
4803
|
|
|
4374
|
-
const
|
|
4375
|
-
const
|
|
4376
|
-
function
|
|
4377
|
-
return a
|
|
4804
|
+
const structuralToolCallsByChoice=new Map();
|
|
4805
|
+
const orderedStreamChoices=[...streamedToolCallsByChoice.entries()].sort(
|
|
4806
|
+
function sortStreamChoices(a,b){
|
|
4807
|
+
return a[0]-b[0];
|
|
4378
4808
|
}
|
|
4379
4809
|
);
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
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'
|
|
4384
4861
|
}
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4862
|
+
];
|
|
4863
|
+
for(const [choicePosition,toolCalls] of structuralToolCallsByChoice){
|
|
4864
|
+
if(choicePosition===0){
|
|
4865
|
+
continue;
|
|
4388
4866
|
}
|
|
4389
|
-
|
|
4390
|
-
|
|
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
|
+
});
|
|
4391
4876
|
}
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
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);
|
|
4398
4906
|
}
|
|
4399
4907
|
if(finishSpeech){
|
|
4400
4908
|
this.finishTTS();
|
|
@@ -4403,7 +4911,7 @@ class AI {
|
|
|
4403
4911
|
|
|
4404
4912
|
//sync
|
|
4405
4913
|
speechTurnCompleted=true;
|
|
4406
|
-
return streamResult;
|
|
4914
|
+
return returnCompletion?completion:streamResult;
|
|
4407
4915
|
}catch(error){
|
|
4408
4916
|
if(isAIRequestAbort(error,signal)){
|
|
4409
4917
|
throw normalizeAIRequestAbort(error);
|
|
@@ -4422,12 +4930,23 @@ class AI {
|
|
|
4422
4930
|
localOnly=false,
|
|
4423
4931
|
tools=[],
|
|
4424
4932
|
toolChoice='auto',
|
|
4425
|
-
parallelToolCalls=
|
|
4933
|
+
parallelToolCalls=false,
|
|
4426
4934
|
id=Date.now(),
|
|
4427
4935
|
signal=null,
|
|
4428
4936
|
onRequest=function ignoreFetchRequest(){},
|
|
4429
|
-
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
|
|
4430
4948
|
}={}){
|
|
4949
|
+
validateAIStructuralRequest(messages,tools,parallelToolCalls);
|
|
4431
4950
|
if(localOnly!==true&&localOnly!==false){
|
|
4432
4951
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
4433
4952
|
}
|
|
@@ -4448,7 +4967,18 @@ class AI {
|
|
|
4448
4967
|
tools,
|
|
4449
4968
|
toolChoice,
|
|
4450
4969
|
parallelToolCalls,
|
|
4451
|
-
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}:{})
|
|
4452
4982
|
};
|
|
4453
4983
|
await this.#reportRequest(onRequest,request,id);
|
|
4454
4984
|
if(signal?.aborted){
|
|
@@ -4466,6 +4996,7 @@ class AI {
|
|
|
4466
4996
|
if(signal?.aborted){
|
|
4467
4997
|
throw normalizeAIRequestAbort();
|
|
4468
4998
|
}
|
|
4999
|
+
normalizeAICompletionToolCalls(response);
|
|
4469
5000
|
await onResponse(response,id,false);
|
|
4470
5001
|
if(signal?.aborted){
|
|
4471
5002
|
throw normalizeAIRequestAbort();
|
|
@@ -4492,7 +5023,7 @@ class AI {
|
|
|
4492
5023
|
structuredOutput=false,
|
|
4493
5024
|
tools=[],
|
|
4494
5025
|
tool_choice='auto',
|
|
4495
|
-
parallel_tool_calls=
|
|
5026
|
+
parallel_tool_calls=false,
|
|
4496
5027
|
id=Date.now(),
|
|
4497
5028
|
requestHandler=function ignoreFetchRequest(){},
|
|
4498
5029
|
signal=null,
|
|
@@ -4531,11 +5062,12 @@ class AI {
|
|
|
4531
5062
|
structuredOutput=false,
|
|
4532
5063
|
tools=[],
|
|
4533
5064
|
tool_choice='auto',
|
|
4534
|
-
parallel_tool_calls=
|
|
5065
|
+
parallel_tool_calls=false,
|
|
4535
5066
|
id=Date.now(),
|
|
4536
5067
|
requestHandler=function ignoreFetchRequest(){},
|
|
4537
5068
|
signal=null,
|
|
4538
5069
|
){
|
|
5070
|
+
validateAIStructuralRequest(messages,tools,parallel_tool_calls);
|
|
4539
5071
|
this.#assertServiceConfigured(this.llmService);
|
|
4540
5072
|
if(signal&&(
|
|
4541
5073
|
typeof signal.aborted!=='boolean'
|
|
@@ -4610,16 +5142,14 @@ class AI {
|
|
|
4610
5142
|
if(signal?.aborted){
|
|
4611
5143
|
throw normalizeAIRequestAbort();
|
|
4612
5144
|
}
|
|
4613
|
-
this.#assertRequiredOllamaToolCall(
|
|
4614
|
-
Array.isArray(nativeResponse?.message?.tool_calls)
|
|
4615
|
-
?nativeResponse.message.tool_calls
|
|
4616
|
-
:[],
|
|
4617
|
-
tool_choice
|
|
4618
|
-
);
|
|
4619
5145
|
const responseJSON=this.#openAICompatibleOllamaResponse(
|
|
4620
5146
|
nativeResponse,
|
|
4621
5147
|
id
|
|
4622
5148
|
);
|
|
5149
|
+
this.#assertRequiredOllamaToolCall(
|
|
5150
|
+
normalizeAICompletionToolCalls(responseJSON),
|
|
5151
|
+
tool_choice
|
|
5152
|
+
);
|
|
4623
5153
|
|
|
4624
5154
|
if(signal?.aborted){
|
|
4625
5155
|
throw normalizeAIRequestAbort();
|
|
@@ -4689,6 +5219,7 @@ class AI {
|
|
|
4689
5219
|
|
|
4690
5220
|
//console.log(responseJSON);
|
|
4691
5221
|
//async
|
|
5222
|
+
normalizeAICompletionToolCalls(responseJSON);
|
|
4692
5223
|
await responseHandler(responseJSON,id,false);
|
|
4693
5224
|
//sync
|
|
4694
5225
|
return responseJSON;
|
|
@@ -4715,7 +5246,10 @@ class AI {
|
|
|
4715
5246
|
try{
|
|
4716
5247
|
this.#assertServiceConfigured(this.ttsService,'tts');
|
|
4717
5248
|
}catch(error){
|
|
4718
|
-
|
|
5249
|
+
this.#publishTTSFailure(error,{
|
|
5250
|
+
boundary:'synthesis',
|
|
5251
|
+
generation:this.speechGeneration
|
|
5252
|
+
});
|
|
4719
5253
|
return Promise.resolve(false);
|
|
4720
5254
|
}
|
|
4721
5255
|
|
|
@@ -4739,20 +5273,15 @@ class AI {
|
|
|
4739
5273
|
|
|
4740
5274
|
#extractSpeechSegments(end=false){
|
|
4741
5275
|
const segments=[];
|
|
4742
|
-
const maximumLength=220;
|
|
4743
5276
|
let remainder=this.audioMessageChunks;
|
|
4744
5277
|
|
|
4745
|
-
while(remainder.
|
|
5278
|
+
while(remainder.length>0){
|
|
4746
5279
|
const terminator=this.#findSpeechTerminator(remainder,end);
|
|
4747
5280
|
let boundary=terminator
|
|
4748
5281
|
?terminator.index+terminator[0].length
|
|
4749
5282
|
:-1;
|
|
4750
5283
|
|
|
4751
|
-
if(boundary<0&&
|
|
4752
|
-
const candidate=remainder.slice(0,maximumLength+1);
|
|
4753
|
-
const whitespace=candidate.lastIndexOf(' ');
|
|
4754
|
-
boundary=whitespace>=80?whitespace+1:maximumLength;
|
|
4755
|
-
}else if(boundary<0&&end){
|
|
5284
|
+
if(boundary<0&&end){
|
|
4756
5285
|
boundary=remainder.length;
|
|
4757
5286
|
}
|
|
4758
5287
|
|
|
@@ -4760,8 +5289,8 @@ class AI {
|
|
|
4760
5289
|
break;
|
|
4761
5290
|
}
|
|
4762
5291
|
|
|
4763
|
-
const segment=remainder.slice(0,boundary)
|
|
4764
|
-
remainder=remainder.slice(boundary)
|
|
5292
|
+
const segment=remainder.slice(0,boundary);
|
|
5293
|
+
remainder=remainder.slice(boundary);
|
|
4765
5294
|
|
|
4766
5295
|
if(segment){
|
|
4767
5296
|
segments.push(segment);
|
|
@@ -4827,7 +5356,11 @@ class AI {
|
|
|
4827
5356
|
}
|
|
4828
5357
|
).catch(
|
|
4829
5358
|
function discardFailedSpeechJob(error){
|
|
4830
|
-
return runtime.#failSpeechJob(
|
|
5359
|
+
return runtime.#failSpeechJob(
|
|
5360
|
+
job,
|
|
5361
|
+
error,
|
|
5362
|
+
job.state==='decoding'?'decode':'synthesis'
|
|
5363
|
+
);
|
|
4831
5364
|
}
|
|
4832
5365
|
);
|
|
4833
5366
|
|
|
@@ -4852,6 +5385,7 @@ class AI {
|
|
|
4852
5385
|
return this.#cancelSpeechJob(job);
|
|
4853
5386
|
}
|
|
4854
5387
|
|
|
5388
|
+
job.state='decoding';
|
|
4855
5389
|
const audioContext=this.#getSpeechAudioContext();
|
|
4856
5390
|
return this.playAudio(
|
|
4857
5391
|
audio.chunks,
|
|
@@ -5332,6 +5866,12 @@ class AI {
|
|
|
5332
5866
|
this.speechResumePending=false;
|
|
5333
5867
|
}
|
|
5334
5868
|
this.#waitForSpeechGesture(error);
|
|
5869
|
+
if(error?.name!=='NotAllowedError'){
|
|
5870
|
+
this.#publishTTSFailure(error,{
|
|
5871
|
+
boundary:'playback-resume',
|
|
5872
|
+
generation:this.speechGeneration
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5335
5875
|
return false;
|
|
5336
5876
|
}
|
|
5337
5877
|
|
|
@@ -5341,7 +5881,7 @@ class AI {
|
|
|
5341
5881
|
|
|
5342
5882
|
async playAudio(
|
|
5343
5883
|
audioChunks=[],
|
|
5344
|
-
audioContext=
|
|
5884
|
+
audioContext=null,
|
|
5345
5885
|
sourceNode=null,
|
|
5346
5886
|
audioType=this.audioType,
|
|
5347
5887
|
speechJob=null
|
|
@@ -5364,19 +5904,20 @@ class AI {
|
|
|
5364
5904
|
|
|
5365
5905
|
try{
|
|
5366
5906
|
job.state='decoding';
|
|
5907
|
+
const playbackContext=audioContext||this.#getSpeechAudioContext();
|
|
5367
5908
|
const audioBlob=new Blob(audioChunks,{type:audioType});
|
|
5368
5909
|
const arrayBuffer=await audioBlob.arrayBuffer();
|
|
5369
|
-
const audioBuffer=await
|
|
5910
|
+
const audioBuffer=await playbackContext.decodeAudioData(arrayBuffer);
|
|
5370
5911
|
|
|
5371
5912
|
if(this.muted||job.generation!==this.speechGeneration){
|
|
5372
5913
|
return this.#cancelSpeechJob(job);
|
|
5373
5914
|
}
|
|
5374
5915
|
|
|
5375
|
-
const preparedSource=sourceNode||
|
|
5916
|
+
const preparedSource=sourceNode||playbackContext.createBufferSource();
|
|
5376
5917
|
const runtime=this;
|
|
5377
5918
|
|
|
5378
5919
|
preparedSource.buffer=audioBuffer;
|
|
5379
|
-
preparedSource.connect(
|
|
5920
|
+
preparedSource.connect(playbackContext.destination);
|
|
5380
5921
|
preparedSource.__arcaneStarted=false;
|
|
5381
5922
|
preparedSource.onended=function finishQueuedSpeechSource(){
|
|
5382
5923
|
runtime.nextSentance(job);
|
|
@@ -5387,14 +5928,20 @@ class AI {
|
|
|
5387
5928
|
this.#requestSpeechPlayback();
|
|
5388
5929
|
return true;
|
|
5389
5930
|
}catch(error){
|
|
5390
|
-
return this.#failSpeechJob(job,error);
|
|
5931
|
+
return this.#failSpeechJob(job,error,'decode');
|
|
5391
5932
|
}
|
|
5392
5933
|
}
|
|
5393
5934
|
|
|
5394
5935
|
#requestSpeechPlayback(){
|
|
5936
|
+
const runtime=this;
|
|
5395
5937
|
this.#pumpSpeechPlayback().catch(
|
|
5396
5938
|
function reportSpeechPlaybackFailure(error){
|
|
5397
|
-
|
|
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);
|
|
5398
5945
|
}
|
|
5399
5946
|
);
|
|
5400
5947
|
}
|
|
@@ -5405,10 +5952,12 @@ class AI {
|
|
|
5405
5952
|
}
|
|
5406
5953
|
|
|
5407
5954
|
this.speechPlaybackStarting=true;
|
|
5955
|
+
let activeJob=null;
|
|
5408
5956
|
|
|
5409
5957
|
try{
|
|
5410
5958
|
while(!this.isSpeaking&&!this.muted){
|
|
5411
5959
|
const job=this.speechJobs[0];
|
|
5960
|
+
activeJob=job||null;
|
|
5412
5961
|
|
|
5413
5962
|
if(!job){
|
|
5414
5963
|
return false;
|
|
@@ -5449,14 +5998,21 @@ class AI {
|
|
|
5449
5998
|
job.sourceNode.__arcaneStarted=true;
|
|
5450
5999
|
this.currentSpeechJob=job;
|
|
5451
6000
|
this.isSpeaking=true;
|
|
5452
|
-
job.sourceNode.start(0);
|
|
6001
|
+
await job.sourceNode.start(0);
|
|
5453
6002
|
return true;
|
|
5454
6003
|
}catch(error){
|
|
5455
6004
|
this.currentSpeechJob=null;
|
|
5456
6005
|
this.isSpeaking=false;
|
|
5457
|
-
this.#failSpeechJob(job,error);
|
|
6006
|
+
this.#failSpeechJob(job,error,'playback-start');
|
|
5458
6007
|
}
|
|
5459
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;
|
|
5460
6016
|
}finally{
|
|
5461
6017
|
this.speechPlaybackStarting=false;
|
|
5462
6018
|
|
|
@@ -5502,13 +6058,76 @@ class AI {
|
|
|
5502
6058
|
return false;
|
|
5503
6059
|
}
|
|
5504
6060
|
|
|
5505
|
-
#
|
|
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'){
|
|
5506
6120
|
if(job.state==='failed'||job.state==='cancelled'){
|
|
5507
6121
|
return false;
|
|
5508
6122
|
}
|
|
5509
6123
|
|
|
5510
6124
|
job.state='failed';
|
|
5511
6125
|
|
|
6126
|
+
this.#publishTTSFailure(error,{
|
|
6127
|
+
boundary,
|
|
6128
|
+
generation:job.generation
|
|
6129
|
+
});
|
|
6130
|
+
|
|
5512
6131
|
if(job.sourceNode){
|
|
5513
6132
|
job.sourceNode.onended=null;
|
|
5514
6133
|
}
|
|
@@ -5520,10 +6139,6 @@ class AI {
|
|
|
5520
6139
|
this.isSpeaking=false;
|
|
5521
6140
|
}
|
|
5522
6141
|
|
|
5523
|
-
if(job.generation===this.speechGeneration&&error?.name!=='AbortError'){
|
|
5524
|
-
console.warn('AI speech synthesis failed.');
|
|
5525
|
-
}
|
|
5526
|
-
|
|
5527
6142
|
this.#requestSpeechPlayback();
|
|
5528
6143
|
return false;
|
|
5529
6144
|
}
|
|
@@ -5635,7 +6250,7 @@ function installAIUserReadyRegistration(){
|
|
|
5635
6250
|
delete globalThis[AI_USER_READY_REGISTRATION_KEY];
|
|
5636
6251
|
}
|
|
5637
6252
|
}
|
|
5638
|
-
registration=
|
|
6253
|
+
registration=completeValue({
|
|
5639
6254
|
protocol:AI_USER_READY_REGISTRATION_PROTOCOL,
|
|
5640
6255
|
dispose:disposeAIUserReadyRegistration
|
|
5641
6256
|
});
|