arcane-os 0.5.16 → 0.5.18
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 +44 -0
- package/README.md +42 -19
- package/bin/arcane-test.mjs +16 -13
- package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
- package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
- package/browser-runtime/ai/browser-wasm.mjs +6 -3
- package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
- package/browser-runtime/ai/model-controller.mjs +94 -91
- package/browser-runtime/ai/speech-worker-client.mjs +12 -9
- package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
- package/browser-runtime/dependencies/strong-type/index.js +207 -82
- package/browser-runtime/dependencies/strong-type/package.json +15 -6
- package/browser-runtime/dom-event-instrumentation.mjs +24 -20
- package/browser-runtime/event-manager.mjs +111 -108
- package/browser-runtime/speech-text.mjs +35 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +8 -8
- package/docs/reference/ai/browser-speech.md +230 -16
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/availability-and-normalization.md +42 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +60 -4
- package/docs/reference/inventory/runtime-components.json +2 -2
- package/docs/reference/inventory/runtime-modules.json +29 -31
- package/docs/reference/protocols.md +17 -13
- package/docs/reference/runtime-components.md +6 -5
- package/docs/reference/runtime-modules.md +212 -70
- package/docs/reference/sdk-api.md +197 -11
- package/examples/wasm-ai-demo/index.html +1 -0
- package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
- package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
- package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
- package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
- package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
- package/node_modules/strong-type/README.md +42 -24
- package/node_modules/strong-type/benchmark/core.js +324 -0
- package/node_modules/strong-type/index.js +207 -82
- package/node_modules/strong-type/package.json +14 -6
- package/package.json +4 -3
- package/runtime/arcane/components/app-bar.html +5 -2
- package/runtime/arcane/components/assistant-panel.html +5 -2
- package/runtime/arcane/components/chart.html +15 -12
- package/runtime/arcane/components/chat.html +118 -141
- package/runtime/arcane/components/dashboard-config.html +6 -3
- package/runtime/arcane/components/data-view.html +4 -1
- package/runtime/arcane/components/directory-picker.html +5 -2
- package/runtime/arcane/components/document-inspector.html +6 -3
- package/runtime/arcane/components/file-drop.html +5 -2
- package/runtime/arcane/components/file-inspector.html +9 -6
- package/runtime/arcane/components/file-manager.html +16 -13
- package/runtime/arcane/components/local-ai-status.html +5 -2
- package/runtime/arcane/components/markdown-document.html +14 -11
- package/runtime/arcane/components/markdown-editor.html +4 -1
- package/runtime/arcane/components/modal.html +4 -1
- package/runtime/arcane/components/output-panel.html +6 -3
- package/runtime/arcane/components/preferences-form.html +4 -1
- package/runtime/arcane/components/record-timeline.html +8 -5
- package/runtime/arcane/components/relationship-board.html +9 -6
- package/runtime/arcane/components/source-code-viewer.html +12 -9
- package/runtime/arcane/components/source-explanation.html +8 -5
- package/runtime/arcane/components/speech.html +24 -21
- package/runtime/arcane/components/summary-strip.html +4 -1
- package/runtime/arcane/components/task-progress.html +6 -3
- package/runtime/arcane/components/terminal-workspace.html +4 -1
- package/runtime/arcane/components/voice-transcription.html +11 -8
- package/runtime/arcane/components/web-navigator.html +5 -2
- package/runtime/arcane/entities/ApiModelRecord.js +5 -2
- package/runtime/arcane/entities/Calculation.js +5 -2
- package/runtime/arcane/entities/Chat.js +33 -33
- package/runtime/arcane/entities/CommunicationMessage.js +4 -1
- package/runtime/arcane/entities/CommunicationThread.js +4 -1
- package/runtime/arcane/entities/File.js +1 -1
- package/runtime/arcane/entities/Image.js +8 -5
- package/runtime/arcane/entities/IntentEnvelope.js +20 -17
- package/runtime/arcane/entities/Preference.js +9 -6
- package/runtime/arcane/entities/Theme.js +5 -2
- package/runtime/arcane/entities/User.js +11 -11
- package/runtime/arcane/entities/Weather.js +5 -2
- package/runtime/arcane/modules/AI.js +503 -187
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
- package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
- package/runtime/arcane/modules/AIRuntimeState.js +29 -26
- package/runtime/arcane/modules/AnsiText.js +4 -1
- package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
- package/runtime/arcane/modules/AppDataScope.js +8 -5
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
- package/runtime/arcane/modules/AsyncBoundary.js +13 -10
- package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
- package/runtime/arcane/modules/CalculatorEngine.js +5 -2
- package/runtime/arcane/modules/ChatRecords.js +18 -15
- package/runtime/arcane/modules/CommunicationAppController.js +9 -6
- package/runtime/arcane/modules/CommunicationHub.js +9 -6
- package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
- package/runtime/arcane/modules/ComponentContracts.js +56 -53
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
- package/runtime/arcane/modules/ConversationActionItems.js +15 -12
- package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
- package/runtime/arcane/modules/ConversationTimebox.js +28 -25
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
- package/runtime/arcane/modules/DBLS.js +7 -4
- package/runtime/arcane/modules/DBOPFS.js +7 -7
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
- package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
- package/runtime/arcane/modules/DirectoryPicker.js +8 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
- package/runtime/arcane/modules/DocumentNavigation.js +7 -4
- package/runtime/arcane/modules/Errors.js +28 -25
- package/runtime/arcane/modules/HTMLImport.js +7 -4
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
- package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
- package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
- package/runtime/arcane/modules/MD.js +1 -1
- package/runtime/arcane/modules/Mail.js +46 -43
- package/runtime/arcane/modules/MailOutbox.mjs +48 -45
- package/runtime/arcane/modules/MailTransport.mjs +29 -26
- package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
- package/runtime/arcane/modules/MemoryRecords.js +7 -4
- package/runtime/arcane/modules/MessageAdvisory.js +6 -3
- package/runtime/arcane/modules/ModelDefinition.js +7 -4
- package/runtime/arcane/modules/Ollama.js +6 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
- package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
- package/runtime/arcane/modules/PreferenceStore.js +20 -17
- package/runtime/arcane/modules/PreparedSpeech.js +485 -0
- package/runtime/arcane/modules/Questionnaire.js +6 -3
- package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
- package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
- package/runtime/arcane/modules/RecordReviewStore.js +12 -9
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
- package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
- package/runtime/arcane/modules/ScreenCapture.js +20 -17
- package/runtime/arcane/modules/SpeechPlayback.js +61 -28
- package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
- package/runtime/arcane/modules/SystemAppearance.js +5 -2
- package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
- package/runtime/arcane/modules/TerminalClient.js +14 -11
- package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
- package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
- package/runtime/arcane/modules/ThemeManager.js +5 -2
- package/runtime/arcane/modules/TimeGuard.js +1 -1
- package/runtime/arcane/modules/ToolCallRouter.js +11 -8
- package/runtime/arcane/modules/WaitForComponent.js +19 -16
- package/runtime/arcane/modules/YouTubeMedia.js +4 -1
- package/runtime/strong-type/index.js +1276 -352
- package/runtime/strong-type/package.json +69 -45
- package/src/app-descriptor.mjs +17 -14
- package/src/application-tests.mjs +4 -1
- package/src/cli/main.mjs +8 -5
- package/src/constants.mjs +4 -1
- package/src/dev-server.mjs +8 -5
- package/src/doctor.mjs +5 -2
- package/src/dom-event-instrumentation.mjs +24 -20
- package/src/errors.mjs +7 -3
- package/src/event-manager.mjs +111 -108
- package/src/event-queue.mjs +8 -5
- package/src/events.mjs +12 -9
- package/src/import-map.mjs +36 -27
- package/src/installed-sdk-runtime.mjs +4 -1
- package/src/integrated-provider-loader.mjs +9 -6
- package/src/mail-credentials.mjs +16 -13
- package/src/mail-server.mjs +53 -50
- package/src/mail.mjs +18 -15
- package/src/native-plan.mjs +12 -9
- package/src/native-provider-loader.mjs +7 -4
- package/src/packager/core.mjs +18 -15
- package/src/process.mjs +6 -3
- package/src/release-bundle.mjs +14 -11
- package/src/runtime.mjs +4 -1
- package/src/scaffold.mjs +9 -6
- package/src/sdk-browser-runtime.mjs +4 -1
- package/src/source-server.mjs +17 -14
- package/src/targets/index.mjs +5 -2
- package/src/templates/workspace-template.mjs +13 -6
- package/src/testing-loader.mjs +7 -4
- package/src/testing.mjs +10 -7
- package/src/toolchain.mjs +13 -10
- package/src/update-check.mjs +9 -6
- package/src/workspace-operation-lock.mjs +14 -11
- package/src/workspace-runtime.mjs +4 -1
- package/src/workspace.mjs +17 -14
- package/runtime/arcane/modules/AIResponseLength.js +0 -32
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {arcaneLogging} from 'arcane-os/logging';
|
|
5
|
+
import {stripSpeechFormatting} from 'arcane-os/speech-text';
|
|
2
6
|
import {
|
|
3
7
|
AI_RUNTIME_ROLES,
|
|
4
8
|
getAIRuntimeState,
|
|
@@ -87,7 +91,7 @@ async function awaitStreamCleanup(operation) {
|
|
|
87
91
|
|
|
88
92
|
function assertStreamCleanupComplete(outcome) {
|
|
89
93
|
const rejected = outcome.completed
|
|
90
|
-
&&
|
|
94
|
+
&& is.array(outcome.results)
|
|
91
95
|
&& outcome.results.some(function hasRejectedAIStreamCleanup(result) {
|
|
92
96
|
return result.status === 'rejected';
|
|
93
97
|
});
|
|
@@ -106,7 +110,7 @@ function assertRole(role) {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
function assertIdentifier(value, label) {
|
|
109
|
-
if (
|
|
113
|
+
if (!is.string(value)
|
|
110
114
|
|| value.length < 1
|
|
111
115
|
|| value.trim() !== value) {
|
|
112
116
|
fail(`${label} must be a nonempty trimmed string.`);
|
|
@@ -114,7 +118,7 @@ function assertIdentifier(value, label) {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
function nextSequence(value) {
|
|
117
|
-
if (
|
|
121
|
+
if (is.bigint(value)) {
|
|
118
122
|
return value + 1n;
|
|
119
123
|
}
|
|
120
124
|
if (value === Number.MAX_SAFE_INTEGER) {
|
|
@@ -128,16 +132,16 @@ function assertAbortSignal(signal) {
|
|
|
128
132
|
return;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
if (
|
|
132
|
-
||
|
|
133
|
-
||
|
|
134
|
-
||
|
|
135
|
+
if (!is.object(signal)
|
|
136
|
+
|| !is.boolean(signal.aborted)
|
|
137
|
+
|| !is.function(signal.addEventListener)
|
|
138
|
+
|| !is.function(signal.removeEventListener)) {
|
|
135
139
|
fail('AI provider operation signal must be an AbortSignal.');
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
function assertPlainObject(value, label) {
|
|
140
|
-
if (!value ||
|
|
144
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
141
145
|
fail(`${label} must be a plain object.`);
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -148,7 +152,7 @@ function assertPlainObject(value, label) {
|
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
function isPlainRecord(value) {
|
|
151
|
-
if (!value ||
|
|
155
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
152
156
|
return false;
|
|
153
157
|
}
|
|
154
158
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -159,7 +163,7 @@ function validateLLMToolDeclarations(value) {
|
|
|
159
163
|
if (value === undefined) {
|
|
160
164
|
return;
|
|
161
165
|
}
|
|
162
|
-
if (!
|
|
166
|
+
if (!is.array(value)) {
|
|
163
167
|
fail(
|
|
164
168
|
'AI LLM tools must be an array.',
|
|
165
169
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -177,9 +181,9 @@ function validateLLMToolDeclarations(value) {
|
|
|
177
181
|
|| !isPlainRecord(parameters.properties)
|
|
178
182
|
|| !isPlainRecord(messageSchema)
|
|
179
183
|
|| messageSchema.type !== 'string'
|
|
180
|
-
|| !
|
|
184
|
+
|| !is.integer(messageSchema.minLength)
|
|
181
185
|
|| messageSchema.minLength < 1
|
|
182
|
-
|| !
|
|
186
|
+
|| !is.array(parameters.required)
|
|
183
187
|
|| !parameters.required.includes('message')) {
|
|
184
188
|
fail(
|
|
185
189
|
`AI LLM tools[${index}] must require a nonempty string parameters.properties.message.`,
|
|
@@ -191,13 +195,13 @@ function validateLLMToolDeclarations(value) {
|
|
|
191
195
|
|
|
192
196
|
function validateLLMToolCall(call, label) {
|
|
193
197
|
if (!isPlainRecord(call)
|
|
194
|
-
||
|
|
198
|
+
|| !is.string(call.id)
|
|
195
199
|
|| !call.id.trim()
|
|
196
200
|
|| call.type !== 'function'
|
|
197
201
|
|| !isPlainRecord(call.function)
|
|
198
|
-
||
|
|
202
|
+
|| !is.string(call.function.name)
|
|
199
203
|
|| !call.function.name.trim()
|
|
200
|
-
||
|
|
204
|
+
|| !is.string(call.function.arguments)) {
|
|
201
205
|
fail(
|
|
202
206
|
`${label} must be one complete structural function call.`,
|
|
203
207
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -219,7 +223,7 @@ function validateLLMToolCall(call, label) {
|
|
|
219
223
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
220
224
|
);
|
|
221
225
|
}
|
|
222
|
-
if (
|
|
226
|
+
if (!is.string(argumentsRecord.message)
|
|
223
227
|
|| !argumentsRecord.message.trim()) {
|
|
224
228
|
fail(
|
|
225
229
|
`${label} arguments must include a nonempty user-facing message.`,
|
|
@@ -251,7 +255,7 @@ function validateLLMMessageToolCalls(message, label) {
|
|
|
251
255
|
}
|
|
252
256
|
const descriptor = Object.getOwnPropertyDescriptor(message, 'tool_calls');
|
|
253
257
|
if (!descriptor || !Object.hasOwn(descriptor, 'value')
|
|
254
|
-
|| !
|
|
258
|
+
|| !is.array(descriptor.value)) {
|
|
255
259
|
fail(
|
|
256
260
|
`${label}.tool_calls must be an array data property.`,
|
|
257
261
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -276,7 +280,7 @@ function validateLLMMessageToolCalls(message, label) {
|
|
|
276
280
|
|
|
277
281
|
function validateLLMRequestPayload(payload) {
|
|
278
282
|
assertPlainObject(payload, 'AI LLM request payload');
|
|
279
|
-
if (!
|
|
283
|
+
if (!is.array(payload.messages)) {
|
|
280
284
|
fail('AI LLM request payload.messages must be an array.');
|
|
281
285
|
}
|
|
282
286
|
const pendingToolCallIds = new Set();
|
|
@@ -316,7 +320,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
316
320
|
openedToolCall = true;
|
|
317
321
|
}
|
|
318
322
|
if (message.role === 'tool') {
|
|
319
|
-
if (
|
|
323
|
+
if (!is.string(message.content)
|
|
320
324
|
|| !message.content.trim()) {
|
|
321
325
|
fail(
|
|
322
326
|
`AI LLM request payload.messages[${index}] must contain a nonblank user-facing tool result.`,
|
|
@@ -324,7 +328,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
324
328
|
);
|
|
325
329
|
}
|
|
326
330
|
if (!pendingToolCallIds.size
|
|
327
|
-
||
|
|
331
|
+
|| !is.string(message.tool_call_id)
|
|
328
332
|
|| !pendingToolCallIds.has(message.tool_call_id)) {
|
|
329
333
|
fail(
|
|
330
334
|
`AI LLM request payload.messages[${index}] does not settle the pending structural tool call.`,
|
|
@@ -359,7 +363,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
359
363
|
payload.parallel_tool_calls
|
|
360
364
|
];
|
|
361
365
|
if (parallelValues.some(function invalidParallelLLMToolPreference(value) {
|
|
362
|
-
return value !== undefined &&
|
|
366
|
+
return value !== undefined && !is.boolean(value);
|
|
363
367
|
})) {
|
|
364
368
|
fail('AI LLM parallel tool-call preferences must be boolean when provided.');
|
|
365
369
|
}
|
|
@@ -376,7 +380,7 @@ function validateLLMTerminalMessage(message, label) {
|
|
|
376
380
|
}
|
|
377
381
|
|
|
378
382
|
function validateLLMTerminalResult(value, label) {
|
|
379
|
-
if (
|
|
383
|
+
if (is.string(value)) {
|
|
380
384
|
return value;
|
|
381
385
|
}
|
|
382
386
|
if (!isPlainRecord(value)) {
|
|
@@ -406,7 +410,7 @@ function validateLLMTerminalResult(value, label) {
|
|
|
406
410
|
}
|
|
407
411
|
const choicesDescriptor = Object.getOwnPropertyDescriptor(value, 'choices');
|
|
408
412
|
if (!choicesDescriptor || !Object.hasOwn(choicesDescriptor, 'value')
|
|
409
|
-
|| !
|
|
413
|
+
|| !is.array(choicesDescriptor.value)
|
|
410
414
|
|| choicesDescriptor.value.length === 0) {
|
|
411
415
|
fail(
|
|
412
416
|
`${label}.choices must be a nonempty array data property.`,
|
|
@@ -417,7 +421,7 @@ function validateLLMTerminalResult(value, label) {
|
|
|
417
421
|
for (let position = 0; position < choicesDescriptor.value.length; position += 1) {
|
|
418
422
|
const choice = choicesDescriptor.value[position];
|
|
419
423
|
if (!isPlainRecord(choice)
|
|
420
|
-
|| !
|
|
424
|
+
|| !is.safeInteger(choice.index)
|
|
421
425
|
|| choice.index < 0
|
|
422
426
|
|| indexes.has(choice.index)) {
|
|
423
427
|
fail(
|
|
@@ -461,22 +465,22 @@ function canonicalLLMToolCall(call) {
|
|
|
461
465
|
}
|
|
462
466
|
|
|
463
467
|
function copyLLMDataValue(value, seen = new Map()) {
|
|
464
|
-
if (!value ||
|
|
468
|
+
if (!value || !is.object(value)) {
|
|
465
469
|
return value;
|
|
466
470
|
}
|
|
467
471
|
if (seen.has(value)) {
|
|
468
472
|
return seen.get(value);
|
|
469
473
|
}
|
|
470
|
-
if (!
|
|
474
|
+
if (!is.array(value) && !isPlainRecord(value)) {
|
|
471
475
|
return value;
|
|
472
476
|
}
|
|
473
|
-
const result =
|
|
477
|
+
const result = is.array(value)
|
|
474
478
|
? new Array(value.length)
|
|
475
479
|
: Object.create(Object.getPrototypeOf(value));
|
|
476
480
|
seen.set(value, result);
|
|
477
481
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
478
482
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
479
|
-
if (
|
|
483
|
+
if (is.array(value) && key === 'length') {
|
|
480
484
|
continue;
|
|
481
485
|
}
|
|
482
486
|
const descriptor = descriptors[key];
|
|
@@ -493,12 +497,12 @@ function sameLLMDataValue(left, right, seen = new Map()) {
|
|
|
493
497
|
return true;
|
|
494
498
|
}
|
|
495
499
|
if (!left || !right
|
|
496
|
-
||
|
|
497
|
-
||
|
|
498
|
-
||
|
|
500
|
+
|| !is.object(left)
|
|
501
|
+
|| !is.object(right)
|
|
502
|
+
|| is.array(left) !== is.array(right)) {
|
|
499
503
|
return false;
|
|
500
504
|
}
|
|
501
|
-
if (!
|
|
505
|
+
if (!is.array(left)
|
|
502
506
|
&& (!isPlainRecord(left) || !isPlainRecord(right))) {
|
|
503
507
|
return false;
|
|
504
508
|
}
|
|
@@ -561,11 +565,11 @@ function terminalLLMToolCallRecord(choiceIndex, completeCalls) {
|
|
|
561
565
|
}
|
|
562
566
|
|
|
563
567
|
function terminalLLMToolCalls(value) {
|
|
564
|
-
if (
|
|
568
|
+
if (is.string(value)) {
|
|
565
569
|
return {direct: terminalLLMToolCallRecord(null, []), choices: []};
|
|
566
570
|
}
|
|
567
571
|
if (Object.hasOwn(value, 'message')) {
|
|
568
|
-
const completeCalls =
|
|
572
|
+
const completeCalls = is.array(value.message?.tool_calls)
|
|
569
573
|
? value.message.tool_calls
|
|
570
574
|
: [];
|
|
571
575
|
return {
|
|
@@ -576,7 +580,7 @@ function terminalLLMToolCalls(value) {
|
|
|
576
580
|
return {
|
|
577
581
|
direct: null,
|
|
578
582
|
choices: value.choices.map(function retainTerminalLLMChoice(choice) {
|
|
579
|
-
const completeCalls =
|
|
583
|
+
const completeCalls = is.array(choice.message?.tool_calls)
|
|
580
584
|
? choice.message.tool_calls
|
|
581
585
|
: [];
|
|
582
586
|
return terminalLLMToolCallRecord(choice.index, completeCalls);
|
|
@@ -636,7 +640,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
636
640
|
}
|
|
637
641
|
const descriptor = Object.getOwnPropertyDescriptor(source, 'tool_calls');
|
|
638
642
|
if (!descriptor || !Object.hasOwn(descriptor, 'value')
|
|
639
|
-
|| !
|
|
643
|
+
|| !is.array(descriptor.value)) {
|
|
640
644
|
throw llmStreamToolCallMismatch(
|
|
641
645
|
`${label}.tool_calls must be an array data property.`
|
|
642
646
|
);
|
|
@@ -685,7 +689,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
685
689
|
);
|
|
686
690
|
}
|
|
687
691
|
if (fragment.index !== undefined
|
|
688
|
-
&& (!
|
|
692
|
+
&& (!is.safeInteger(fragment.index) || fragment.index < 0)) {
|
|
689
693
|
throw llmStreamToolCallMismatch(
|
|
690
694
|
`${label}.tool_calls[${position}] has an invalid structural call index.`
|
|
691
695
|
);
|
|
@@ -700,7 +704,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
700
704
|
invalid: false
|
|
701
705
|
};
|
|
702
706
|
if (fragment.id !== undefined) {
|
|
703
|
-
if (
|
|
707
|
+
if (!is.string(fragment.id)
|
|
704
708
|
|| !fragment.id
|
|
705
709
|
|| (retained.id && retained.id !== fragment.id)) {
|
|
706
710
|
retained.invalid = true;
|
|
@@ -709,7 +713,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
709
713
|
}
|
|
710
714
|
}
|
|
711
715
|
if (fragment.type !== undefined) {
|
|
712
|
-
if (
|
|
716
|
+
if (!is.string(fragment.type)
|
|
713
717
|
|| !fragment.type
|
|
714
718
|
|| (retained.type && retained.type !== fragment.type)) {
|
|
715
719
|
retained.invalid = true;
|
|
@@ -725,14 +729,14 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
725
729
|
? fragment.function
|
|
726
730
|
: {};
|
|
727
731
|
if (functionFragment.name !== undefined) {
|
|
728
|
-
if (
|
|
732
|
+
if (!is.string(functionFragment.name)) {
|
|
729
733
|
retained.invalid = true;
|
|
730
734
|
} else {
|
|
731
735
|
retained.name += functionFragment.name;
|
|
732
736
|
}
|
|
733
737
|
}
|
|
734
738
|
if (functionFragment.arguments !== undefined) {
|
|
735
|
-
if (
|
|
739
|
+
if (!is.string(functionFragment.arguments)) {
|
|
736
740
|
retained.invalid = true;
|
|
737
741
|
} else {
|
|
738
742
|
retained.arguments += functionFragment.arguments;
|
|
@@ -761,7 +765,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
761
765
|
}
|
|
762
766
|
);
|
|
763
767
|
if (choice.index !== undefined
|
|
764
|
-
&& (!
|
|
768
|
+
&& (!is.safeInteger(choice.index) || choice.index < 0)) {
|
|
765
769
|
throw llmStreamToolCallMismatch(
|
|
766
770
|
`AI provider stream choice ${position} has an invalid index.`
|
|
767
771
|
);
|
|
@@ -810,7 +814,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
810
814
|
'AI provider stream chunk.message'
|
|
811
815
|
);
|
|
812
816
|
}
|
|
813
|
-
if (
|
|
817
|
+
if (is.array(chunk.choices)) {
|
|
814
818
|
for (let position = 0; position < chunk.choices.length; position += 1) {
|
|
815
819
|
observeChoice(chunk.choices[position], position);
|
|
816
820
|
}
|
|
@@ -1008,13 +1012,13 @@ function projectLLMStreamData(value, seen = new Map()) {
|
|
|
1008
1012
|
if (value === null || value === undefined) {
|
|
1009
1013
|
return value;
|
|
1010
1014
|
}
|
|
1011
|
-
if (
|
|
1015
|
+
if (!is.object(value)) {
|
|
1012
1016
|
return value;
|
|
1013
1017
|
}
|
|
1014
1018
|
if (seen.has(value)) {
|
|
1015
1019
|
return seen.get(value);
|
|
1016
1020
|
}
|
|
1017
|
-
if (
|
|
1021
|
+
if (is.array(value)) {
|
|
1018
1022
|
const result = [];
|
|
1019
1023
|
seen.set(value, result);
|
|
1020
1024
|
for (const item of value) {
|
|
@@ -1030,7 +1034,7 @@ function projectLLMStreamData(value, seen = new Map()) {
|
|
|
1030
1034
|
let sourceDataFields = 0;
|
|
1031
1035
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1032
1036
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1033
|
-
if (
|
|
1037
|
+
if (is.symbol(key)) {
|
|
1034
1038
|
continue;
|
|
1035
1039
|
}
|
|
1036
1040
|
const descriptor = descriptors[key];
|
|
@@ -1054,19 +1058,19 @@ function projectLLMStreamChunk(value) {
|
|
|
1054
1058
|
}
|
|
1055
1059
|
|
|
1056
1060
|
function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
|
|
1057
|
-
if (
|
|
1061
|
+
if (is.function(value)) {
|
|
1058
1062
|
fail(
|
|
1059
1063
|
'AI providers receive data-only request payloads.',
|
|
1060
1064
|
'ARCANE_AI_PROVIDER_CALLBACK_BOUNDARY'
|
|
1061
1065
|
);
|
|
1062
1066
|
}
|
|
1063
|
-
if (!value ||
|
|
1067
|
+
if (!value || !is.object(value)) {
|
|
1064
1068
|
return;
|
|
1065
1069
|
}
|
|
1066
|
-
if (value instanceof ArrayBuffer ||
|
|
1070
|
+
if (value instanceof ArrayBuffer || is.arrayBufferView(value)) {
|
|
1067
1071
|
return;
|
|
1068
1072
|
}
|
|
1069
|
-
if (
|
|
1073
|
+
if (is.function(globalThis.Blob) && value instanceof Blob) {
|
|
1070
1074
|
return;
|
|
1071
1075
|
}
|
|
1072
1076
|
if (seen.has(value)) {
|
|
@@ -1076,7 +1080,7 @@ function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
|
|
|
1076
1080
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1077
1081
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1078
1082
|
const descriptor = descriptors[key];
|
|
1079
|
-
if (
|
|
1083
|
+
if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
|
|
1080
1084
|
fail('AI provider request payloads must contain data properties only.');
|
|
1081
1085
|
}
|
|
1082
1086
|
assertCallbackFreeProviderValue(descriptor.value, seen);
|
|
@@ -1089,7 +1093,7 @@ function assertClosedRecord(value, keys, label) {
|
|
|
1089
1093
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1090
1094
|
const actual = Reflect.ownKeys(value);
|
|
1091
1095
|
if (actual.some(function hasSymbolKey(key) {
|
|
1092
|
-
return
|
|
1096
|
+
return is.symbol(key);
|
|
1093
1097
|
})) {
|
|
1094
1098
|
fail(`${label} must not contain symbol keys.`);
|
|
1095
1099
|
}
|
|
@@ -1118,7 +1122,7 @@ function immutableSelection(role, value) {
|
|
|
1118
1122
|
);
|
|
1119
1123
|
assertIdentifier(value.providerId, `${role} provider selection.providerId`);
|
|
1120
1124
|
assertIdentifier(value.modelId, `${role} provider selection.modelId`);
|
|
1121
|
-
if (value.localOnly !== null &&
|
|
1125
|
+
if (value.localOnly !== null && !is.boolean(value.localOnly)) {
|
|
1122
1126
|
fail(`${role} provider selection.localOnly must be null or a boolean.`);
|
|
1123
1127
|
}
|
|
1124
1128
|
|
|
@@ -1188,7 +1192,7 @@ function immutableStartupOptions(options) {
|
|
|
1188
1192
|
assertPlainObject(options, 'AI provider startup options');
|
|
1189
1193
|
const descriptors = Object.getOwnPropertyDescriptors(options);
|
|
1190
1194
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1191
|
-
if (
|
|
1195
|
+
if (is.symbol(key)
|
|
1192
1196
|
|| (key !== 'startLanguageModel'
|
|
1193
1197
|
&& key !== 'startMuted'
|
|
1194
1198
|
&& key !== 'startTranscription'
|
|
@@ -1214,13 +1218,13 @@ function immutableStartupOptions(options) {
|
|
|
1214
1218
|
const signal = Object.hasOwn(descriptors, 'signal')
|
|
1215
1219
|
? descriptors.signal.value
|
|
1216
1220
|
: null;
|
|
1217
|
-
if (
|
|
1221
|
+
if (!is.boolean(startLanguageModel)) {
|
|
1218
1222
|
fail('AI startup startLanguageModel must be a boolean.');
|
|
1219
1223
|
}
|
|
1220
|
-
if (
|
|
1224
|
+
if (!is.boolean(startMuted)) {
|
|
1221
1225
|
fail('AI startup startMuted must be a boolean.');
|
|
1222
1226
|
}
|
|
1223
|
-
if (
|
|
1227
|
+
if (!is.boolean(startTranscription)) {
|
|
1224
1228
|
fail('AI startup startTranscription must be a boolean.');
|
|
1225
1229
|
}
|
|
1226
1230
|
assertAbortSignal(signal);
|
|
@@ -1236,7 +1240,7 @@ function immutableInspectionOptions(options) {
|
|
|
1236
1240
|
assertPlainObject(options, 'AI provider inspection options');
|
|
1237
1241
|
const descriptors = Object.getOwnPropertyDescriptors(options);
|
|
1238
1242
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1239
|
-
if (
|
|
1243
|
+
if (is.symbol(key)
|
|
1240
1244
|
|| (key !== 'localOnly' && key !== 'signal')) {
|
|
1241
1245
|
fail('AI provider inspection options contain an unknown option.');
|
|
1242
1246
|
}
|
|
@@ -1250,7 +1254,7 @@ function immutableInspectionOptions(options) {
|
|
|
1250
1254
|
const signal = Object.hasOwn(descriptors, 'signal')
|
|
1251
1255
|
? descriptors.signal.value
|
|
1252
1256
|
: null;
|
|
1253
|
-
if (
|
|
1257
|
+
if (!is.boolean(localOnly)) {
|
|
1254
1258
|
fail('AI provider inspection localOnly must be a boolean.');
|
|
1255
1259
|
}
|
|
1256
1260
|
assertAbortSignal(signal);
|
|
@@ -1258,7 +1262,7 @@ function immutableInspectionOptions(options) {
|
|
|
1258
1262
|
}
|
|
1259
1263
|
|
|
1260
1264
|
function nullableTupleIdentifier(value) {
|
|
1261
|
-
return
|
|
1265
|
+
return is.string(value) && value.trim()
|
|
1262
1266
|
? value
|
|
1263
1267
|
: null;
|
|
1264
1268
|
}
|
|
@@ -1271,7 +1275,7 @@ function completeProgress(value) {
|
|
|
1271
1275
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1272
1276
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1273
1277
|
const descriptor = descriptors[key];
|
|
1274
|
-
if (
|
|
1278
|
+
if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
|
|
1275
1279
|
fail('AI provider progress must contain string-keyed data properties only.');
|
|
1276
1280
|
}
|
|
1277
1281
|
result[key] = descriptor.value;
|
|
@@ -1280,7 +1284,7 @@ function completeProgress(value) {
|
|
|
1280
1284
|
}
|
|
1281
1285
|
|
|
1282
1286
|
function stateError(error, fallbackCode) {
|
|
1283
|
-
const code =
|
|
1287
|
+
const code = is.string(error?.code)
|
|
1284
1288
|
&& /^[A-Z][A-Z0-9_]*$/.test(error.code)
|
|
1285
1289
|
? error.code
|
|
1286
1290
|
: fallbackCode;
|
|
@@ -1292,7 +1296,7 @@ function stateError(error, fallbackCode) {
|
|
|
1292
1296
|
: code.includes('UNAVAILABLE') || code.includes('NOT_REGISTERED')
|
|
1293
1297
|
? 'The selected AI provider is unavailable.'
|
|
1294
1298
|
: 'The selected AI provider operation failed.';
|
|
1295
|
-
const message =
|
|
1299
|
+
const message = is.string(error?.message) && error.message
|
|
1296
1300
|
? error.message
|
|
1297
1301
|
: fallbackMessage;
|
|
1298
1302
|
return completeValue(
|
|
@@ -1334,13 +1338,13 @@ function validateProvider(provider) {
|
|
|
1334
1338
|
}
|
|
1335
1339
|
assertRole(provider.role);
|
|
1336
1340
|
assertIdentifier(provider.id, 'AI provider.id');
|
|
1337
|
-
if (
|
|
1341
|
+
if (!is.boolean(provider.localOnly)) {
|
|
1338
1342
|
fail('AI provider.localOnly must be a boolean.');
|
|
1339
1343
|
}
|
|
1340
1344
|
const maxConcurrentRequests = provider.maxConcurrentRequests === undefined
|
|
1341
1345
|
? 1
|
|
1342
1346
|
: provider.maxConcurrentRequests;
|
|
1343
|
-
if (!
|
|
1347
|
+
if (!is.safeInteger(maxConcurrentRequests)
|
|
1344
1348
|
|| maxConcurrentRequests < 1) {
|
|
1345
1349
|
fail('AI provider.maxConcurrentRequests must be a positive safe integer.');
|
|
1346
1350
|
}
|
|
@@ -1348,7 +1352,7 @@ function validateProvider(provider) {
|
|
|
1348
1352
|
fail('Only TTS providers may process more than one concurrent request.');
|
|
1349
1353
|
}
|
|
1350
1354
|
for (const method of PROVIDER_METHODS) {
|
|
1351
|
-
if (
|
|
1355
|
+
if (!is.function(provider[method])) {
|
|
1352
1356
|
fail(`AI provider.${method} must be a function.`);
|
|
1353
1357
|
}
|
|
1354
1358
|
}
|
|
@@ -1615,9 +1619,9 @@ function immutableSpeechProviderRoleReplacement(role, value) {
|
|
|
1615
1619
|
|
|
1616
1620
|
function validateProviderStatus(status) {
|
|
1617
1621
|
assertPlainObject(status, 'AI provider status');
|
|
1618
|
-
if (
|
|
1619
|
-
||
|
|
1620
|
-
||
|
|
1622
|
+
if (!is.string(status.state)
|
|
1623
|
+
|| !is.boolean(status.loaded)
|
|
1624
|
+
|| !is.boolean(status.busy)) {
|
|
1621
1625
|
fail('AI provider status must include state, loaded, and busy values.');
|
|
1622
1626
|
}
|
|
1623
1627
|
return status;
|
|
@@ -1625,14 +1629,14 @@ function validateProviderStatus(status) {
|
|
|
1625
1629
|
|
|
1626
1630
|
function validateInspection(inspection, selection) {
|
|
1627
1631
|
assertPlainObject(inspection, 'AI provider inspection');
|
|
1628
|
-
if (
|
|
1632
|
+
if (!is.boolean(inspection.available)) {
|
|
1629
1633
|
fail('AI provider inspection.available must be a boolean.');
|
|
1630
1634
|
}
|
|
1631
1635
|
if (!inspection.available) {
|
|
1632
|
-
const code =
|
|
1636
|
+
const code = is.string(inspection.code) && inspection.code.length > 0
|
|
1633
1637
|
? inspection.code
|
|
1634
1638
|
: 'ARCANE_AI_PROVIDER_AUTHORITY_BLOCKED';
|
|
1635
|
-
const message =
|
|
1639
|
+
const message = is.string(inspection.message) && inspection.message.length > 0
|
|
1636
1640
|
? inspection.message
|
|
1637
1641
|
: 'The selected AI provider is unavailable.';
|
|
1638
1642
|
throw operationError(message, code);
|
|
@@ -1922,7 +1926,7 @@ export class AIProviderRuntime {
|
|
|
1922
1926
|
const localOnly = Object.hasOwn(options, 'localOnly')
|
|
1923
1927
|
? options.localOnly
|
|
1924
1928
|
: false;
|
|
1925
|
-
if (
|
|
1929
|
+
if (!is.boolean(localOnly)) {
|
|
1926
1930
|
fail('AI provider route localOnly must be a boolean.');
|
|
1927
1931
|
}
|
|
1928
1932
|
const slot = this.#slots[role];
|
|
@@ -2437,7 +2441,7 @@ export class AIProviderRuntime {
|
|
|
2437
2441
|
|
|
2438
2442
|
configureFromTuple(tuple) {
|
|
2439
2443
|
arcaneLogging.debug('[Arcane speech runtime] configureFromTuple', tuple);
|
|
2440
|
-
if (!
|
|
2444
|
+
if (!is.array(tuple) || tuple.length !== 6) {
|
|
2441
2445
|
fail('AI preference tuple must contain exactly six entries.');
|
|
2442
2446
|
}
|
|
2443
2447
|
|
|
@@ -2543,7 +2547,7 @@ export class AIProviderRuntime {
|
|
|
2543
2547
|
if (SPEECH_ROLES.includes(role)) {
|
|
2544
2548
|
arcaneLogging.debug('[Arcane speech runtime] provider.catalog.result', role, provider.id, providerCatalog);
|
|
2545
2549
|
}
|
|
2546
|
-
if (!
|
|
2550
|
+
if (!is.array(providerCatalog)) {
|
|
2547
2551
|
fail('AI provider.catalog() must synchronously return an array.');
|
|
2548
2552
|
}
|
|
2549
2553
|
entries.push(
|
|
@@ -2727,7 +2731,7 @@ export class AIProviderRuntime {
|
|
|
2727
2731
|
? options.localOnly
|
|
2728
2732
|
: false;
|
|
2729
2733
|
assertAbortSignal(signal);
|
|
2730
|
-
if (
|
|
2734
|
+
if (!is.boolean(localOnly)) {
|
|
2731
2735
|
fail('AI provider load localOnly must be a boolean.');
|
|
2732
2736
|
}
|
|
2733
2737
|
const slot = this.#slots[role];
|
|
@@ -3343,14 +3347,14 @@ export class AIProviderRuntime {
|
|
|
3343
3347
|
return this.#disposeAllPromise;
|
|
3344
3348
|
}
|
|
3345
3349
|
|
|
3346
|
-
request(role, options = {}) {
|
|
3350
|
+
request(role, options = {}, {speechInputPrepared = false} = {}) {
|
|
3347
3351
|
if (SPEECH_ROLES.includes(role)) {
|
|
3348
3352
|
arcaneLogging.debug('[Arcane speech runtime] request', role, options);
|
|
3349
3353
|
}
|
|
3350
|
-
return this.#requestRole(role, options, false);
|
|
3354
|
+
return this.#requestRole(role, options, false, speechInputPrepared);
|
|
3351
3355
|
}
|
|
3352
3356
|
|
|
3353
|
-
#requestRole(role, options, queued) {
|
|
3357
|
+
#requestRole(role, options, queued, speechInputPrepared = false) {
|
|
3354
3358
|
this.#assertOpen();
|
|
3355
3359
|
this.#assertNotConfiguring();
|
|
3356
3360
|
assertRole(role);
|
|
@@ -3379,6 +3383,24 @@ export class AIProviderRuntime {
|
|
|
3379
3383
|
if (options.signal?.aborted) {
|
|
3380
3384
|
return Promise.reject(normalizedAbort());
|
|
3381
3385
|
}
|
|
3386
|
+
if (role === 'tts' && !queued && speechInputPrepared !== true) {
|
|
3387
|
+
const payload = options.payload;
|
|
3388
|
+
const field = Object.hasOwn(payload ?? {}, 'input') ? 'input' : 'text';
|
|
3389
|
+
const text = Object.getOwnPropertyDescriptor(payload ?? {}, field)?.value;
|
|
3390
|
+
if (is.string(text)) {
|
|
3391
|
+
const input = stripSpeechFormatting(text);
|
|
3392
|
+
if (input !== text) {
|
|
3393
|
+
const payloadProperties = Object.getOwnPropertyDescriptors(payload);
|
|
3394
|
+
payloadProperties[field] = {...payloadProperties[field], value: input};
|
|
3395
|
+
const requestProperties = Object.getOwnPropertyDescriptors(options);
|
|
3396
|
+
requestProperties.payload = {
|
|
3397
|
+
...requestProperties.payload,
|
|
3398
|
+
value: Object.create(Object.getPrototypeOf(payload), payloadProperties)
|
|
3399
|
+
};
|
|
3400
|
+
options = Object.create(Object.getPrototypeOf(options), requestProperties);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3382
3404
|
const slot = this.#slots[role];
|
|
3383
3405
|
if (slot.disposed) {
|
|
3384
3406
|
return Promise.reject(
|
|
@@ -3640,14 +3662,14 @@ export class AIProviderRuntime {
|
|
|
3640
3662
|
|
|
3641
3663
|
function beginAIProviderStreamHandleCleanup(opened, activeIterator, reason) {
|
|
3642
3664
|
const cleanup = [];
|
|
3643
|
-
if (
|
|
3665
|
+
if (is.function(opened?.cancel)) {
|
|
3644
3666
|
cleanup.push(
|
|
3645
3667
|
Promise.resolve().then(function cancelOpenedAIStream() {
|
|
3646
3668
|
return opened.cancel(reason);
|
|
3647
3669
|
})
|
|
3648
3670
|
);
|
|
3649
3671
|
}
|
|
3650
|
-
if (activeIterator &&
|
|
3672
|
+
if (activeIterator && is.function(activeIterator.return)) {
|
|
3651
3673
|
cleanup.push(
|
|
3652
3674
|
Promise.resolve().then(function returnOpenedAIStream() {
|
|
3653
3675
|
return activeIterator.return();
|
|
@@ -3680,7 +3702,7 @@ export class AIProviderRuntime {
|
|
|
3680
3702
|
}
|
|
3681
3703
|
const lateCleanup = providerOpenPromise.then(
|
|
3682
3704
|
async function cleanupLateAIProviderStream(lateHandle) {
|
|
3683
|
-
if (!lateHandle ||
|
|
3705
|
+
if (!lateHandle || !is.function(lateHandle.cancel)) {
|
|
3684
3706
|
throw operationError(
|
|
3685
3707
|
'The late AI provider stream did not expose cancellable ownership.',
|
|
3686
3708
|
'ARCANE_AI_STREAM_CLEANUP_INCOMPLETE'
|
|
@@ -3796,10 +3818,10 @@ export class AIProviderRuntime {
|
|
|
3796
3818
|
});
|
|
3797
3819
|
opened = await Promise.race([providerOpenPromise, abortedOpen]);
|
|
3798
3820
|
if (!opened
|
|
3799
|
-
||
|
|
3800
|
-
||
|
|
3821
|
+
|| !is.function(opened[Symbol.asyncIterator])
|
|
3822
|
+
|| !is.function(opened.cancel)
|
|
3801
3823
|
|| !opened.result
|
|
3802
|
-
||
|
|
3824
|
+
|| !is.function(opened.result.then)) {
|
|
3803
3825
|
throw operationError(
|
|
3804
3826
|
'AI stream providers must return an async iterable with result and cancel().',
|
|
3805
3827
|
'ARCANE_AI_PROVIDER_STREAM_INVALID'
|
|
@@ -3807,7 +3829,7 @@ export class AIProviderRuntime {
|
|
|
3807
3829
|
}
|
|
3808
3830
|
providerHandle = opened;
|
|
3809
3831
|
iterator = opened[Symbol.asyncIterator]();
|
|
3810
|
-
if (!iterator ||
|
|
3832
|
+
if (!iterator || !is.function(iterator.next)) {
|
|
3811
3833
|
throw operationError(
|
|
3812
3834
|
'The AI provider stream iterator has no next() method.',
|
|
3813
3835
|
'ARCANE_AI_PROVIDER_STREAM_INVALID'
|
|
@@ -4001,7 +4023,9 @@ export class AIProviderRuntime {
|
|
|
4001
4023
|
if (SPEECH_ROLES.includes(role)) {
|
|
4002
4024
|
arcaneLogging.debug('[Arcane speech runtime] provider.request', {generation, requestSequence, operationId}, providerRequest);
|
|
4003
4025
|
}
|
|
4004
|
-
const result =
|
|
4026
|
+
const result = role === 'tts'
|
|
4027
|
+
? await provider.request(providerRequest, {speechInputPrepared: true})
|
|
4028
|
+
: await provider.request(providerRequest);
|
|
4005
4029
|
if (SPEECH_ROLES.includes(role)) {
|
|
4006
4030
|
arcaneLogging.debug('[Arcane speech runtime] provider.request.result', {role, generation, requestSequence, operationId}, result);
|
|
4007
4031
|
}
|
|
@@ -4065,9 +4089,9 @@ export class AIProviderRuntime {
|
|
|
4065
4089
|
return this.#roleRequestAlias('stt', 'transcribe', payload, options);
|
|
4066
4090
|
}
|
|
4067
4091
|
|
|
4068
|
-
synthesize(payload, options = {}) {
|
|
4092
|
+
synthesize(payload, options = {}, preparation = {}) {
|
|
4069
4093
|
arcaneLogging.debug('[Arcane speech runtime] synthesize', payload, options);
|
|
4070
|
-
return this.#roleRequestAlias('tts', 'synthesize', payload, options);
|
|
4094
|
+
return this.#roleRequestAlias('tts', 'synthesize', payload, options, preparation);
|
|
4071
4095
|
}
|
|
4072
4096
|
|
|
4073
4097
|
cancel(role) {
|
|
@@ -4100,7 +4124,7 @@ export class AIProviderRuntime {
|
|
|
4100
4124
|
arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted', muted);
|
|
4101
4125
|
this.#assertOpen();
|
|
4102
4126
|
this.#assertNotConfiguring();
|
|
4103
|
-
if (
|
|
4127
|
+
if (!is.boolean(muted)) {
|
|
4104
4128
|
fail('AI speech muted state must be a boolean.');
|
|
4105
4129
|
}
|
|
4106
4130
|
this.#speechDesiredMuted = muted;
|
|
@@ -4159,7 +4183,7 @@ export class AIProviderRuntime {
|
|
|
4159
4183
|
) ?? null;
|
|
4160
4184
|
}
|
|
4161
4185
|
|
|
4162
|
-
#roleRequestAlias(role, operation, payload, options) {
|
|
4186
|
+
#roleRequestAlias(role, operation, payload, options, preparation = {}) {
|
|
4163
4187
|
assertPlainObject(options, `AI ${operation} options`);
|
|
4164
4188
|
for (const key of Reflect.ownKeys(options)) {
|
|
4165
4189
|
if (key !== 'localOnly' && key !== 'signal') {
|
|
@@ -4179,7 +4203,8 @@ export class AIProviderRuntime {
|
|
|
4179
4203
|
payload,
|
|
4180
4204
|
localOnly,
|
|
4181
4205
|
signal
|
|
4182
|
-
}
|
|
4206
|
+
},
|
|
4207
|
+
preparation
|
|
4183
4208
|
);
|
|
4184
4209
|
}
|
|
4185
4210
|
|