arcane-os 0.2.2 → 0.3.0
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 +29 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +27 -27
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +462 -384
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +639 -98
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +470 -0
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
export const DEFAULT_MAIL_REQUEST_TIMEOUT_MS=590_000;
|
|
2
2
|
export const MAX_MAIL_RESPONSE_BYTES=65_536;
|
|
3
3
|
|
|
4
|
+
const REPORT_KEY_PATTERN=/^[a-zA-Z0-9._:-]{8,128}$/;
|
|
5
|
+
const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]{8,128}$/;
|
|
6
|
+
const PROVIDER_ID_PATTERN=/^[a-zA-Z0-9._:-]{1,256}$/;
|
|
7
|
+
const ERROR_CODE_PATTERN=/^[a-zA-Z0-9._:-]{1,80}$/;
|
|
8
|
+
const RETRYABLE_STATUS_CODES=new Set([408,425,429,500,502,503,504]);
|
|
9
|
+
const NON_RETRYABLE_RATE_CODES=new Set(['daily_quota_exceeded','monthly_quota_exceeded']);
|
|
10
|
+
const RESPONSE_CONTRACT=Object.freeze({
|
|
11
|
+
accepted:202,
|
|
12
|
+
delivery_uncertain:207,
|
|
13
|
+
partially_accepted:207,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export class MailTransportError extends Error {
|
|
17
|
+
constructor(message,{cause,code='MAIL_TRANSPORT_ERROR',retryable=false,
|
|
18
|
+
retryAfterMs=0,statusCode=0,uncertain=false}={}){
|
|
19
|
+
super(message,{cause});
|
|
20
|
+
this.name='MailTransportError';
|
|
21
|
+
this.code=code;
|
|
22
|
+
this.retryable=Boolean(retryable);
|
|
23
|
+
this.retryAfterMs=Number.isSafeInteger(retryAfterMs)&&retryAfterMs>0
|
|
24
|
+
? retryAfterMs
|
|
25
|
+
: 0;
|
|
26
|
+
this.statusCode=Number.isSafeInteger(statusCode)?statusCode:0;
|
|
27
|
+
this.uncertain=Boolean(uncertain);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
4
31
|
export function normalizeMailEndpoint(endpoint,base=globalThis.location?.href){
|
|
5
32
|
if(typeof endpoint!=='string'||!endpoint.trim()){
|
|
6
33
|
throw new Error('Mail endpoint is required');
|
|
@@ -21,15 +48,7 @@ export function normalizeMailEndpoint(endpoint,base=globalThis.location?.href){
|
|
|
21
48
|
return url.href;
|
|
22
49
|
}
|
|
23
50
|
|
|
24
|
-
|
|
25
|
-
const REQUEST_ID_PATTERN=/^[a-zA-Z0-9-]{8,128}$/;
|
|
26
|
-
const RESPONSE_CONTRACT=Object.freeze({
|
|
27
|
-
accepted:202,
|
|
28
|
-
delivery_uncertain:207,
|
|
29
|
-
partially_accepted:207,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
function serializeReport(report){
|
|
51
|
+
export function serializeMailReport(report){
|
|
33
52
|
if(!report||typeof report!=='object'||Array.isArray(report)){
|
|
34
53
|
throw new Error('Mail report must be a JSON object');
|
|
35
54
|
}
|
|
@@ -44,25 +63,69 @@ function serializeReport(report){
|
|
|
44
63
|
}
|
|
45
64
|
}
|
|
46
65
|
|
|
47
|
-
function
|
|
48
|
-
|
|
66
|
+
function validateSerializedReport(serializedReport){
|
|
67
|
+
if(typeof serializedReport!=='string'||!serializedReport){
|
|
68
|
+
throw new Error('Serialized mail report is required');
|
|
69
|
+
}
|
|
70
|
+
let parsed;
|
|
49
71
|
try{
|
|
50
|
-
|
|
72
|
+
parsed=JSON.parse(serializedReport);
|
|
51
73
|
}catch{
|
|
52
|
-
throw new Error('
|
|
74
|
+
throw new Error('Serialized mail report must contain valid JSON');
|
|
75
|
+
}
|
|
76
|
+
if(!parsed||typeof parsed!=='object'||Array.isArray(parsed)){
|
|
77
|
+
throw new Error('Serialized mail report must contain a JSON object');
|
|
78
|
+
}
|
|
79
|
+
return serializedReport;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseJsonObject(value){
|
|
83
|
+
try{
|
|
84
|
+
const parsed=JSON.parse(value);
|
|
85
|
+
return parsed&&typeof parsed==='object'&&!Array.isArray(parsed)?parsed:null;
|
|
86
|
+
}catch{
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseRetryAfter(value,now=Date.now()){
|
|
92
|
+
if(typeof value!=='string'||!value.trim()){
|
|
93
|
+
return 0;
|
|
53
94
|
}
|
|
54
|
-
|
|
95
|
+
const trimmed=value.trim();
|
|
96
|
+
if(/^\d+(?:\.\d+)?$/u.test(trimmed)){
|
|
97
|
+
return Math.min(86_400_000,Math.max(0,Math.ceil(Number(trimmed)*1000)));
|
|
98
|
+
}
|
|
99
|
+
const timestamp=Date.parse(trimmed);
|
|
100
|
+
return Number.isFinite(timestamp)
|
|
101
|
+
? Math.min(86_400_000,Math.max(0,timestamp-now))
|
|
102
|
+
: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function invalidSuccessResponse(response){
|
|
106
|
+
return new MailTransportError('Mail server returned an invalid success response',{
|
|
107
|
+
code:'MAIL_INVALID_RESPONSE',statusCode:response.status,uncertain:true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseDeliveryResponse(response,responseText){
|
|
112
|
+
const body=parseJsonObject(responseText);
|
|
113
|
+
if(!body
|
|
55
114
|
|| typeof body.requestId!=='string'||!REQUEST_ID_PATTERN.test(body.requestId)
|
|
56
115
|
|| !Object.hasOwn(RESPONSE_CONTRACT,body.status)
|
|
57
116
|
|| RESPONSE_CONTRACT[body.status]!==response.status) {
|
|
58
|
-
throw
|
|
117
|
+
throw invalidSuccessResponse(response);
|
|
59
118
|
}
|
|
60
119
|
for(const field of ['accepted','rejected']){
|
|
61
120
|
if(body[field]!==undefined
|
|
62
121
|
&& (!Number.isSafeInteger(body[field])||body[field]<0)) {
|
|
63
|
-
throw
|
|
122
|
+
throw invalidSuccessResponse(response);
|
|
64
123
|
}
|
|
65
124
|
}
|
|
125
|
+
if(body.providerId!==undefined
|
|
126
|
+
&& (typeof body.providerId!=='string'||!PROVIDER_ID_PATTERN.test(body.providerId))){
|
|
127
|
+
throw invalidSuccessResponse(response);
|
|
128
|
+
}
|
|
66
129
|
|
|
67
130
|
return {
|
|
68
131
|
requestId:body.requestId,
|
|
@@ -71,20 +134,69 @@ function parseDeliveryResponse(response,responseText){
|
|
|
71
134
|
uncertain:body.status==='delivery_uncertain',
|
|
72
135
|
status:body.status,
|
|
73
136
|
statusCode:response.status,
|
|
137
|
+
...(body.providerId?{providerId:body.providerId}:{}),
|
|
138
|
+
retryAfterMs:Number.isSafeInteger(body.retryAfterMs)&&body.retryAfterMs>0
|
|
139
|
+
? body.retryAfterMs
|
|
140
|
+
: parseRetryAfter(response.headers?.get?.('retry-after')),
|
|
74
141
|
};
|
|
75
142
|
}
|
|
76
143
|
|
|
144
|
+
function parseRejection(response,responseText){
|
|
145
|
+
const body=parseJsonObject(responseText);
|
|
146
|
+
const source=body?.error&&typeof body.error==='object'&&!Array.isArray(body.error)
|
|
147
|
+
? body.error
|
|
148
|
+
: body;
|
|
149
|
+
const rawCode=source?.code;
|
|
150
|
+
const code=typeof rawCode==='string'&&ERROR_CODE_PATTERN.test(rawCode)
|
|
151
|
+
? rawCode
|
|
152
|
+
: `MAIL_HTTP_${String(response.status)}`;
|
|
153
|
+
let retryable=typeof source?.retryable==='boolean'
|
|
154
|
+
? source.retryable
|
|
155
|
+
: RETRYABLE_STATUS_CODES.has(response.status);
|
|
156
|
+
if(code==='invalid_idempotent_request'||NON_RETRYABLE_RATE_CODES.has(code)){
|
|
157
|
+
retryable=false;
|
|
158
|
+
}else if(code==='concurrent_idempotent_requests'){
|
|
159
|
+
retryable=true;
|
|
160
|
+
}
|
|
161
|
+
const uncertain=typeof source?.uncertain==='boolean'?source.uncertain:false;
|
|
162
|
+
const bodyRetryAfter=Number.isSafeInteger(source?.retryAfterMs)&&source.retryAfterMs>0
|
|
163
|
+
? source.retryAfterMs
|
|
164
|
+
: 0;
|
|
165
|
+
return new MailTransportError(`Mail server rejected the request (${response.status})`,{
|
|
166
|
+
code,
|
|
167
|
+
retryable,
|
|
168
|
+
retryAfterMs:bodyRetryAfter||parseRetryAfter(response.headers?.get?.('retry-after')),
|
|
169
|
+
statusCode:response.status,
|
|
170
|
+
uncertain,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
77
174
|
async function readBoundedResponseText(response){
|
|
78
175
|
const declaredLength=response.headers?.get?.('content-length');
|
|
79
176
|
if(declaredLength!==null&&declaredLength!==undefined&&declaredLength!==''){
|
|
80
177
|
const parsedLength=Number(declaredLength);
|
|
81
178
|
if(!Number.isSafeInteger(parsedLength)||parsedLength<0||parsedLength>MAX_MAIL_RESPONSE_BYTES){
|
|
82
|
-
throw new
|
|
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
|
+
);
|
|
83
183
|
}
|
|
84
184
|
}
|
|
85
185
|
|
|
86
186
|
if(!response.body||typeof response.body.getReader!=='function'){
|
|
87
|
-
|
|
187
|
+
if(typeof response.text!=='function'){
|
|
188
|
+
throw new MailTransportError('Mail server returned an unreadable response',{
|
|
189
|
+
code:'MAIL_UNREADABLE_RESPONSE',statusCode:response.status,uncertain:true,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const text=await response.text();
|
|
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;
|
|
88
200
|
}
|
|
89
201
|
|
|
90
202
|
const reader=response.body.getReader();
|
|
@@ -96,12 +208,17 @@ async function readBoundedResponseText(response){
|
|
|
96
208
|
const { done,value }=await reader.read();
|
|
97
209
|
if(done) break;
|
|
98
210
|
if(!(value instanceof Uint8Array)){
|
|
99
|
-
throw new
|
|
211
|
+
throw new MailTransportError('Mail server returned an unreadable response',{
|
|
212
|
+
code:'MAIL_UNREADABLE_RESPONSE',statusCode:response.status,uncertain:true,
|
|
213
|
+
});
|
|
100
214
|
}
|
|
101
215
|
byteLength+=value.byteLength;
|
|
102
216
|
if(byteLength>MAX_MAIL_RESPONSE_BYTES){
|
|
103
|
-
await reader.cancel().catch(()
|
|
104
|
-
throw new
|
|
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
|
+
);
|
|
105
222
|
}
|
|
106
223
|
text+=decoder.decode(value,{stream:true});
|
|
107
224
|
}
|
|
@@ -112,6 +229,17 @@ async function readBoundedResponseText(response){
|
|
|
112
229
|
}
|
|
113
230
|
}
|
|
114
231
|
|
|
232
|
+
function requestBodyFrom({report,serializedReport}){
|
|
233
|
+
if(serializedReport===undefined){
|
|
234
|
+
return serializeMailReport(report);
|
|
235
|
+
}
|
|
236
|
+
const validated=validateSerializedReport(serializedReport);
|
|
237
|
+
if(report!==undefined&&serializeMailReport(report)!==validated){
|
|
238
|
+
throw new Error('Mail report does not match its immutable serialized request body');
|
|
239
|
+
}
|
|
240
|
+
return validated;
|
|
241
|
+
}
|
|
242
|
+
|
|
115
243
|
export async function sendMailReport({
|
|
116
244
|
appKey,
|
|
117
245
|
appName,
|
|
@@ -120,9 +248,11 @@ export async function sendMailReport({
|
|
|
120
248
|
report,
|
|
121
249
|
reportKey,
|
|
122
250
|
requestTimeout=DEFAULT_MAIL_REQUEST_TIMEOUT_MS,
|
|
251
|
+
serializedReport,
|
|
252
|
+
signal,
|
|
123
253
|
}){
|
|
124
254
|
if(typeof fetchImpl!=='function'){
|
|
125
|
-
throw new
|
|
255
|
+
throw new MailTransportError('Mail transport is unavailable',{code:'MAIL_UNAVAILABLE'});
|
|
126
256
|
}
|
|
127
257
|
const resolvedEndpoint=normalizeMailEndpoint(endpoint);
|
|
128
258
|
if(typeof appName!=='string'||!/^[a-z0-9](?:[a-z0-9-]{0,62})$/.test(appName)){
|
|
@@ -137,7 +267,10 @@ export async function sendMailReport({
|
|
|
137
267
|
if(appKey!==undefined&&appKey!==null&&typeof appKey!=='string'){
|
|
138
268
|
throw new Error('Mail application key must be a string');
|
|
139
269
|
}
|
|
140
|
-
|
|
270
|
+
if(signal!==undefined&&!(signal instanceof AbortSignal)){
|
|
271
|
+
throw new TypeError('Mail signal must be an AbortSignal');
|
|
272
|
+
}
|
|
273
|
+
const requestBody=requestBodyFrom({report,serializedReport});
|
|
141
274
|
|
|
142
275
|
const headers={
|
|
143
276
|
'Content-Type':'application/json',
|
|
@@ -149,32 +282,57 @@ export async function sendMailReport({
|
|
|
149
282
|
}
|
|
150
283
|
|
|
151
284
|
const controller=new AbortController();
|
|
285
|
+
let timedOut=false;
|
|
286
|
+
const forwardAbort=function forwardMailAbort(){
|
|
287
|
+
controller.abort(signal.reason??new Error('Mail request was cancelled'));
|
|
288
|
+
};
|
|
289
|
+
signal?.addEventListener('abort',forwardAbort,{once:true});
|
|
290
|
+
if(signal?.aborted){
|
|
291
|
+
forwardAbort();
|
|
292
|
+
}
|
|
152
293
|
const timeout=setTimeout(
|
|
153
|
-
|
|
294
|
+
function abortTimedOutMail(){
|
|
295
|
+
timedOut=true;
|
|
296
|
+
controller.abort(new Error('Mail request timed out'));
|
|
297
|
+
},
|
|
154
298
|
requestTimeout
|
|
155
299
|
);
|
|
156
300
|
|
|
157
301
|
try{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
302
|
+
let response;
|
|
303
|
+
try{
|
|
304
|
+
response=await fetchImpl(
|
|
305
|
+
resolvedEndpoint,
|
|
306
|
+
{
|
|
307
|
+
method:'POST',
|
|
308
|
+
headers,
|
|
309
|
+
body:requestBody,
|
|
310
|
+
credentials:'same-origin',
|
|
311
|
+
redirect:'error',
|
|
312
|
+
referrerPolicy:'no-referrer',
|
|
313
|
+
signal:controller.signal,
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}catch(error){
|
|
317
|
+
const cancelled=signal?.aborted&&!timedOut;
|
|
318
|
+
throw new MailTransportError(
|
|
319
|
+
cancelled?'Mail request was cancelled':timedOut?'Mail request timed out':'Mail server could not be reached',
|
|
320
|
+
{
|
|
321
|
+
cause:error,
|
|
322
|
+
code:cancelled?'MAIL_CANCELLED':timedOut?'MAIL_TIMEOUT':'MAIL_NETWORK_ERROR',
|
|
323
|
+
retryable:true,
|
|
324
|
+
uncertain:true,
|
|
325
|
+
}
|
|
326
|
+
);
|
|
173
327
|
}
|
|
174
328
|
|
|
175
329
|
const responseText=await readBoundedResponseText(response);
|
|
330
|
+
if(!response.ok){
|
|
331
|
+
throw parseRejection(response,responseText);
|
|
332
|
+
}
|
|
176
333
|
return parseDeliveryResponse(response,responseText);
|
|
177
334
|
}finally{
|
|
178
335
|
clearTimeout(timeout);
|
|
336
|
+
signal?.removeEventListener('abort',forwardAbort);
|
|
179
337
|
}
|
|
180
338
|
}
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
* First-class browser module for Arcane's capability-gated Ollama service.
|
|
3
3
|
* Apps should import this module instead of connecting to localhost:11434.
|
|
4
4
|
*/
|
|
5
|
+
import {
|
|
6
|
+
createArcaneEventSource,
|
|
7
|
+
projectArcaneDOMEvent
|
|
8
|
+
} from 'arcane-os/event-manager';
|
|
9
|
+
|
|
10
|
+
const PUBLISH_OLLAMA_READY=Symbol('publish-ollama-ready');
|
|
11
|
+
|
|
12
|
+
export const OLLAMA_EVENT_TYPES=Object.freeze({
|
|
13
|
+
ready:'arcane-ollama-ready'
|
|
14
|
+
});
|
|
15
|
+
export const OLLAMA_REASONS=Object.freeze({
|
|
16
|
+
ready:'ollama-module-ready'
|
|
17
|
+
});
|
|
18
|
+
|
|
5
19
|
function api(){
|
|
6
20
|
const client=globalThis.Arcane?.ollama
|
|
7
21
|
if(!client){
|
|
@@ -13,6 +27,11 @@ function api(){
|
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
export class Ollama{
|
|
30
|
+
#events=createArcaneEventSource(this,{
|
|
31
|
+
source:'ollama',
|
|
32
|
+
eventTypes:Object.freeze(Object.values(OLLAMA_EVENT_TYPES))
|
|
33
|
+
})
|
|
34
|
+
|
|
16
35
|
version(){ return api().version() }
|
|
17
36
|
models(){ return api().models() }
|
|
18
37
|
list(){ return api().models() }
|
|
@@ -63,6 +82,22 @@ export class Ollama{
|
|
|
63
82
|
unload(model){
|
|
64
83
|
return this.generate({ model,prompt:'',keep_alive:0 })
|
|
65
84
|
}
|
|
85
|
+
|
|
86
|
+
[PUBLISH_OLLAMA_READY](){
|
|
87
|
+
const reason=OLLAMA_REASONS.ready;
|
|
88
|
+
const {occurrence}=this.#events.dispatch(
|
|
89
|
+
OLLAMA_EVENT_TYPES.ready,
|
|
90
|
+
Object.freeze({ollama:this,reason}),
|
|
91
|
+
{
|
|
92
|
+
operationId:`${this.#events.instanceId}:ready:1`,
|
|
93
|
+
publicDetail:Object.freeze({ready:true,reason})
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
if(typeof globalThis.CustomEvent==='function'
|
|
97
|
+
&&typeof globalThis.dispatchEvent==='function'){
|
|
98
|
+
projectArcaneDOMEvent(globalThis,occurrence)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
export const ollama=Object.freeze(new Ollama())
|
|
@@ -71,4 +106,4 @@ export default ollama
|
|
|
71
106
|
if(!Object.prototype.hasOwnProperty.call(globalThis,'arcaneOllama')){
|
|
72
107
|
Object.defineProperty(globalThis,'arcaneOllama',{ value:ollama,enumerable:true,configurable:false,writable:false })
|
|
73
108
|
}
|
|
74
|
-
|
|
109
|
+
ollama[PUBLISH_OLLAMA_READY]()
|