arcane-os 0.5.17 → 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 +24 -0
- package/README.md +9 -9
- 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 +46 -43
- 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 +5 -1
- package/docs/reference/ai/browser-speech.md +198 -5
- package/docs/reference/availability-and-normalization.md +40 -0
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +20 -24
- package/docs/reference/protocols.md +1 -1
- package/docs/reference/runtime-modules.md +125 -35
- package/docs/reference/sdk-api.md +23 -7
- 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 +3 -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 +117 -138
- 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 +491 -165
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
- 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/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 +29 -26
- 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 +35 -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,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {arcaneLogging} from 'arcane-os/logging';
|
|
2
5
|
import {stripSpeechFormatting} from 'arcane-os/speech-text';
|
|
3
6
|
import {
|
|
@@ -88,7 +91,7 @@ async function awaitStreamCleanup(operation) {
|
|
|
88
91
|
|
|
89
92
|
function assertStreamCleanupComplete(outcome) {
|
|
90
93
|
const rejected = outcome.completed
|
|
91
|
-
&&
|
|
94
|
+
&& is.array(outcome.results)
|
|
92
95
|
&& outcome.results.some(function hasRejectedAIStreamCleanup(result) {
|
|
93
96
|
return result.status === 'rejected';
|
|
94
97
|
});
|
|
@@ -107,7 +110,7 @@ function assertRole(role) {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
function assertIdentifier(value, label) {
|
|
110
|
-
if (
|
|
113
|
+
if (!is.string(value)
|
|
111
114
|
|| value.length < 1
|
|
112
115
|
|| value.trim() !== value) {
|
|
113
116
|
fail(`${label} must be a nonempty trimmed string.`);
|
|
@@ -115,7 +118,7 @@ function assertIdentifier(value, label) {
|
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
function nextSequence(value) {
|
|
118
|
-
if (
|
|
121
|
+
if (is.bigint(value)) {
|
|
119
122
|
return value + 1n;
|
|
120
123
|
}
|
|
121
124
|
if (value === Number.MAX_SAFE_INTEGER) {
|
|
@@ -129,16 +132,16 @@ function assertAbortSignal(signal) {
|
|
|
129
132
|
return;
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
if (
|
|
133
|
-
||
|
|
134
|
-
||
|
|
135
|
-
||
|
|
135
|
+
if (!is.object(signal)
|
|
136
|
+
|| !is.boolean(signal.aborted)
|
|
137
|
+
|| !is.function(signal.addEventListener)
|
|
138
|
+
|| !is.function(signal.removeEventListener)) {
|
|
136
139
|
fail('AI provider operation signal must be an AbortSignal.');
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
function assertPlainObject(value, label) {
|
|
141
|
-
if (!value ||
|
|
144
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
142
145
|
fail(`${label} must be a plain object.`);
|
|
143
146
|
}
|
|
144
147
|
|
|
@@ -149,7 +152,7 @@ function assertPlainObject(value, label) {
|
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
function isPlainRecord(value) {
|
|
152
|
-
if (!value ||
|
|
155
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
153
156
|
return false;
|
|
154
157
|
}
|
|
155
158
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -160,7 +163,7 @@ function validateLLMToolDeclarations(value) {
|
|
|
160
163
|
if (value === undefined) {
|
|
161
164
|
return;
|
|
162
165
|
}
|
|
163
|
-
if (!
|
|
166
|
+
if (!is.array(value)) {
|
|
164
167
|
fail(
|
|
165
168
|
'AI LLM tools must be an array.',
|
|
166
169
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -178,9 +181,9 @@ function validateLLMToolDeclarations(value) {
|
|
|
178
181
|
|| !isPlainRecord(parameters.properties)
|
|
179
182
|
|| !isPlainRecord(messageSchema)
|
|
180
183
|
|| messageSchema.type !== 'string'
|
|
181
|
-
|| !
|
|
184
|
+
|| !is.integer(messageSchema.minLength)
|
|
182
185
|
|| messageSchema.minLength < 1
|
|
183
|
-
|| !
|
|
186
|
+
|| !is.array(parameters.required)
|
|
184
187
|
|| !parameters.required.includes('message')) {
|
|
185
188
|
fail(
|
|
186
189
|
`AI LLM tools[${index}] must require a nonempty string parameters.properties.message.`,
|
|
@@ -192,13 +195,13 @@ function validateLLMToolDeclarations(value) {
|
|
|
192
195
|
|
|
193
196
|
function validateLLMToolCall(call, label) {
|
|
194
197
|
if (!isPlainRecord(call)
|
|
195
|
-
||
|
|
198
|
+
|| !is.string(call.id)
|
|
196
199
|
|| !call.id.trim()
|
|
197
200
|
|| call.type !== 'function'
|
|
198
201
|
|| !isPlainRecord(call.function)
|
|
199
|
-
||
|
|
202
|
+
|| !is.string(call.function.name)
|
|
200
203
|
|| !call.function.name.trim()
|
|
201
|
-
||
|
|
204
|
+
|| !is.string(call.function.arguments)) {
|
|
202
205
|
fail(
|
|
203
206
|
`${label} must be one complete structural function call.`,
|
|
204
207
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -220,7 +223,7 @@ function validateLLMToolCall(call, label) {
|
|
|
220
223
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
221
224
|
);
|
|
222
225
|
}
|
|
223
|
-
if (
|
|
226
|
+
if (!is.string(argumentsRecord.message)
|
|
224
227
|
|| !argumentsRecord.message.trim()) {
|
|
225
228
|
fail(
|
|
226
229
|
`${label} arguments must include a nonempty user-facing message.`,
|
|
@@ -252,7 +255,7 @@ function validateLLMMessageToolCalls(message, label) {
|
|
|
252
255
|
}
|
|
253
256
|
const descriptor = Object.getOwnPropertyDescriptor(message, 'tool_calls');
|
|
254
257
|
if (!descriptor || !Object.hasOwn(descriptor, 'value')
|
|
255
|
-
|| !
|
|
258
|
+
|| !is.array(descriptor.value)) {
|
|
256
259
|
fail(
|
|
257
260
|
`${label}.tool_calls must be an array data property.`,
|
|
258
261
|
'ARCANE_AI_TOOL_CALL_INVALID'
|
|
@@ -277,7 +280,7 @@ function validateLLMMessageToolCalls(message, label) {
|
|
|
277
280
|
|
|
278
281
|
function validateLLMRequestPayload(payload) {
|
|
279
282
|
assertPlainObject(payload, 'AI LLM request payload');
|
|
280
|
-
if (!
|
|
283
|
+
if (!is.array(payload.messages)) {
|
|
281
284
|
fail('AI LLM request payload.messages must be an array.');
|
|
282
285
|
}
|
|
283
286
|
const pendingToolCallIds = new Set();
|
|
@@ -317,7 +320,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
317
320
|
openedToolCall = true;
|
|
318
321
|
}
|
|
319
322
|
if (message.role === 'tool') {
|
|
320
|
-
if (
|
|
323
|
+
if (!is.string(message.content)
|
|
321
324
|
|| !message.content.trim()) {
|
|
322
325
|
fail(
|
|
323
326
|
`AI LLM request payload.messages[${index}] must contain a nonblank user-facing tool result.`,
|
|
@@ -325,7 +328,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
325
328
|
);
|
|
326
329
|
}
|
|
327
330
|
if (!pendingToolCallIds.size
|
|
328
|
-
||
|
|
331
|
+
|| !is.string(message.tool_call_id)
|
|
329
332
|
|| !pendingToolCallIds.has(message.tool_call_id)) {
|
|
330
333
|
fail(
|
|
331
334
|
`AI LLM request payload.messages[${index}] does not settle the pending structural tool call.`,
|
|
@@ -360,7 +363,7 @@ function validateLLMRequestPayload(payload) {
|
|
|
360
363
|
payload.parallel_tool_calls
|
|
361
364
|
];
|
|
362
365
|
if (parallelValues.some(function invalidParallelLLMToolPreference(value) {
|
|
363
|
-
return value !== undefined &&
|
|
366
|
+
return value !== undefined && !is.boolean(value);
|
|
364
367
|
})) {
|
|
365
368
|
fail('AI LLM parallel tool-call preferences must be boolean when provided.');
|
|
366
369
|
}
|
|
@@ -377,7 +380,7 @@ function validateLLMTerminalMessage(message, label) {
|
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
function validateLLMTerminalResult(value, label) {
|
|
380
|
-
if (
|
|
383
|
+
if (is.string(value)) {
|
|
381
384
|
return value;
|
|
382
385
|
}
|
|
383
386
|
if (!isPlainRecord(value)) {
|
|
@@ -407,7 +410,7 @@ function validateLLMTerminalResult(value, label) {
|
|
|
407
410
|
}
|
|
408
411
|
const choicesDescriptor = Object.getOwnPropertyDescriptor(value, 'choices');
|
|
409
412
|
if (!choicesDescriptor || !Object.hasOwn(choicesDescriptor, 'value')
|
|
410
|
-
|| !
|
|
413
|
+
|| !is.array(choicesDescriptor.value)
|
|
411
414
|
|| choicesDescriptor.value.length === 0) {
|
|
412
415
|
fail(
|
|
413
416
|
`${label}.choices must be a nonempty array data property.`,
|
|
@@ -418,7 +421,7 @@ function validateLLMTerminalResult(value, label) {
|
|
|
418
421
|
for (let position = 0; position < choicesDescriptor.value.length; position += 1) {
|
|
419
422
|
const choice = choicesDescriptor.value[position];
|
|
420
423
|
if (!isPlainRecord(choice)
|
|
421
|
-
|| !
|
|
424
|
+
|| !is.safeInteger(choice.index)
|
|
422
425
|
|| choice.index < 0
|
|
423
426
|
|| indexes.has(choice.index)) {
|
|
424
427
|
fail(
|
|
@@ -462,22 +465,22 @@ function canonicalLLMToolCall(call) {
|
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
function copyLLMDataValue(value, seen = new Map()) {
|
|
465
|
-
if (!value ||
|
|
468
|
+
if (!value || !is.object(value)) {
|
|
466
469
|
return value;
|
|
467
470
|
}
|
|
468
471
|
if (seen.has(value)) {
|
|
469
472
|
return seen.get(value);
|
|
470
473
|
}
|
|
471
|
-
if (!
|
|
474
|
+
if (!is.array(value) && !isPlainRecord(value)) {
|
|
472
475
|
return value;
|
|
473
476
|
}
|
|
474
|
-
const result =
|
|
477
|
+
const result = is.array(value)
|
|
475
478
|
? new Array(value.length)
|
|
476
479
|
: Object.create(Object.getPrototypeOf(value));
|
|
477
480
|
seen.set(value, result);
|
|
478
481
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
479
482
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
480
|
-
if (
|
|
483
|
+
if (is.array(value) && key === 'length') {
|
|
481
484
|
continue;
|
|
482
485
|
}
|
|
483
486
|
const descriptor = descriptors[key];
|
|
@@ -494,12 +497,12 @@ function sameLLMDataValue(left, right, seen = new Map()) {
|
|
|
494
497
|
return true;
|
|
495
498
|
}
|
|
496
499
|
if (!left || !right
|
|
497
|
-
||
|
|
498
|
-
||
|
|
499
|
-
||
|
|
500
|
+
|| !is.object(left)
|
|
501
|
+
|| !is.object(right)
|
|
502
|
+
|| is.array(left) !== is.array(right)) {
|
|
500
503
|
return false;
|
|
501
504
|
}
|
|
502
|
-
if (!
|
|
505
|
+
if (!is.array(left)
|
|
503
506
|
&& (!isPlainRecord(left) || !isPlainRecord(right))) {
|
|
504
507
|
return false;
|
|
505
508
|
}
|
|
@@ -562,11 +565,11 @@ function terminalLLMToolCallRecord(choiceIndex, completeCalls) {
|
|
|
562
565
|
}
|
|
563
566
|
|
|
564
567
|
function terminalLLMToolCalls(value) {
|
|
565
|
-
if (
|
|
568
|
+
if (is.string(value)) {
|
|
566
569
|
return {direct: terminalLLMToolCallRecord(null, []), choices: []};
|
|
567
570
|
}
|
|
568
571
|
if (Object.hasOwn(value, 'message')) {
|
|
569
|
-
const completeCalls =
|
|
572
|
+
const completeCalls = is.array(value.message?.tool_calls)
|
|
570
573
|
? value.message.tool_calls
|
|
571
574
|
: [];
|
|
572
575
|
return {
|
|
@@ -577,7 +580,7 @@ function terminalLLMToolCalls(value) {
|
|
|
577
580
|
return {
|
|
578
581
|
direct: null,
|
|
579
582
|
choices: value.choices.map(function retainTerminalLLMChoice(choice) {
|
|
580
|
-
const completeCalls =
|
|
583
|
+
const completeCalls = is.array(choice.message?.tool_calls)
|
|
581
584
|
? choice.message.tool_calls
|
|
582
585
|
: [];
|
|
583
586
|
return terminalLLMToolCallRecord(choice.index, completeCalls);
|
|
@@ -637,7 +640,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
637
640
|
}
|
|
638
641
|
const descriptor = Object.getOwnPropertyDescriptor(source, 'tool_calls');
|
|
639
642
|
if (!descriptor || !Object.hasOwn(descriptor, 'value')
|
|
640
|
-
|| !
|
|
643
|
+
|| !is.array(descriptor.value)) {
|
|
641
644
|
throw llmStreamToolCallMismatch(
|
|
642
645
|
`${label}.tool_calls must be an array data property.`
|
|
643
646
|
);
|
|
@@ -686,7 +689,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
686
689
|
);
|
|
687
690
|
}
|
|
688
691
|
if (fragment.index !== undefined
|
|
689
|
-
&& (!
|
|
692
|
+
&& (!is.safeInteger(fragment.index) || fragment.index < 0)) {
|
|
690
693
|
throw llmStreamToolCallMismatch(
|
|
691
694
|
`${label}.tool_calls[${position}] has an invalid structural call index.`
|
|
692
695
|
);
|
|
@@ -701,7 +704,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
701
704
|
invalid: false
|
|
702
705
|
};
|
|
703
706
|
if (fragment.id !== undefined) {
|
|
704
|
-
if (
|
|
707
|
+
if (!is.string(fragment.id)
|
|
705
708
|
|| !fragment.id
|
|
706
709
|
|| (retained.id && retained.id !== fragment.id)) {
|
|
707
710
|
retained.invalid = true;
|
|
@@ -710,7 +713,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
710
713
|
}
|
|
711
714
|
}
|
|
712
715
|
if (fragment.type !== undefined) {
|
|
713
|
-
if (
|
|
716
|
+
if (!is.string(fragment.type)
|
|
714
717
|
|| !fragment.type
|
|
715
718
|
|| (retained.type && retained.type !== fragment.type)) {
|
|
716
719
|
retained.invalid = true;
|
|
@@ -726,14 +729,14 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
726
729
|
? fragment.function
|
|
727
730
|
: {};
|
|
728
731
|
if (functionFragment.name !== undefined) {
|
|
729
|
-
if (
|
|
732
|
+
if (!is.string(functionFragment.name)) {
|
|
730
733
|
retained.invalid = true;
|
|
731
734
|
} else {
|
|
732
735
|
retained.name += functionFragment.name;
|
|
733
736
|
}
|
|
734
737
|
}
|
|
735
738
|
if (functionFragment.arguments !== undefined) {
|
|
736
|
-
if (
|
|
739
|
+
if (!is.string(functionFragment.arguments)) {
|
|
737
740
|
retained.invalid = true;
|
|
738
741
|
} else {
|
|
739
742
|
retained.arguments += functionFragment.arguments;
|
|
@@ -762,7 +765,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
762
765
|
}
|
|
763
766
|
);
|
|
764
767
|
if (choice.index !== undefined
|
|
765
|
-
&& (!
|
|
768
|
+
&& (!is.safeInteger(choice.index) || choice.index < 0)) {
|
|
766
769
|
throw llmStreamToolCallMismatch(
|
|
767
770
|
`AI provider stream choice ${position} has an invalid index.`
|
|
768
771
|
);
|
|
@@ -811,7 +814,7 @@ function createLLMStreamToolCallCorrelation() {
|
|
|
811
814
|
'AI provider stream chunk.message'
|
|
812
815
|
);
|
|
813
816
|
}
|
|
814
|
-
if (
|
|
817
|
+
if (is.array(chunk.choices)) {
|
|
815
818
|
for (let position = 0; position < chunk.choices.length; position += 1) {
|
|
816
819
|
observeChoice(chunk.choices[position], position);
|
|
817
820
|
}
|
|
@@ -1009,13 +1012,13 @@ function projectLLMStreamData(value, seen = new Map()) {
|
|
|
1009
1012
|
if (value === null || value === undefined) {
|
|
1010
1013
|
return value;
|
|
1011
1014
|
}
|
|
1012
|
-
if (
|
|
1015
|
+
if (!is.object(value)) {
|
|
1013
1016
|
return value;
|
|
1014
1017
|
}
|
|
1015
1018
|
if (seen.has(value)) {
|
|
1016
1019
|
return seen.get(value);
|
|
1017
1020
|
}
|
|
1018
|
-
if (
|
|
1021
|
+
if (is.array(value)) {
|
|
1019
1022
|
const result = [];
|
|
1020
1023
|
seen.set(value, result);
|
|
1021
1024
|
for (const item of value) {
|
|
@@ -1031,7 +1034,7 @@ function projectLLMStreamData(value, seen = new Map()) {
|
|
|
1031
1034
|
let sourceDataFields = 0;
|
|
1032
1035
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1033
1036
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1034
|
-
if (
|
|
1037
|
+
if (is.symbol(key)) {
|
|
1035
1038
|
continue;
|
|
1036
1039
|
}
|
|
1037
1040
|
const descriptor = descriptors[key];
|
|
@@ -1055,19 +1058,19 @@ function projectLLMStreamChunk(value) {
|
|
|
1055
1058
|
}
|
|
1056
1059
|
|
|
1057
1060
|
function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
|
|
1058
|
-
if (
|
|
1061
|
+
if (is.function(value)) {
|
|
1059
1062
|
fail(
|
|
1060
1063
|
'AI providers receive data-only request payloads.',
|
|
1061
1064
|
'ARCANE_AI_PROVIDER_CALLBACK_BOUNDARY'
|
|
1062
1065
|
);
|
|
1063
1066
|
}
|
|
1064
|
-
if (!value ||
|
|
1067
|
+
if (!value || !is.object(value)) {
|
|
1065
1068
|
return;
|
|
1066
1069
|
}
|
|
1067
|
-
if (value instanceof ArrayBuffer ||
|
|
1070
|
+
if (value instanceof ArrayBuffer || is.arrayBufferView(value)) {
|
|
1068
1071
|
return;
|
|
1069
1072
|
}
|
|
1070
|
-
if (
|
|
1073
|
+
if (is.function(globalThis.Blob) && value instanceof Blob) {
|
|
1071
1074
|
return;
|
|
1072
1075
|
}
|
|
1073
1076
|
if (seen.has(value)) {
|
|
@@ -1077,7 +1080,7 @@ function assertCallbackFreeProviderValue(value, seen = new WeakSet()) {
|
|
|
1077
1080
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1078
1081
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1079
1082
|
const descriptor = descriptors[key];
|
|
1080
|
-
if (
|
|
1083
|
+
if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
|
|
1081
1084
|
fail('AI provider request payloads must contain data properties only.');
|
|
1082
1085
|
}
|
|
1083
1086
|
assertCallbackFreeProviderValue(descriptor.value, seen);
|
|
@@ -1090,7 +1093,7 @@ function assertClosedRecord(value, keys, label) {
|
|
|
1090
1093
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1091
1094
|
const actual = Reflect.ownKeys(value);
|
|
1092
1095
|
if (actual.some(function hasSymbolKey(key) {
|
|
1093
|
-
return
|
|
1096
|
+
return is.symbol(key);
|
|
1094
1097
|
})) {
|
|
1095
1098
|
fail(`${label} must not contain symbol keys.`);
|
|
1096
1099
|
}
|
|
@@ -1119,7 +1122,7 @@ function immutableSelection(role, value) {
|
|
|
1119
1122
|
);
|
|
1120
1123
|
assertIdentifier(value.providerId, `${role} provider selection.providerId`);
|
|
1121
1124
|
assertIdentifier(value.modelId, `${role} provider selection.modelId`);
|
|
1122
|
-
if (value.localOnly !== null &&
|
|
1125
|
+
if (value.localOnly !== null && !is.boolean(value.localOnly)) {
|
|
1123
1126
|
fail(`${role} provider selection.localOnly must be null or a boolean.`);
|
|
1124
1127
|
}
|
|
1125
1128
|
|
|
@@ -1189,7 +1192,7 @@ function immutableStartupOptions(options) {
|
|
|
1189
1192
|
assertPlainObject(options, 'AI provider startup options');
|
|
1190
1193
|
const descriptors = Object.getOwnPropertyDescriptors(options);
|
|
1191
1194
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1192
|
-
if (
|
|
1195
|
+
if (is.symbol(key)
|
|
1193
1196
|
|| (key !== 'startLanguageModel'
|
|
1194
1197
|
&& key !== 'startMuted'
|
|
1195
1198
|
&& key !== 'startTranscription'
|
|
@@ -1215,13 +1218,13 @@ function immutableStartupOptions(options) {
|
|
|
1215
1218
|
const signal = Object.hasOwn(descriptors, 'signal')
|
|
1216
1219
|
? descriptors.signal.value
|
|
1217
1220
|
: null;
|
|
1218
|
-
if (
|
|
1221
|
+
if (!is.boolean(startLanguageModel)) {
|
|
1219
1222
|
fail('AI startup startLanguageModel must be a boolean.');
|
|
1220
1223
|
}
|
|
1221
|
-
if (
|
|
1224
|
+
if (!is.boolean(startMuted)) {
|
|
1222
1225
|
fail('AI startup startMuted must be a boolean.');
|
|
1223
1226
|
}
|
|
1224
|
-
if (
|
|
1227
|
+
if (!is.boolean(startTranscription)) {
|
|
1225
1228
|
fail('AI startup startTranscription must be a boolean.');
|
|
1226
1229
|
}
|
|
1227
1230
|
assertAbortSignal(signal);
|
|
@@ -1237,7 +1240,7 @@ function immutableInspectionOptions(options) {
|
|
|
1237
1240
|
assertPlainObject(options, 'AI provider inspection options');
|
|
1238
1241
|
const descriptors = Object.getOwnPropertyDescriptors(options);
|
|
1239
1242
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1240
|
-
if (
|
|
1243
|
+
if (is.symbol(key)
|
|
1241
1244
|
|| (key !== 'localOnly' && key !== 'signal')) {
|
|
1242
1245
|
fail('AI provider inspection options contain an unknown option.');
|
|
1243
1246
|
}
|
|
@@ -1251,7 +1254,7 @@ function immutableInspectionOptions(options) {
|
|
|
1251
1254
|
const signal = Object.hasOwn(descriptors, 'signal')
|
|
1252
1255
|
? descriptors.signal.value
|
|
1253
1256
|
: null;
|
|
1254
|
-
if (
|
|
1257
|
+
if (!is.boolean(localOnly)) {
|
|
1255
1258
|
fail('AI provider inspection localOnly must be a boolean.');
|
|
1256
1259
|
}
|
|
1257
1260
|
assertAbortSignal(signal);
|
|
@@ -1259,7 +1262,7 @@ function immutableInspectionOptions(options) {
|
|
|
1259
1262
|
}
|
|
1260
1263
|
|
|
1261
1264
|
function nullableTupleIdentifier(value) {
|
|
1262
|
-
return
|
|
1265
|
+
return is.string(value) && value.trim()
|
|
1263
1266
|
? value
|
|
1264
1267
|
: null;
|
|
1265
1268
|
}
|
|
@@ -1272,7 +1275,7 @@ function completeProgress(value) {
|
|
|
1272
1275
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
1273
1276
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
1274
1277
|
const descriptor = descriptors[key];
|
|
1275
|
-
if (
|
|
1278
|
+
if (is.symbol(key) || !Object.hasOwn(descriptor, 'value')) {
|
|
1276
1279
|
fail('AI provider progress must contain string-keyed data properties only.');
|
|
1277
1280
|
}
|
|
1278
1281
|
result[key] = descriptor.value;
|
|
@@ -1281,7 +1284,7 @@ function completeProgress(value) {
|
|
|
1281
1284
|
}
|
|
1282
1285
|
|
|
1283
1286
|
function stateError(error, fallbackCode) {
|
|
1284
|
-
const code =
|
|
1287
|
+
const code = is.string(error?.code)
|
|
1285
1288
|
&& /^[A-Z][A-Z0-9_]*$/.test(error.code)
|
|
1286
1289
|
? error.code
|
|
1287
1290
|
: fallbackCode;
|
|
@@ -1293,7 +1296,7 @@ function stateError(error, fallbackCode) {
|
|
|
1293
1296
|
: code.includes('UNAVAILABLE') || code.includes('NOT_REGISTERED')
|
|
1294
1297
|
? 'The selected AI provider is unavailable.'
|
|
1295
1298
|
: 'The selected AI provider operation failed.';
|
|
1296
|
-
const message =
|
|
1299
|
+
const message = is.string(error?.message) && error.message
|
|
1297
1300
|
? error.message
|
|
1298
1301
|
: fallbackMessage;
|
|
1299
1302
|
return completeValue(
|
|
@@ -1335,13 +1338,13 @@ function validateProvider(provider) {
|
|
|
1335
1338
|
}
|
|
1336
1339
|
assertRole(provider.role);
|
|
1337
1340
|
assertIdentifier(provider.id, 'AI provider.id');
|
|
1338
|
-
if (
|
|
1341
|
+
if (!is.boolean(provider.localOnly)) {
|
|
1339
1342
|
fail('AI provider.localOnly must be a boolean.');
|
|
1340
1343
|
}
|
|
1341
1344
|
const maxConcurrentRequests = provider.maxConcurrentRequests === undefined
|
|
1342
1345
|
? 1
|
|
1343
1346
|
: provider.maxConcurrentRequests;
|
|
1344
|
-
if (!
|
|
1347
|
+
if (!is.safeInteger(maxConcurrentRequests)
|
|
1345
1348
|
|| maxConcurrentRequests < 1) {
|
|
1346
1349
|
fail('AI provider.maxConcurrentRequests must be a positive safe integer.');
|
|
1347
1350
|
}
|
|
@@ -1349,7 +1352,7 @@ function validateProvider(provider) {
|
|
|
1349
1352
|
fail('Only TTS providers may process more than one concurrent request.');
|
|
1350
1353
|
}
|
|
1351
1354
|
for (const method of PROVIDER_METHODS) {
|
|
1352
|
-
if (
|
|
1355
|
+
if (!is.function(provider[method])) {
|
|
1353
1356
|
fail(`AI provider.${method} must be a function.`);
|
|
1354
1357
|
}
|
|
1355
1358
|
}
|
|
@@ -1616,9 +1619,9 @@ function immutableSpeechProviderRoleReplacement(role, value) {
|
|
|
1616
1619
|
|
|
1617
1620
|
function validateProviderStatus(status) {
|
|
1618
1621
|
assertPlainObject(status, 'AI provider status');
|
|
1619
|
-
if (
|
|
1620
|
-
||
|
|
1621
|
-
||
|
|
1622
|
+
if (!is.string(status.state)
|
|
1623
|
+
|| !is.boolean(status.loaded)
|
|
1624
|
+
|| !is.boolean(status.busy)) {
|
|
1622
1625
|
fail('AI provider status must include state, loaded, and busy values.');
|
|
1623
1626
|
}
|
|
1624
1627
|
return status;
|
|
@@ -1626,14 +1629,14 @@ function validateProviderStatus(status) {
|
|
|
1626
1629
|
|
|
1627
1630
|
function validateInspection(inspection, selection) {
|
|
1628
1631
|
assertPlainObject(inspection, 'AI provider inspection');
|
|
1629
|
-
if (
|
|
1632
|
+
if (!is.boolean(inspection.available)) {
|
|
1630
1633
|
fail('AI provider inspection.available must be a boolean.');
|
|
1631
1634
|
}
|
|
1632
1635
|
if (!inspection.available) {
|
|
1633
|
-
const code =
|
|
1636
|
+
const code = is.string(inspection.code) && inspection.code.length > 0
|
|
1634
1637
|
? inspection.code
|
|
1635
1638
|
: 'ARCANE_AI_PROVIDER_AUTHORITY_BLOCKED';
|
|
1636
|
-
const message =
|
|
1639
|
+
const message = is.string(inspection.message) && inspection.message.length > 0
|
|
1637
1640
|
? inspection.message
|
|
1638
1641
|
: 'The selected AI provider is unavailable.';
|
|
1639
1642
|
throw operationError(message, code);
|
|
@@ -1923,7 +1926,7 @@ export class AIProviderRuntime {
|
|
|
1923
1926
|
const localOnly = Object.hasOwn(options, 'localOnly')
|
|
1924
1927
|
? options.localOnly
|
|
1925
1928
|
: false;
|
|
1926
|
-
if (
|
|
1929
|
+
if (!is.boolean(localOnly)) {
|
|
1927
1930
|
fail('AI provider route localOnly must be a boolean.');
|
|
1928
1931
|
}
|
|
1929
1932
|
const slot = this.#slots[role];
|
|
@@ -2438,7 +2441,7 @@ export class AIProviderRuntime {
|
|
|
2438
2441
|
|
|
2439
2442
|
configureFromTuple(tuple) {
|
|
2440
2443
|
arcaneLogging.debug('[Arcane speech runtime] configureFromTuple', tuple);
|
|
2441
|
-
if (!
|
|
2444
|
+
if (!is.array(tuple) || tuple.length !== 6) {
|
|
2442
2445
|
fail('AI preference tuple must contain exactly six entries.');
|
|
2443
2446
|
}
|
|
2444
2447
|
|
|
@@ -2544,7 +2547,7 @@ export class AIProviderRuntime {
|
|
|
2544
2547
|
if (SPEECH_ROLES.includes(role)) {
|
|
2545
2548
|
arcaneLogging.debug('[Arcane speech runtime] provider.catalog.result', role, provider.id, providerCatalog);
|
|
2546
2549
|
}
|
|
2547
|
-
if (!
|
|
2550
|
+
if (!is.array(providerCatalog)) {
|
|
2548
2551
|
fail('AI provider.catalog() must synchronously return an array.');
|
|
2549
2552
|
}
|
|
2550
2553
|
entries.push(
|
|
@@ -2728,7 +2731,7 @@ export class AIProviderRuntime {
|
|
|
2728
2731
|
? options.localOnly
|
|
2729
2732
|
: false;
|
|
2730
2733
|
assertAbortSignal(signal);
|
|
2731
|
-
if (
|
|
2734
|
+
if (!is.boolean(localOnly)) {
|
|
2732
2735
|
fail('AI provider load localOnly must be a boolean.');
|
|
2733
2736
|
}
|
|
2734
2737
|
const slot = this.#slots[role];
|
|
@@ -3384,7 +3387,7 @@ export class AIProviderRuntime {
|
|
|
3384
3387
|
const payload = options.payload;
|
|
3385
3388
|
const field = Object.hasOwn(payload ?? {}, 'input') ? 'input' : 'text';
|
|
3386
3389
|
const text = Object.getOwnPropertyDescriptor(payload ?? {}, field)?.value;
|
|
3387
|
-
if (
|
|
3390
|
+
if (is.string(text)) {
|
|
3388
3391
|
const input = stripSpeechFormatting(text);
|
|
3389
3392
|
if (input !== text) {
|
|
3390
3393
|
const payloadProperties = Object.getOwnPropertyDescriptors(payload);
|
|
@@ -3659,14 +3662,14 @@ export class AIProviderRuntime {
|
|
|
3659
3662
|
|
|
3660
3663
|
function beginAIProviderStreamHandleCleanup(opened, activeIterator, reason) {
|
|
3661
3664
|
const cleanup = [];
|
|
3662
|
-
if (
|
|
3665
|
+
if (is.function(opened?.cancel)) {
|
|
3663
3666
|
cleanup.push(
|
|
3664
3667
|
Promise.resolve().then(function cancelOpenedAIStream() {
|
|
3665
3668
|
return opened.cancel(reason);
|
|
3666
3669
|
})
|
|
3667
3670
|
);
|
|
3668
3671
|
}
|
|
3669
|
-
if (activeIterator &&
|
|
3672
|
+
if (activeIterator && is.function(activeIterator.return)) {
|
|
3670
3673
|
cleanup.push(
|
|
3671
3674
|
Promise.resolve().then(function returnOpenedAIStream() {
|
|
3672
3675
|
return activeIterator.return();
|
|
@@ -3699,7 +3702,7 @@ export class AIProviderRuntime {
|
|
|
3699
3702
|
}
|
|
3700
3703
|
const lateCleanup = providerOpenPromise.then(
|
|
3701
3704
|
async function cleanupLateAIProviderStream(lateHandle) {
|
|
3702
|
-
if (!lateHandle ||
|
|
3705
|
+
if (!lateHandle || !is.function(lateHandle.cancel)) {
|
|
3703
3706
|
throw operationError(
|
|
3704
3707
|
'The late AI provider stream did not expose cancellable ownership.',
|
|
3705
3708
|
'ARCANE_AI_STREAM_CLEANUP_INCOMPLETE'
|
|
@@ -3815,10 +3818,10 @@ export class AIProviderRuntime {
|
|
|
3815
3818
|
});
|
|
3816
3819
|
opened = await Promise.race([providerOpenPromise, abortedOpen]);
|
|
3817
3820
|
if (!opened
|
|
3818
|
-
||
|
|
3819
|
-
||
|
|
3821
|
+
|| !is.function(opened[Symbol.asyncIterator])
|
|
3822
|
+
|| !is.function(opened.cancel)
|
|
3820
3823
|
|| !opened.result
|
|
3821
|
-
||
|
|
3824
|
+
|| !is.function(opened.result.then)) {
|
|
3822
3825
|
throw operationError(
|
|
3823
3826
|
'AI stream providers must return an async iterable with result and cancel().',
|
|
3824
3827
|
'ARCANE_AI_PROVIDER_STREAM_INVALID'
|
|
@@ -3826,7 +3829,7 @@ export class AIProviderRuntime {
|
|
|
3826
3829
|
}
|
|
3827
3830
|
providerHandle = opened;
|
|
3828
3831
|
iterator = opened[Symbol.asyncIterator]();
|
|
3829
|
-
if (!iterator ||
|
|
3832
|
+
if (!iterator || !is.function(iterator.next)) {
|
|
3830
3833
|
throw operationError(
|
|
3831
3834
|
'The AI provider stream iterator has no next() method.',
|
|
3832
3835
|
'ARCANE_AI_PROVIDER_STREAM_INVALID'
|
|
@@ -4121,7 +4124,7 @@ export class AIProviderRuntime {
|
|
|
4121
4124
|
arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted', muted);
|
|
4122
4125
|
this.#assertOpen();
|
|
4123
4126
|
this.#assertNotConfiguring();
|
|
4124
|
-
if (
|
|
4127
|
+
if (!is.boolean(muted)) {
|
|
4125
4128
|
fail('AI speech muted state must be a boolean.');
|
|
4126
4129
|
}
|
|
4127
4130
|
this.#speechDesiredMuted = muted;
|