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,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* First-class browser module for Arcane's capability-gated Ollama service.
|
|
3
6
|
* Apps should import this module instead of connecting to localhost:11434.
|
|
@@ -59,7 +62,7 @@ export class Ollama{
|
|
|
59
62
|
try{
|
|
60
63
|
const response=await this.version()
|
|
61
64
|
const version=String(
|
|
62
|
-
|
|
65
|
+
is.string(response) ? response : response?.version||''
|
|
63
66
|
).trim()||null
|
|
64
67
|
return {ready:true,version,errorCode:null}
|
|
65
68
|
}catch(error){
|
|
@@ -95,8 +98,8 @@ export class Ollama{
|
|
|
95
98
|
publicDetail:{ready:true,reason}
|
|
96
99
|
}
|
|
97
100
|
)
|
|
98
|
-
if(
|
|
99
|
-
&&
|
|
101
|
+
if(is.function(globalThis.CustomEvent)
|
|
102
|
+
&&is.function(globalThis.dispatchEvent)){
|
|
100
103
|
projectArcaneDOMEvent(globalThis,occurrence)
|
|
101
104
|
}
|
|
102
105
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
const OLLAMA_MODEL_IDENTIFIER=
|
|
2
5
|
/^[A-Za-z0-9][A-Za-z0-9._/-]{0,191}(?::[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$/;
|
|
3
6
|
|
|
@@ -7,7 +10,7 @@ const OLLAMA_MODEL_IDENTIFIER=
|
|
|
7
10
|
* compatibility from the name.
|
|
8
11
|
*/
|
|
9
12
|
export function normalizeOllamaModelIdentifier(value){
|
|
10
|
-
if(
|
|
13
|
+
if(!is.string(value)||value!==value.trim()){
|
|
11
14
|
return null;
|
|
12
15
|
}
|
|
13
16
|
if(!OLLAMA_MODEL_IDENTIFIER.test(value)||value.toUpperCase()==='TWIN'){
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
import ApiModelDatabase from './ApiModelDatabase.js';
|
|
3
6
|
import {WeatherDay,WeatherLocation,WeatherObservation,WeatherSnapshot} from '../entities/Weather.js';
|
|
@@ -41,18 +44,18 @@ function signalLike(value){
|
|
|
41
44
|
return value===undefined
|
|
42
45
|
||value===null
|
|
43
46
|
||(
|
|
44
|
-
|
|
45
|
-
&&
|
|
46
|
-
&&
|
|
47
|
-
&&
|
|
47
|
+
is.object(value)
|
|
48
|
+
&&is.boolean(value.aborted)
|
|
49
|
+
&&is.function(value.addEventListener)
|
|
50
|
+
&&is.function(value.removeEventListener)
|
|
48
51
|
);
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
function defineWeatherError(error,contract,message){
|
|
52
|
-
const candidate=error&&(
|
|
55
|
+
const candidate=error&&(is.object(error)||is.function(error))
|
|
53
56
|
?error
|
|
54
|
-
:new Error(
|
|
55
|
-
const priorCode=
|
|
57
|
+
:new Error(is.string(error)&&error.trim()?error:message);
|
|
58
|
+
const priorCode=is.string(candidate.code)&&candidate.code?candidate.code:null;
|
|
56
59
|
try{
|
|
57
60
|
if(priorCode&&priorCode!==contract.code&&!Object.hasOwn(candidate,'providerCode')){
|
|
58
61
|
Object.defineProperty(candidate,'providerCode',{configurable:true,enumerable:false,value:priorCode,writable:true});
|
|
@@ -65,7 +68,7 @@ function defineWeatherError(error,contract,message){
|
|
|
65
68
|
return candidate;
|
|
66
69
|
}catch{
|
|
67
70
|
const replacement=new Error(
|
|
68
|
-
|
|
71
|
+
is.string(candidate.message)&&candidate.message.trim()?candidate.message:message
|
|
69
72
|
);
|
|
70
73
|
replacement.code=contract.code;
|
|
71
74
|
replacement.reason=contract.reason;
|
|
@@ -111,7 +114,7 @@ function normalizedOperationError(error,record){
|
|
|
111
114
|
|
|
112
115
|
function operationOptions(value){
|
|
113
116
|
if(value===undefined)return {signal:null};
|
|
114
|
-
if(!value
|
|
117
|
+
if(!value||!is.object(value)||is.array(value)){
|
|
115
118
|
throw invalidOptionsError('Open-Meteo operation options must be an object.');
|
|
116
119
|
}
|
|
117
120
|
if(!signalLike(value.signal)){
|
|
@@ -207,7 +210,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
#releaseSignal(record){
|
|
210
|
-
if(!
|
|
213
|
+
if(!is.array(record.cleanup))return;
|
|
211
214
|
for(const remove of record.cleanup.splice(0))remove();
|
|
212
215
|
}
|
|
213
216
|
|
|
@@ -295,7 +298,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
295
298
|
#forwardRequest(kind,event){
|
|
296
299
|
if(this.#disposed||this.#events.disposed)return;
|
|
297
300
|
const operationId=event?.operationId??event?.detail?.requestId;
|
|
298
|
-
if(
|
|
301
|
+
if(!is.string(operationId)||!operationId)return;
|
|
299
302
|
const record=this.#operations.get(operationId);
|
|
300
303
|
this.#dispatch(
|
|
301
304
|
'request',
|
|
@@ -307,7 +310,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
307
310
|
#forwardError(kind,event){
|
|
308
311
|
if(this.#disposed||this.#events.disposed)return;
|
|
309
312
|
const operationId=event?.operationId??event?.detail?.requestId;
|
|
310
|
-
if(
|
|
313
|
+
if(!is.string(operationId)||!operationId)return;
|
|
311
314
|
const record=this.#operations.get(operationId);
|
|
312
315
|
if(record){
|
|
313
316
|
if(record.errorPublished)return;
|
|
@@ -394,7 +397,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
394
397
|
precipitationUnit='inch',
|
|
395
398
|
forecastDays
|
|
396
399
|
}=options;
|
|
397
|
-
if(forecastDays!==undefined&&(!
|
|
400
|
+
if(forecastDays!==undefined&&(!is.safeInteger(forecastDays)||forecastDays<=0)){
|
|
398
401
|
throw invalidOptionsError('Open-Meteo forecastDays must be a positive integer when provided.');
|
|
399
402
|
}
|
|
400
403
|
const record=this.#startOperation('forecast-load',options.signal??null);
|
|
@@ -458,8 +461,8 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
458
461
|
'The weather event type is invalid.'
|
|
459
462
|
);
|
|
460
463
|
}
|
|
461
|
-
const operationId=(
|
|
462
|
-
||(
|
|
464
|
+
const operationId=(is.string(detail?.operationId)&&detail.operationId)
|
|
465
|
+
||(is.string(detail?.requestId)&&detail.requestId)
|
|
463
466
|
||`${this.#events.instanceId}:emit:${(++this.#operationSequence).toString(36)}`;
|
|
464
467
|
this.#dispatch(type,detail,operationId);
|
|
465
468
|
}
|
|
@@ -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 ChatEntity from '../entities/Chat.js';
|
|
3
6
|
import ConfiguredAIChatSession,{
|
|
@@ -28,7 +31,7 @@ function coded(error,code){
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
function isPlainRecord(value){
|
|
31
|
-
if(!value
|
|
34
|
+
if(!value||!is.object(value)||is.array(value))return false;
|
|
32
35
|
const prototype=Object.getPrototypeOf(value);
|
|
33
36
|
return prototype===Object.prototype||prototype===null;
|
|
34
37
|
}
|
|
@@ -40,16 +43,16 @@ function assertKnownKeys(value,allowed,label){
|
|
|
40
43
|
|
|
41
44
|
function boolean(value,label,defaultValue){
|
|
42
45
|
if(value===undefined) return defaultValue;
|
|
43
|
-
if(
|
|
46
|
+
if(!is.boolean(value)) throw new TypeError(`${label} must be a boolean.`);
|
|
44
47
|
return value;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
function signalLike(value){
|
|
48
51
|
return value===undefined||value===null||(
|
|
49
|
-
|
|
50
|
-
&&
|
|
51
|
-
&&
|
|
52
|
-
&&
|
|
52
|
+
is.object(value)
|
|
53
|
+
&&is.boolean(value.aborted)
|
|
54
|
+
&&is.function(value.addEventListener)
|
|
55
|
+
&&is.function(value.removeEventListener)
|
|
53
56
|
);
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -61,7 +64,7 @@ function providerRequestWithoutLifecycleCallbacks(value){
|
|
|
61
64
|
|
|
62
65
|
async function configuredArcaneChat(request){
|
|
63
66
|
const api=globalThis.Arcane?.ai;
|
|
64
|
-
if(
|
|
67
|
+
if(!is.function(api?.chat)){
|
|
65
68
|
throw coded(
|
|
66
69
|
new Error('The configured Arcane AI chat capability is unavailable.'),
|
|
67
70
|
'AI_CHAT_UNAVAILABLE'
|
|
@@ -85,7 +88,7 @@ function normalizeStreamHandlers(value){
|
|
|
85
88
|
'Persistent chat stream handlers',
|
|
86
89
|
);
|
|
87
90
|
for(const key of ['onChunk','onDataChunk','onDataResult','onToolCall']){
|
|
88
|
-
if(value[key]!==undefined
|
|
91
|
+
if(value[key]!==undefined&&!is.function(value[key])){
|
|
89
92
|
throw new TypeError(`${key} must be a function when provided.`);
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -99,7 +102,7 @@ function normalizeStreamHandlers(value){
|
|
|
99
102
|
|
|
100
103
|
function normalizeStreamResponse(terminal,output){
|
|
101
104
|
const value=terminal??output;
|
|
102
|
-
if(
|
|
105
|
+
if(is.string(value)){
|
|
103
106
|
return {message:{role:'assistant',content:value}};
|
|
104
107
|
}
|
|
105
108
|
if(isPlainRecord(value)) return value;
|
|
@@ -120,14 +123,14 @@ function terminalStructuralToolCalls(response){
|
|
|
120
123
|
}
|
|
121
124
|
const messages=hasMessage
|
|
122
125
|
?[response.message]
|
|
123
|
-
:hasChoices&&
|
|
126
|
+
:hasChoices&&is.array(response.choices)
|
|
124
127
|
?response.choices.map(choice=>choice?.message).filter(Boolean)
|
|
125
128
|
:[];
|
|
126
129
|
const calls=[];
|
|
127
130
|
for(let messageIndex=0;messageIndex<messages.length;messageIndex++){
|
|
128
131
|
const value=messages[messageIndex]?.tool_calls;
|
|
129
132
|
if(value===undefined) continue;
|
|
130
|
-
if(!
|
|
133
|
+
if(!is.array(value)){
|
|
131
134
|
throw coded(
|
|
132
135
|
new TypeError('The AI stream terminal response contains invalid structural tool calls.'),
|
|
133
136
|
'AI_CHAT_INVALID_TOOL_CALL',
|
|
@@ -146,12 +149,12 @@ function terminalStructuralToolCalls(response){
|
|
|
146
149
|
|
|
147
150
|
function sameDataValue(left,right,seen=new Map()){
|
|
148
151
|
if(Object.is(left,right)) return true;
|
|
149
|
-
if(!left||!right
|
|
150
|
-
if(
|
|
152
|
+
if(!left||!right||!is.object(left)||!is.object(right)) return false;
|
|
153
|
+
if(is.array(left)!==is.array(right)) return false;
|
|
151
154
|
const matched=seen.get(left);
|
|
152
155
|
if(matched!==undefined) return matched===right;
|
|
153
156
|
seen.set(left,right);
|
|
154
|
-
if(
|
|
157
|
+
if(is.array(left)){
|
|
155
158
|
return left.length===right.length
|
|
156
159
|
&&left.every((value,index)=>sameDataValue(value,right[index],seen));
|
|
157
160
|
}
|
|
@@ -168,8 +171,8 @@ function sameStructuralToolCall(left,right){
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
function sameStructuralToolCalls(left,right){
|
|
171
|
-
return
|
|
172
|
-
&&
|
|
174
|
+
return is.array(left)
|
|
175
|
+
&&is.array(right)
|
|
173
176
|
&&left.length===right.length
|
|
174
177
|
&&left.every((call,index)=>sameStructuralToolCall(call,right[index]));
|
|
175
178
|
}
|
|
@@ -183,13 +186,13 @@ function normalizeSend(input){
|
|
|
183
186
|
throw new TypeError('Persistent chat input must contain exactly one message or messages field.');
|
|
184
187
|
}
|
|
185
188
|
const sourceMessages=hasMessages?input.messages:[input.message];
|
|
186
|
-
if(!
|
|
189
|
+
if(!is.array(sourceMessages)||!sourceMessages.length){
|
|
187
190
|
throw new TypeError('messages must be a nonempty array of tool-result messages.');
|
|
188
191
|
}
|
|
189
192
|
const normalizedMessages=sourceMessages.map((value,index)=>{
|
|
190
193
|
const label=hasMessages?`messages[${index}]`:'message';
|
|
191
194
|
if(!isPlainRecord(value)) throw new TypeError(`${label} must be a plain object.`);
|
|
192
|
-
if(
|
|
195
|
+
if(!is.string(value.content)||!value.content.trim()){
|
|
193
196
|
throw new TypeError(`${label}.content must contain text.`);
|
|
194
197
|
}
|
|
195
198
|
const role=value.role??'user';
|
|
@@ -202,24 +205,24 @@ function normalizeSend(input){
|
|
|
202
205
|
let persistenceName=null;
|
|
203
206
|
let persistenceStatus=null;
|
|
204
207
|
if(role==='tool'){
|
|
205
|
-
if(
|
|
208
|
+
if(!is.string(value.tool_call_id)||!value.tool_call_id.trim()){
|
|
206
209
|
throw new TypeError(`${label}.tool_call_id is required for tool messages.`);
|
|
207
210
|
}
|
|
208
211
|
toolCallId=value.tool_call_id;
|
|
209
212
|
if(value.message!==undefined){
|
|
210
|
-
if(
|
|
213
|
+
if(!is.string(value.message)||!value.message.trim()){
|
|
211
214
|
throw new TypeError(`${label}.message must contain user-facing text when provided.`);
|
|
212
215
|
}
|
|
213
216
|
persistenceMessage=value.message;
|
|
214
217
|
}
|
|
215
218
|
if(value.name!==undefined){
|
|
216
|
-
if(
|
|
219
|
+
if(!is.string(value.name)||!value.name.trim()){
|
|
217
220
|
throw new TypeError(`${label}.name must contain text when provided.`);
|
|
218
221
|
}
|
|
219
222
|
persistenceName=value.name;
|
|
220
223
|
}
|
|
221
224
|
if(value.status!==undefined){
|
|
222
|
-
if(
|
|
225
|
+
if(!is.string(value.status)||!value.status.trim()){
|
|
223
226
|
throw new TypeError(`${label}.status must contain text when provided.`);
|
|
224
227
|
}
|
|
225
228
|
persistenceStatus=value.status;
|
|
@@ -292,7 +295,7 @@ function normalizeOpening(input){
|
|
|
292
295
|
assertKnownKeys(input,new Set(['message','request','signal']),'Persistent chat opening input');
|
|
293
296
|
if(!isPlainRecord(input.message)) throw new TypeError('message must be a plain object.');
|
|
294
297
|
assertKnownKeys(input.message,new Set(['content','persist','role']),'message');
|
|
295
|
-
if(
|
|
298
|
+
if(!is.string(input.message.content)||!input.message.content.trim()){
|
|
296
299
|
throw new TypeError('message.content must contain text.');
|
|
297
300
|
}
|
|
298
301
|
const role=input.message.role??'user';
|
|
@@ -317,7 +320,7 @@ function normalizeOpening(input){
|
|
|
317
320
|
}
|
|
318
321
|
|
|
319
322
|
function fileName(value){
|
|
320
|
-
if(
|
|
323
|
+
if(!is.string(value)||value.length===0){
|
|
321
324
|
throw new TypeError('chatFileName must be a nonempty string.');
|
|
322
325
|
}
|
|
323
326
|
return value;
|
|
@@ -355,8 +358,8 @@ class PersistentAIChatSession{
|
|
|
355
358
|
}
|
|
356
359
|
if(options.ai!==undefined&&(
|
|
357
360
|
!options.ai
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
||!is.object(options.ai)
|
|
362
|
+
||!is.function(options.ai.fetchRequest)
|
|
360
363
|
)){
|
|
361
364
|
throw new TypeError('ai must expose fetchRequest(request).');
|
|
362
365
|
}
|
|
@@ -369,7 +372,7 @@ class PersistentAIChatSession{
|
|
|
369
372
|
const chat=options.ai===undefined
|
|
370
373
|
?options.chat??configuredArcaneChat
|
|
371
374
|
:configuredAIChat(options.ai);
|
|
372
|
-
if(
|
|
375
|
+
if(!is.function(chat)) throw new TypeError('chat must be a function.');
|
|
373
376
|
if(options.request!==undefined&&!isPlainRecord(options.request)){
|
|
374
377
|
throw new TypeError('request must be a plain object.');
|
|
375
378
|
}
|
|
@@ -381,12 +384,12 @@ class PersistentAIChatSession{
|
|
|
381
384
|
throw new TypeError(`request.${managedRequestField} is managed by the chat session.`);
|
|
382
385
|
}
|
|
383
386
|
for(const key of ['parallelToolCalls','parallel_tool_calls']){
|
|
384
|
-
if(Object.hasOwn(request,key)
|
|
387
|
+
if(Object.hasOwn(request,key)&&!is.boolean(request[key])){
|
|
385
388
|
throw new TypeError(`request.${key} must be a boolean when provided.`);
|
|
386
389
|
}
|
|
387
390
|
}
|
|
388
391
|
const systemPrompt=options.systemPrompt??'';
|
|
389
|
-
if(
|
|
392
|
+
if(!is.string(systemPrompt)) throw new TypeError('systemPrompt must be a string.');
|
|
390
393
|
this.#entity=options.chatEntity??new ChatEntity(systemPrompt);
|
|
391
394
|
if(options.chatFileName!==undefined){
|
|
392
395
|
this.#entity.fileName=fileName(options.chatFileName);
|
|
@@ -418,7 +421,7 @@ class PersistentAIChatSession{
|
|
|
418
421
|
async #requestConfiguredAI(request){
|
|
419
422
|
const providerRequest=providerRequestWithoutLifecycleCallbacks(request);
|
|
420
423
|
if(!this.#activeStream) return this.#fetchChat(providerRequest);
|
|
421
|
-
if(
|
|
424
|
+
if(!is.function(this.#options.ai?.streamRequest)){
|
|
422
425
|
const response=await this.#fetchChat(providerRequest);
|
|
423
426
|
await this.#activeStream.onDataResult(response,providerRequest.id??null);
|
|
424
427
|
return response;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
const is=new Is(false);
|
|
3
|
+
|
|
1
4
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
5
|
import Preference,{preferenceSchema} from '../entities/Preference.js';
|
|
3
6
|
import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
|
|
@@ -47,17 +50,17 @@ function operationAbortedError(reason){
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function isPlainRecord(value){
|
|
50
|
-
if(!value
|
|
53
|
+
if(!value||!is.object(value)||is.array(value)) return false;
|
|
51
54
|
const prototype=Object.getPrototypeOf(value);
|
|
52
55
|
return prototype===Object.prototype||prototype===null;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
function isAbortSignal(value){
|
|
56
59
|
return Boolean(value)
|
|
57
|
-
&&
|
|
58
|
-
&&
|
|
59
|
-
&&
|
|
60
|
-
&&
|
|
60
|
+
&&is.object(value)
|
|
61
|
+
&&is.boolean(value.aborted)
|
|
62
|
+
&&is.function(value.addEventListener)
|
|
63
|
+
&&is.function(value.removeEventListener);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
function normalizeOperationOptions(value={}){
|
|
@@ -108,10 +111,10 @@ function completePreferenceValues(values){
|
|
|
108
111
|
|
|
109
112
|
function validateAdapter(adapter){
|
|
110
113
|
if(!adapter
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
||(Reflect.has(adapter,'setMany')
|
|
114
|
+
||!is.function(adapter.get)
|
|
115
|
+
||!is.function(adapter.set)
|
|
116
|
+
||!is.function(adapter.delete)
|
|
117
|
+
||(Reflect.has(adapter,'setMany')&&!is.function(adapter.setMany))){
|
|
115
118
|
throw preferenceStoreError(
|
|
116
119
|
PREFERENCE_STORE_ERROR_CODES.adapterInvalid,
|
|
117
120
|
'preference-storage-adapter-invalid',
|
|
@@ -144,9 +147,9 @@ function localAdapter(prefix){
|
|
|
144
147
|
|
|
145
148
|
function nativeAdapter(){
|
|
146
149
|
const preferences=globalThis.Arcane?.preferences;
|
|
147
|
-
if(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
if(!is.function(preferences?.get)
|
|
151
|
+
||!is.function(preferences?.set)
|
|
152
|
+
||!is.function(preferences?.delete)) return null;
|
|
150
153
|
return preferences;
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -156,7 +159,7 @@ function isUnsupportedNativeAdapter(error){
|
|
|
156
159
|
|
|
157
160
|
function preferenceAdapter(){
|
|
158
161
|
const local=localAdapter('arcane.preferences');
|
|
159
|
-
if(
|
|
162
|
+
if(is.function(globalThis.arcaneAndroid?.postMessage)) return local;
|
|
160
163
|
const native=nativeAdapter();
|
|
161
164
|
if(!native) return local;
|
|
162
165
|
let active=native;
|
|
@@ -167,7 +170,7 @@ function preferenceAdapter(){
|
|
|
167
170
|
if(active!==native||!isUnsupportedNativeAdapter(error)) throw error;
|
|
168
171
|
active=local;
|
|
169
172
|
delete adapter.setMany;
|
|
170
|
-
if(
|
|
173
|
+
if(!is.function(active[method])) throw error;
|
|
171
174
|
return active[method](...args);
|
|
172
175
|
}
|
|
173
176
|
}
|
|
@@ -182,7 +185,7 @@ function preferenceAdapter(){
|
|
|
182
185
|
return call('delete',[key]);
|
|
183
186
|
}
|
|
184
187
|
};
|
|
185
|
-
if(
|
|
188
|
+
if(is.function(native.setMany)){
|
|
186
189
|
adapter.setMany=async function setMany(entries,context){
|
|
187
190
|
return call('setMany',[entries,context]);
|
|
188
191
|
};
|
|
@@ -327,7 +330,7 @@ export default class PreferenceStore extends EventTarget{
|
|
|
327
330
|
return this.#enqueueOperation(
|
|
328
331
|
async function setAllPreferences(commitPreferenceOperation){
|
|
329
332
|
const context={operationId,signal:operation.signal};
|
|
330
|
-
if(
|
|
333
|
+
if(is.function(store.adapter.setMany)){
|
|
331
334
|
const batch={};
|
|
332
335
|
for(const entry of entries){
|
|
333
336
|
setDataProperty(batch,entry.storageKey,entry.value);
|
|
@@ -437,7 +440,7 @@ export default class PreferenceStore extends EventTarget{
|
|
|
437
440
|
const publicDetail={
|
|
438
441
|
namespace:this.namespace,
|
|
439
442
|
values,
|
|
440
|
-
...(
|
|
443
|
+
...(is.string(detail.key)
|
|
441
444
|
?{preferenceId:detail.key,value:detail.value}
|
|
442
445
|
:{})
|
|
443
446
|
};
|