arcane-os 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
- package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -148
- package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1042 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +27 -67
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +109 -1558
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -185
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1295
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -719
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2965
- package/docs/reference/sdk-api.md +0 -6698
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
export const DEFAULT_MAIL_REQUEST_TIMEOUT_MS=
|
|
2
|
-
export const MAX_MAIL_RESPONSE_BYTES=65_536;
|
|
1
|
+
export const DEFAULT_MAIL_REQUEST_TIMEOUT_MS=null;
|
|
3
2
|
|
|
4
|
-
const REPORT_KEY_PATTERN=/^[a-zA-Z0-9._:-]
|
|
5
|
-
const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]
|
|
6
|
-
const PROVIDER_ID_PATTERN=/^[a-zA-Z0-9._:-]
|
|
7
|
-
const ERROR_CODE_PATTERN=/^[a-zA-Z0-9._:-]
|
|
3
|
+
const REPORT_KEY_PATTERN=/^[a-zA-Z0-9._:-]+$/;
|
|
4
|
+
const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]+$/;
|
|
5
|
+
const PROVIDER_ID_PATTERN=/^[a-zA-Z0-9._:-]+$/;
|
|
6
|
+
const ERROR_CODE_PATTERN=/^[a-zA-Z0-9._:-]+$/;
|
|
8
7
|
const RETRYABLE_STATUS_CODES=new Set([408,425,429,500,502,503,504]);
|
|
9
8
|
const NON_RETRYABLE_RATE_CODES=new Set(['daily_quota_exceeded','monthly_quota_exceeded']);
|
|
10
|
-
const RESPONSE_CONTRACT=
|
|
9
|
+
const RESPONSE_CONTRACT={
|
|
11
10
|
accepted:202,
|
|
12
11
|
delivery_uncertain:207,
|
|
13
12
|
partially_accepted:207,
|
|
14
|
-
}
|
|
13
|
+
};
|
|
15
14
|
|
|
16
15
|
export class MailTransportError extends Error {
|
|
17
|
-
constructor(message,{cause,code='MAIL_TRANSPORT_ERROR',retryable=false,
|
|
16
|
+
constructor(message,{cause,code='MAIL_TRANSPORT_ERROR',details=null,retryable=false,
|
|
18
17
|
retryAfterMs=0,statusCode=0,uncertain=false}={}){
|
|
19
18
|
super(message,{cause});
|
|
20
19
|
this.name='MailTransportError';
|
|
21
20
|
this.code=code;
|
|
21
|
+
this.details=details;
|
|
22
22
|
this.retryable=Boolean(retryable);
|
|
23
23
|
this.retryAfterMs=Number.isSafeInteger(retryAfterMs)&&retryAfterMs>0
|
|
24
24
|
? retryAfterMs
|
|
@@ -94,17 +94,18 @@ function parseRetryAfter(value,now=Date.now()){
|
|
|
94
94
|
}
|
|
95
95
|
const trimmed=value.trim();
|
|
96
96
|
if(/^\d+(?:\.\d+)?$/u.test(trimmed)){
|
|
97
|
-
return Math.
|
|
97
|
+
return Math.max(0,Math.ceil(Number(trimmed)*1000));
|
|
98
98
|
}
|
|
99
99
|
const timestamp=Date.parse(trimmed);
|
|
100
100
|
return Number.isFinite(timestamp)
|
|
101
|
-
? Math.
|
|
101
|
+
? Math.max(0,timestamp-now)
|
|
102
102
|
: 0;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function invalidSuccessResponse(response){
|
|
105
|
+
function invalidSuccessResponse(response,responseText){
|
|
106
106
|
return new MailTransportError('Mail server returned an invalid success response',{
|
|
107
107
|
code:'MAIL_INVALID_RESPONSE',statusCode:response.status,uncertain:true,
|
|
108
|
+
details:parseJsonObject(responseText)??responseText
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -114,20 +115,21 @@ function parseDeliveryResponse(response,responseText){
|
|
|
114
115
|
|| typeof body.requestId!=='string'||!REQUEST_ID_PATTERN.test(body.requestId)
|
|
115
116
|
|| !Object.hasOwn(RESPONSE_CONTRACT,body.status)
|
|
116
117
|
|| RESPONSE_CONTRACT[body.status]!==response.status) {
|
|
117
|
-
throw invalidSuccessResponse(response);
|
|
118
|
+
throw invalidSuccessResponse(response,responseText);
|
|
118
119
|
}
|
|
119
120
|
for(const field of ['accepted','rejected']){
|
|
120
121
|
if(body[field]!==undefined
|
|
121
122
|
&& (!Number.isSafeInteger(body[field])||body[field]<0)) {
|
|
122
|
-
throw invalidSuccessResponse(response);
|
|
123
|
+
throw invalidSuccessResponse(response,responseText);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
if(body.providerId!==undefined
|
|
126
127
|
&& (typeof body.providerId!=='string'||!PROVIDER_ID_PATTERN.test(body.providerId))){
|
|
127
|
-
throw invalidSuccessResponse(response);
|
|
128
|
+
throw invalidSuccessResponse(response,responseText);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
return {
|
|
132
|
+
...body,
|
|
131
133
|
requestId:body.requestId,
|
|
132
134
|
sent:body.status==='accepted',
|
|
133
135
|
partial:body.status==='partially_accepted',
|
|
@@ -164,6 +166,7 @@ function parseRejection(response,responseText){
|
|
|
164
166
|
: 0;
|
|
165
167
|
return new MailTransportError(`Mail server rejected the request (${response.status})`,{
|
|
166
168
|
code,
|
|
169
|
+
details:body??responseText,
|
|
167
170
|
retryable,
|
|
168
171
|
retryAfterMs:bodyRetryAfter||parseRetryAfter(response.headers?.get?.('retry-after')),
|
|
169
172
|
statusCode:response.status,
|
|
@@ -171,37 +174,18 @@ function parseRejection(response,responseText){
|
|
|
171
174
|
});
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
async function
|
|
175
|
-
const declaredLength=response.headers?.get?.('content-length');
|
|
176
|
-
if(declaredLength!==null&&declaredLength!==undefined&&declaredLength!==''){
|
|
177
|
-
const parsedLength=Number(declaredLength);
|
|
178
|
-
if(!Number.isSafeInteger(parsedLength)||parsedLength<0||parsedLength>MAX_MAIL_RESPONSE_BYTES){
|
|
179
|
-
throw new MailTransportError(
|
|
180
|
-
`Mail server response cannot exceed ${MAX_MAIL_RESPONSE_BYTES.toLocaleString('en-US')} bytes`,
|
|
181
|
-
{code:'MAIL_RESPONSE_TOO_LARGE',statusCode:response.status,uncertain:true}
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
177
|
+
async function readResponseText(response){
|
|
186
178
|
if(!response.body||typeof response.body.getReader!=='function'){
|
|
187
179
|
if(typeof response.text!=='function'){
|
|
188
180
|
throw new MailTransportError('Mail server returned an unreadable response',{
|
|
189
181
|
code:'MAIL_UNREADABLE_RESPONSE',statusCode:response.status,uncertain:true,
|
|
190
182
|
});
|
|
191
183
|
}
|
|
192
|
-
|
|
193
|
-
if(new TextEncoder().encode(text).byteLength>MAX_MAIL_RESPONSE_BYTES){
|
|
194
|
-
throw new MailTransportError(
|
|
195
|
-
`Mail server response cannot exceed ${MAX_MAIL_RESPONSE_BYTES.toLocaleString('en-US')} bytes`,
|
|
196
|
-
{code:'MAIL_RESPONSE_TOO_LARGE',statusCode:response.status,uncertain:true}
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
return text;
|
|
184
|
+
return response.text();
|
|
200
185
|
}
|
|
201
186
|
|
|
202
187
|
const reader=response.body.getReader();
|
|
203
188
|
const decoder=new TextDecoder();
|
|
204
|
-
let byteLength=0;
|
|
205
189
|
let text='';
|
|
206
190
|
try{
|
|
207
191
|
while(true){
|
|
@@ -212,14 +196,6 @@ async function readBoundedResponseText(response){
|
|
|
212
196
|
code:'MAIL_UNREADABLE_RESPONSE',statusCode:response.status,uncertain:true,
|
|
213
197
|
});
|
|
214
198
|
}
|
|
215
|
-
byteLength+=value.byteLength;
|
|
216
|
-
if(byteLength>MAX_MAIL_RESPONSE_BYTES){
|
|
217
|
-
await reader.cancel().catch(function ignoreReaderCancellation(){});
|
|
218
|
-
throw new MailTransportError(
|
|
219
|
-
`Mail server response cannot exceed ${MAX_MAIL_RESPONSE_BYTES.toLocaleString('en-US')} bytes`,
|
|
220
|
-
{code:'MAIL_RESPONSE_TOO_LARGE',statusCode:response.status,uncertain:true}
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
199
|
text+=decoder.decode(value,{stream:true});
|
|
224
200
|
}
|
|
225
201
|
text+=decoder.decode();
|
|
@@ -235,7 +211,7 @@ function requestBodyFrom({report,serializedReport}){
|
|
|
235
211
|
}
|
|
236
212
|
const validated=validateSerializedReport(serializedReport);
|
|
237
213
|
if(report!==undefined&&serializeMailReport(report)!==validated){
|
|
238
|
-
throw new Error('Mail report does not match its
|
|
214
|
+
throw new Error('Mail report does not match its stored serialized request body');
|
|
239
215
|
}
|
|
240
216
|
return validated;
|
|
241
217
|
}
|
|
@@ -259,10 +235,11 @@ export async function sendMailReport({
|
|
|
259
235
|
throw new Error('Mail application identity is invalid');
|
|
260
236
|
}
|
|
261
237
|
if(typeof reportKey!=='string'||!REPORT_KEY_PATTERN.test(reportKey)){
|
|
262
|
-
throw new Error('Mail report key must contain
|
|
238
|
+
throw new Error('Mail report key must contain safe characters');
|
|
263
239
|
}
|
|
264
|
-
if(
|
|
265
|
-
|
|
240
|
+
if(requestTimeout!==null&&requestTimeout!==undefined
|
|
241
|
+
&&(!Number.isSafeInteger(requestTimeout)||requestTimeout<1)){
|
|
242
|
+
throw new Error('Mail request timeout must be a positive integer');
|
|
266
243
|
}
|
|
267
244
|
if(appKey!==undefined&&appKey!==null&&typeof appKey!=='string'){
|
|
268
245
|
throw new Error('Mail application key must be a string');
|
|
@@ -290,13 +267,15 @@ export async function sendMailReport({
|
|
|
290
267
|
if(signal?.aborted){
|
|
291
268
|
forwardAbort();
|
|
292
269
|
}
|
|
293
|
-
const timeout=
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
270
|
+
const timeout=requestTimeout==null
|
|
271
|
+
?null
|
|
272
|
+
:setTimeout(
|
|
273
|
+
function abortTimedOutMail(){
|
|
274
|
+
timedOut=true;
|
|
275
|
+
controller.abort(new Error('Mail request timed out'));
|
|
276
|
+
},
|
|
277
|
+
requestTimeout
|
|
278
|
+
);
|
|
300
279
|
|
|
301
280
|
try{
|
|
302
281
|
let response;
|
|
@@ -326,13 +305,13 @@ export async function sendMailReport({
|
|
|
326
305
|
);
|
|
327
306
|
}
|
|
328
307
|
|
|
329
|
-
const responseText=await
|
|
308
|
+
const responseText=await readResponseText(response);
|
|
330
309
|
if(!response.ok){
|
|
331
310
|
throw parseRejection(response,responseText);
|
|
332
311
|
}
|
|
333
312
|
return parseDeliveryResponse(response,responseText);
|
|
334
313
|
}finally{
|
|
335
|
-
clearTimeout(timeout);
|
|
314
|
+
if(timeout!==null) clearTimeout(timeout);
|
|
336
315
|
signal?.removeEventListener('abort',forwardAbort);
|
|
337
316
|
}
|
|
338
317
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const MAX_PARAMETER_COUNT=32;
|
|
4
|
-
const MAX_RESPONSE_BYTES=MAX_MODEL_DEFINITION_CHARACTERS*4;
|
|
5
|
-
const MODEL_REFERENCE=/^[A-Za-z0-9][A-Za-z0-9._/-]{0,191}(?::[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$/;
|
|
6
|
-
const PARAMETER_NAME=/^[a-z][a-z0-9_]{0,63}$/;
|
|
1
|
+
const MODEL_REFERENCE=/^[A-Za-z0-9][A-Za-z0-9._/-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)?$/;
|
|
2
|
+
const PARAMETER_NAME=/^[a-z][a-z0-9_]*$/;
|
|
7
3
|
|
|
8
4
|
function fail(message){
|
|
9
5
|
const error=new TypeError(message);
|
|
@@ -11,83 +7,18 @@ function fail(message){
|
|
|
11
7
|
throw error;
|
|
12
8
|
}
|
|
13
9
|
|
|
14
|
-
function
|
|
15
|
-
if(
|
|
16
|
-
|
|
10
|
+
async function completeResponseText(response){
|
|
11
|
+
if(typeof response?.text!=='function'){
|
|
12
|
+
fail('The model definition response is not readable.');
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
freeze(child);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return Object.freeze(value);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function responseLength(response){
|
|
27
|
-
const raw=response?.headers?.get?.('content-length');
|
|
15
|
+
const text=await response.text();
|
|
28
16
|
|
|
29
|
-
if(
|
|
30
|
-
|
|
17
|
+
if(typeof text!=='string'){
|
|
18
|
+
fail('The model definition response is not readable text.');
|
|
31
19
|
}
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
return Number.isSafeInteger(value)&&value>=0?value:Number.POSITIVE_INFINITY;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function boundedResponseText(response,maxBytes=MAX_RESPONSE_BYTES){
|
|
38
|
-
const declaredLength=responseLength(response);
|
|
39
|
-
|
|
40
|
-
if(declaredLength!==null&&declaredLength>maxBytes){
|
|
41
|
-
fail('The model definition response exceeds the allowed size.');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const reader=response?.body?.getReader?.();
|
|
45
|
-
|
|
46
|
-
if(!reader){
|
|
47
|
-
if(typeof response?.text!=='function'){
|
|
48
|
-
fail('The model definition response is not readable.');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const text=await response.text();
|
|
52
|
-
|
|
53
|
-
if(typeof text!=='string'||new TextEncoder().encode(text).byteLength>maxBytes){
|
|
54
|
-
fail('The model definition response exceeds the allowed size.');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return text;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const decoder=new TextDecoder('utf-8',{fatal:true});
|
|
61
|
-
const parts=[];
|
|
62
|
-
let total=0;
|
|
63
|
-
|
|
64
|
-
try{
|
|
65
|
-
while(true){
|
|
66
|
-
const {done,value}=await reader.read();
|
|
67
|
-
|
|
68
|
-
if(done){
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
if(!(value instanceof Uint8Array)){
|
|
72
|
-
fail('The model definition response contained an invalid byte chunk.');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
total+=value.byteLength;
|
|
76
|
-
if(total>maxBytes){
|
|
77
|
-
await reader.cancel?.();
|
|
78
|
-
fail('The model definition response exceeds the allowed size.');
|
|
79
|
-
}
|
|
80
|
-
parts.push(decoder.decode(value,{stream:true}));
|
|
81
|
-
}
|
|
82
|
-
parts.push(decoder.decode());
|
|
83
|
-
}catch(error){
|
|
84
|
-
if(error?.code==='MODEL_DEFINITION_INVALID'){
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
fail('The model definition response is not valid UTF-8 text.');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return parts.join('');
|
|
21
|
+
return text;
|
|
91
22
|
}
|
|
92
23
|
|
|
93
24
|
/**
|
|
@@ -98,17 +29,15 @@ async function boundedResponseText(response,maxBytes=MAX_RESPONSE_BYTES){
|
|
|
98
29
|
export function parseModelDefinition(source){
|
|
99
30
|
if(typeof source!=='string'
|
|
100
31
|
||!source
|
|
101
|
-
||source.length>MAX_MODEL_DEFINITION_CHARACTERS
|
|
102
32
|
||source.includes('\0')
|
|
103
33
|
){
|
|
104
|
-
fail('The model definition must be
|
|
34
|
+
fail('The model definition must be non-empty text without null characters.');
|
|
105
35
|
}
|
|
106
36
|
|
|
107
|
-
const normalized=source
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
37
|
+
const normalized=source
|
|
38
|
+
.replace(/^\uFEFF/u,'')
|
|
39
|
+
.replaceAll('\r\n','\n')
|
|
40
|
+
.replaceAll('\r','\n');
|
|
112
41
|
|
|
113
42
|
const match=normalized.match(
|
|
114
43
|
/^FROM ([^\n]+)\n\nSYSTEM """\n([\s\S]*?)\n"""(?:\n\n([\s\S]+?))?\n?$/
|
|
@@ -125,18 +54,9 @@ export function parseModelDefinition(source){
|
|
|
125
54
|
if(!MODEL_REFERENCE.test(from)){
|
|
126
55
|
fail('The model definition contains an invalid base-model reference.');
|
|
127
56
|
}
|
|
128
|
-
if(!system.trim()
|
|
129
|
-
||system!==system.trim()
|
|
130
|
-
||system.length>MAX_SYSTEM_PROMPT_CHARACTERS
|
|
131
|
-
||system.split('\n').some(function systemLineTooLong(line){
|
|
132
|
-
return line.length>4096;
|
|
133
|
-
})
|
|
134
|
-
){
|
|
57
|
+
if(!system.trim()){
|
|
135
58
|
fail('The model definition contains an invalid SYSTEM prompt.');
|
|
136
59
|
}
|
|
137
|
-
if(parameterLines.length>MAX_PARAMETER_COUNT){
|
|
138
|
-
fail('The model definition contains an invalid parameter count.');
|
|
139
|
-
}
|
|
140
60
|
|
|
141
61
|
const parameters={};
|
|
142
62
|
|
|
@@ -145,8 +65,7 @@ export function parseModelDefinition(source){
|
|
|
145
65
|
|
|
146
66
|
if(!parameter
|
|
147
67
|
||!PARAMETER_NAME.test(parameter[1])
|
|
148
|
-
|
|
149
|
-
||parameter[2].length>256
|
|
68
|
+
||!parameter[2].trim()
|
|
150
69
|
||Object.hasOwn(parameters,parameter[1])
|
|
151
70
|
){
|
|
152
71
|
fail(`The model definition contains an invalid parameter line: ${line}`);
|
|
@@ -155,13 +74,13 @@ export function parseModelDefinition(source){
|
|
|
155
74
|
parameters[parameter[1]]=parameter[2];
|
|
156
75
|
}
|
|
157
76
|
|
|
158
|
-
return
|
|
77
|
+
return {from,system,parameters};
|
|
159
78
|
}
|
|
160
79
|
|
|
161
80
|
/**
|
|
162
|
-
* Loads a packaged definition with
|
|
163
|
-
* SYSTEM block. The definition is never evaluated and no model
|
|
164
|
-
* contacted by this helper.
|
|
81
|
+
* Loads a packaged definition with an ordinary read-only GET and returns the
|
|
82
|
+
* complete SYSTEM block. The definition is never evaluated and no model
|
|
83
|
+
* service is contacted by this helper.
|
|
165
84
|
*/
|
|
166
85
|
export async function loadModelDefinitionSystemPrompt(url,{
|
|
167
86
|
fetchImpl=globalThis.fetch
|
|
@@ -171,10 +90,7 @@ export async function loadModelDefinitionSystemPrompt(url,{
|
|
|
171
90
|
}
|
|
172
91
|
|
|
173
92
|
const response=await fetchImpl(url,{
|
|
174
|
-
method:'GET'
|
|
175
|
-
credentials:'same-origin',
|
|
176
|
-
cache:'default',
|
|
177
|
-
redirect:'error'
|
|
93
|
+
method:'GET'
|
|
178
94
|
});
|
|
179
95
|
|
|
180
96
|
if(!response||response.ok!==true){
|
|
@@ -185,5 +101,5 @@ export async function loadModelDefinitionSystemPrompt(url,{
|
|
|
185
101
|
throw error;
|
|
186
102
|
}
|
|
187
103
|
|
|
188
|
-
return parseModelDefinition(await
|
|
104
|
+
return parseModelDefinition(await completeResponseText(response)).system;
|
|
189
105
|
}
|
|
@@ -2,40 +2,40 @@ import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
|
2
2
|
import ApiModelDatabase from './ApiModelDatabase.js';
|
|
3
3
|
import {WeatherDay,WeatherLocation,WeatherObservation,WeatherSnapshot} from '../entities/Weather.js';
|
|
4
4
|
|
|
5
|
-
export const OPEN_METEO_ENDPOINTS=
|
|
5
|
+
export const OPEN_METEO_ENDPOINTS={
|
|
6
6
|
geocoding:'https://geocoding-api.open-meteo.com/v1/search',
|
|
7
7
|
forecast:'https://api.open-meteo.com/v1/forecast'
|
|
8
|
-
}
|
|
8
|
+
};
|
|
9
9
|
|
|
10
|
-
export const OPEN_METEO_WEATHER_EVENTS=
|
|
10
|
+
export const OPEN_METEO_WEATHER_EVENTS={
|
|
11
11
|
requestStarted:'weather-request',
|
|
12
12
|
requestFailed:'weather-error',
|
|
13
13
|
locationSearchSucceeded:'weather-locations',
|
|
14
14
|
forecastLoadSucceeded:'weather-weather'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const WEATHER_EVENT_TYPES=Object.
|
|
18
|
-
|
|
19
|
-
export const OPEN_METEO_WEATHER_ERRORS=
|
|
20
|
-
providerDisposed:
|
|
21
|
-
weatherEventTypeInvalid:
|
|
22
|
-
weatherLocationInvalid:
|
|
23
|
-
weatherOperationOptionsInvalid:
|
|
24
|
-
weatherLocationQueryInvalid:
|
|
25
|
-
locationSearchAborted:
|
|
26
|
-
locationSearchFailed:
|
|
27
|
-
locationSearchSuperseded:
|
|
28
|
-
forecastLoadAborted:
|
|
29
|
-
forecastLoadFailed:
|
|
30
|
-
forecastLoadSuperseded:
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const WEATHER_EVENT_NAMES=
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const WEATHER_EVENT_TYPES=Object.values(OPEN_METEO_WEATHER_EVENTS);
|
|
18
|
+
|
|
19
|
+
export const OPEN_METEO_WEATHER_ERRORS={
|
|
20
|
+
providerDisposed:{code:'ARCANE_OPEN_METEO_WEATHER_PROVIDER_DISPOSED',reason:'open-meteo-weather-provider-disposed'},
|
|
21
|
+
weatherEventTypeInvalid:{code:'ARCANE_OPEN_METEO_WEATHER_EVENT_TYPE_INVALID',reason:'weather-event-type-invalid'},
|
|
22
|
+
weatherLocationInvalid:{code:'ARCANE_OPEN_METEO_WEATHER_LOCATION_INVALID',reason:'weather-location-invalid'},
|
|
23
|
+
weatherOperationOptionsInvalid:{code:'ARCANE_OPEN_METEO_WEATHER_OPERATION_OPTIONS_INVALID',reason:'weather-operation-options-invalid'},
|
|
24
|
+
weatherLocationQueryInvalid:{code:'ARCANE_OPEN_METEO_WEATHER_LOCATION_QUERY_INVALID',reason:'weather-location-query-invalid'},
|
|
25
|
+
locationSearchAborted:{code:'ARCANE_OPEN_METEO_WEATHER_LOCATION_SEARCH_ABORTED',reason:'weather-location-search-aborted'},
|
|
26
|
+
locationSearchFailed:{code:'ARCANE_OPEN_METEO_WEATHER_LOCATION_SEARCH_FAILED',reason:'weather-location-search-rejected'},
|
|
27
|
+
locationSearchSuperseded:{code:'ARCANE_OPEN_METEO_WEATHER_LOCATION_SEARCH_SUPERSEDED',reason:'weather-location-search-superseded'},
|
|
28
|
+
forecastLoadAborted:{code:'ARCANE_OPEN_METEO_WEATHER_FORECAST_LOAD_ABORTED',reason:'weather-forecast-load-aborted'},
|
|
29
|
+
forecastLoadFailed:{code:'ARCANE_OPEN_METEO_WEATHER_FORECAST_LOAD_FAILED',reason:'weather-forecast-load-rejected'},
|
|
30
|
+
forecastLoadSuperseded:{code:'ARCANE_OPEN_METEO_WEATHER_FORECAST_LOAD_SUPERSEDED',reason:'weather-forecast-load-superseded'}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const WEATHER_EVENT_NAMES={
|
|
34
34
|
error:OPEN_METEO_WEATHER_EVENTS.requestFailed,
|
|
35
35
|
locations:OPEN_METEO_WEATHER_EVENTS.locationSearchSucceeded,
|
|
36
36
|
request:OPEN_METEO_WEATHER_EVENTS.requestStarted,
|
|
37
37
|
weather:OPEN_METEO_WEATHER_EVENTS.forecastLoadSucceeded
|
|
38
|
-
}
|
|
38
|
+
};
|
|
39
39
|
|
|
40
40
|
function signalLike(value){
|
|
41
41
|
return value===undefined
|
|
@@ -110,7 +110,7 @@ function normalizedOperationError(error,record){
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function operationOptions(value){
|
|
113
|
-
if(value===undefined)return
|
|
113
|
+
if(value===undefined)return {signal:null};
|
|
114
114
|
if(!value||typeof value!=='object'||Array.isArray(value)){
|
|
115
115
|
throw invalidOptionsError('Open-Meteo operation options must be an object.');
|
|
116
116
|
}
|
|
@@ -152,14 +152,10 @@ function parseLocations(raw){
|
|
|
152
152
|
function parseForecast(raw,{context}){return mapForecast(raw,context.location);}
|
|
153
153
|
|
|
154
154
|
export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
155
|
-
#activeLoad=null;
|
|
156
|
-
#activeSearch=null;
|
|
157
155
|
#disposed=false;
|
|
158
156
|
#events;
|
|
159
|
-
#loadGeneration=0;
|
|
160
157
|
#operationSequence=0;
|
|
161
158
|
#operations=new Map();
|
|
162
|
-
#searchGeneration=0;
|
|
163
159
|
#unsubscribe=[];
|
|
164
160
|
|
|
165
161
|
constructor({
|
|
@@ -210,12 +206,6 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
210
206
|
}
|
|
211
207
|
}
|
|
212
208
|
|
|
213
|
-
#currentOperation(record){
|
|
214
|
-
return record.kind==='location-search'
|
|
215
|
-
?this.#activeSearch===record&&this.#searchGeneration===record.generation
|
|
216
|
-
:this.#activeLoad===record&&this.#loadGeneration===record.generation;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
209
|
#releaseSignal(record){
|
|
220
210
|
if(!Array.isArray(record.cleanup))return;
|
|
221
211
|
for(const remove of record.cleanup.splice(0))remove();
|
|
@@ -225,8 +215,6 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
225
215
|
if(record.settled)return;
|
|
226
216
|
record.settled=true;
|
|
227
217
|
this.#releaseSignal(record);
|
|
228
|
-
if(record.kind==='location-search'&&this.#activeSearch===record)this.#activeSearch=null;
|
|
229
|
-
if(record.kind==='forecast-load'&&this.#activeLoad===record)this.#activeLoad=null;
|
|
230
218
|
}
|
|
231
219
|
|
|
232
220
|
#finishOperation(record){
|
|
@@ -243,30 +231,8 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
243
231
|
'The weather event type is invalid.'
|
|
244
232
|
);
|
|
245
233
|
}
|
|
246
|
-
const compatibilityDetail=
|
|
247
|
-
const
|
|
248
|
-
let publicDetail;
|
|
249
|
-
if(type==='request'){
|
|
250
|
-
publicDetail=Object.freeze(operation?{operation}:{});
|
|
251
|
-
}else if(type==='error'){
|
|
252
|
-
publicDetail=Object.freeze({
|
|
253
|
-
...(operation?{operation}:{}),
|
|
254
|
-
...(typeof detail?.error?.code==='string'?{code:detail.error.code}:{}),
|
|
255
|
-
...(typeof detail?.error?.reason==='string'?{reason:detail.error.reason}:{})
|
|
256
|
-
});
|
|
257
|
-
}else if(type==='locations'){
|
|
258
|
-
publicDetail=Object.freeze({
|
|
259
|
-
...(operation?{operation}:{}),
|
|
260
|
-
count:Array.isArray(detail?.locations)?detail.locations.length:0
|
|
261
|
-
});
|
|
262
|
-
}else{
|
|
263
|
-
publicDetail=Object.freeze({
|
|
264
|
-
...(operation?{operation}:{}),
|
|
265
|
-
...(typeof detail?.weather?.location?.id==='string'
|
|
266
|
-
?{locationId:detail.weather.location.id}
|
|
267
|
-
:{})
|
|
268
|
-
});
|
|
269
|
-
}
|
|
234
|
+
const compatibilityDetail={...detail,operationId};
|
|
235
|
+
const publicDetail={...compatibilityDetail};
|
|
270
236
|
return this.#events.dispatch(eventType,compatibilityDetail,{operationId,publicDetail});
|
|
271
237
|
}
|
|
272
238
|
|
|
@@ -309,16 +275,12 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
309
275
|
operationMessage(kind,'aborted')
|
|
310
276
|
);
|
|
311
277
|
}
|
|
312
|
-
const generation=kind==='location-search'
|
|
313
|
-
?++this.#searchGeneration
|
|
314
|
-
:++this.#loadGeneration;
|
|
315
278
|
const operationId=`${this.#events.instanceId}:${kind}:${(++this.#operationSequence).toString(36)}`;
|
|
316
279
|
const controller=new AbortController();
|
|
317
280
|
const record={
|
|
318
281
|
cleanup:[],
|
|
319
282
|
controller,
|
|
320
283
|
errorPublished:false,
|
|
321
|
-
generation,
|
|
322
284
|
kind,
|
|
323
285
|
operationId,
|
|
324
286
|
publicError:null,
|
|
@@ -326,20 +288,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
326
288
|
terminalError:null
|
|
327
289
|
};
|
|
328
290
|
linkAbortSignal(signal??null,controller,record.cleanup);
|
|
329
|
-
const previous=kind==='location-search'?this.#activeSearch:this.#activeLoad;
|
|
330
|
-
if(kind==='location-search')this.#activeSearch=record;
|
|
331
|
-
else this.#activeLoad=record;
|
|
332
291
|
this.#operations.set(operationId,record);
|
|
333
|
-
if(previous){
|
|
334
|
-
this.#terminateOperation(
|
|
335
|
-
previous,
|
|
336
|
-
defineWeatherError(
|
|
337
|
-
new Error(operationMessage(kind,'superseded')),
|
|
338
|
-
operationContract(kind,'superseded'),
|
|
339
|
-
operationMessage(kind,'superseded')
|
|
340
|
-
)
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
292
|
return record;
|
|
344
293
|
}
|
|
345
294
|
|
|
@@ -348,7 +297,6 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
348
297
|
const operationId=event?.operationId??event?.detail?.requestId;
|
|
349
298
|
if(typeof operationId!=='string'||!operationId)return;
|
|
350
299
|
const record=this.#operations.get(operationId);
|
|
351
|
-
if(record&&!this.#currentOperation(record))return;
|
|
352
300
|
this.#dispatch(
|
|
353
301
|
'request',
|
|
354
302
|
{...(event.detail||{}),operation:record?.kind??kind},
|
|
@@ -383,37 +331,30 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
383
331
|
this.#assertOpen();
|
|
384
332
|
if(geocoding)this.geocoder.setEndpoint(geocoding);
|
|
385
333
|
if(forecast)this.forecast.setEndpoint(forecast);
|
|
386
|
-
return
|
|
334
|
+
return {
|
|
387
335
|
geocoding:this.geocoder.endpoint,
|
|
388
336
|
forecast:this.forecast.endpoint
|
|
389
|
-
}
|
|
337
|
+
};
|
|
390
338
|
}
|
|
391
339
|
|
|
392
340
|
async search(query,optionsValue={}){
|
|
393
341
|
this.#assertOpen();
|
|
394
|
-
const name=String(query
|
|
395
|
-
if(name.
|
|
342
|
+
const name=String(query??'');
|
|
343
|
+
if(!name.trim()){
|
|
396
344
|
throw defineWeatherError(
|
|
397
|
-
new TypeError('Enter
|
|
345
|
+
new TypeError('Enter a location to search.'),
|
|
398
346
|
OPEN_METEO_WEATHER_ERRORS.weatherLocationQueryInvalid,
|
|
399
|
-
'Enter
|
|
347
|
+
'Enter a location to search.'
|
|
400
348
|
);
|
|
401
349
|
}
|
|
402
350
|
const options=operationOptions(optionsValue);
|
|
403
351
|
const record=this.#startOperation('location-search',options.signal??null);
|
|
404
352
|
try{
|
|
405
353
|
const result=await this.geocoder.fetch(
|
|
406
|
-
{name,
|
|
354
|
+
{name,language:'en',format:'json'},
|
|
407
355
|
{},
|
|
408
356
|
{signal:record.controller.signal,operationId:record.operationId}
|
|
409
357
|
);
|
|
410
|
-
if(!this.#currentOperation(record)){
|
|
411
|
-
throw record.terminalError??defineWeatherError(
|
|
412
|
-
new Error(operationMessage(record.kind,'superseded')),
|
|
413
|
-
operationContract(record.kind,'superseded'),
|
|
414
|
-
operationMessage(record.kind,'superseded')
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
358
|
this.#finishOperation(record);
|
|
418
359
|
this.#dispatch(
|
|
419
360
|
'locations',
|
|
@@ -450,8 +391,12 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
450
391
|
const {
|
|
451
392
|
temperatureUnit='fahrenheit',
|
|
452
393
|
windSpeedUnit='mph',
|
|
453
|
-
precipitationUnit='inch'
|
|
394
|
+
precipitationUnit='inch',
|
|
395
|
+
forecastDays
|
|
454
396
|
}=options;
|
|
397
|
+
if(forecastDays!==undefined&&(!Number.isSafeInteger(forecastDays)||forecastDays<=0)){
|
|
398
|
+
throw invalidOptionsError('Open-Meteo forecastDays must be a positive integer when provided.');
|
|
399
|
+
}
|
|
455
400
|
const record=this.#startOperation('forecast-load',options.signal??null);
|
|
456
401
|
try{
|
|
457
402
|
const result=await this.forecast.fetch(
|
|
@@ -459,7 +404,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
459
404
|
latitude:place.latitude,
|
|
460
405
|
longitude:place.longitude,
|
|
461
406
|
timezone:'auto',
|
|
462
|
-
forecast_days:
|
|
407
|
+
...(forecastDays===undefined?{}:{forecast_days:forecastDays}),
|
|
463
408
|
temperature_unit:temperatureUnit,
|
|
464
409
|
wind_speed_unit:windSpeedUnit,
|
|
465
410
|
precipitation_unit:precipitationUnit,
|
|
@@ -484,13 +429,6 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
|
|
|
484
429
|
{location:place},
|
|
485
430
|
{signal:record.controller.signal,operationId:record.operationId}
|
|
486
431
|
);
|
|
487
|
-
if(!this.#currentOperation(record)){
|
|
488
|
-
throw record.terminalError??defineWeatherError(
|
|
489
|
-
new Error(operationMessage(record.kind,'superseded')),
|
|
490
|
-
operationContract(record.kind,'superseded'),
|
|
491
|
-
operationMessage(record.kind,'superseded')
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
432
|
this.#finishOperation(record);
|
|
495
433
|
this.#dispatch(
|
|
496
434
|
'weather',
|