arcane-os 0.29.1 → 0.31.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 +28 -0
- package/README.md +14 -11
- package/browser-runtime/ai/browser-device-settings.mjs +2 -2
- package/browser-runtime/ai/twin-cloud.mjs +230 -0
- package/docs/reference/ai/twin-cloud.md +120 -3
- package/docs/reference/availability-and-normalization.md +9 -0
- package/docs/reference/cli.md +25 -25
- package/docs/reference/inventory/package-api.json +99 -1
- package/docs/reference/mail.md +5 -5
- package/docs/reference/protocols.md +17 -26
- package/docs/reference/pwa.md +25 -45
- package/docs/reference/sdk-api.md +277 -11
- package/docs/reviews/mail-server-purpose-review.md +1 -1
- package/package.json +3 -2
- package/runtime/arcane/modules/AI.js +14 -154
- package/src/app-layout.mjs +0 -22
- package/src/cli/main.mjs +1 -1
- package/src/dev-server.mjs +4 -40
- package/src/import-map.mjs +1 -0
- package/src/mail-credentials.mjs +3 -3
- package/src/packager/core.mjs +5 -48
- package/src/pwa.mjs +0 -43
- package/src/scaffold.mjs +3 -2
- package/src/templates/workspace-template.mjs +3 -2
- package/src/toolchain.mjs +4 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
- Standalone applications use their repository root and installed npm package
|
|
6
|
+
paths. New standalone projects default to `appsRoot: "."`; initialization
|
|
7
|
+
preserves an existing configured layout, and explicit multi-app workspaces
|
|
8
|
+
retain their selected application directories.
|
|
9
|
+
- Remove the retired `legacyAppPaths` setting and all SDK-generated nested app
|
|
10
|
+
redirects and duplicate PWA worker/inventory files. Consumers must remove the
|
|
11
|
+
obsolete setting when upgrading. Enabled PWA files remain at the app root;
|
|
12
|
+
public SDK files remain under `node_modules/arcane-os` or the installed alias.
|
|
13
|
+
- Preserve selected authored files, complete URLs and queries, installation
|
|
14
|
+
identity, saved data and caches. Generation does not delete preexisting app
|
|
15
|
+
files; application owners preserve and relocate their content independently.
|
|
16
|
+
- Generated and offline app files are committed for hosting workflows to
|
|
17
|
+
consume. Existing public Node TWiN and browser APIs remain available.
|
|
18
|
+
|
|
19
|
+
## 0.30.0
|
|
20
|
+
|
|
21
|
+
- Add the public `arcane-os/ai/twin-cloud` subpath with stateless `fetchRequest`
|
|
22
|
+
for Node and browser callers. Supply the TWiN key, model, messages, optional
|
|
23
|
+
structured-output schema and cancellation signal explicitly. The result is
|
|
24
|
+
the complete parsed provider completion, with no browser startup, saved
|
|
25
|
+
conversation, hidden model default or output cap.
|
|
26
|
+
- Share the existing TWiN HTTP, structured JSON, overload retry and cancellation
|
|
27
|
+
implementation with the browser AI owner while preserving its public methods
|
|
28
|
+
and lifecycle. Only overload responses with HTTP 429 use the existing
|
|
29
|
+
three-second retry; other failures retain their complete provider response.
|
|
30
|
+
|
|
3
31
|
## 0.29.1
|
|
4
32
|
|
|
5
33
|
- Preserve complete long and non-ASCII filenames in application release bundles
|
package/README.md
CHANGED
|
@@ -16,31 +16,34 @@
|
|
|
16
16
|
`arcane-os` is the application SDK and command-line toolchain for Arcane OS. It
|
|
17
17
|
supports two explicit workspace profiles: an external app repository uses the
|
|
18
18
|
version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
19
|
-
`arcane/` runtime. Both profiles
|
|
20
|
-
|
|
19
|
+
`arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
|
|
20
|
+
and browser run contracts while retaining their selected application layout.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.31.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Standalone browser apps use their repository root and installed npm package directly, without a
|
|
27
27
|
generated workspace `arcane/` directory or `arcane.lock.json`. Select the
|
|
28
28
|
[four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
|
|
29
29
|
in `arcane-packager.json`: development, managed import maps, and PWA resources
|
|
30
30
|
read the installed SDK at real `/node_modules/arcane-os/...` browser URLs when
|
|
31
31
|
each destination equals its source. An alias uses its actual installed folder.
|
|
32
|
-
|
|
33
|
-
`arcane
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
`arcane new my-app` defaults to `appsRoot: "."`. After npm installation,
|
|
33
|
+
`arcane import-map` refreshes managed maps and the enabled root PWA files for
|
|
34
|
+
ordinary static hosting. It generates no nested app redirects or duplicate PWA
|
|
35
|
+
files. Remove the retired `legacyAppPaths` setting when upgrading. Commit generated
|
|
36
|
+
and offline app files for hosting workflows to consume. Portable app packages
|
|
37
|
+
still contain their selected runtime.
|
|
36
38
|
Root/direct maps use `arcane-os/modules/<filename>` and
|
|
37
39
|
`arcane-os/entities/<filename>` (including extensions) plus the focused lowercase
|
|
38
40
|
exports, rather than `arcane/*` aliases. These paths resolve to the actual npm
|
|
39
41
|
files, preserving relative component URLs. New root apps put the SDK in runtime
|
|
40
42
|
`dependencies`; init preserves existing runtime/optional declarations and promotes
|
|
41
43
|
a root app's SDK development declaration without changing its other packages.
|
|
42
|
-
|
|
43
|
-
supported
|
|
44
|
+
Explicit multi-app `appsRoot: "apps"`, virtual runtime routes, and existing
|
|
45
|
+
materialized workspaces remain supported; initialization preserves their selected
|
|
46
|
+
layout. Node services continue to
|
|
44
47
|
use the installed CLI or public imports such as `arcane-os/mail`.
|
|
45
48
|
|
|
46
49
|
The [mail gateway](docs/reference/mail.md) serves HTTPS with HTTP/2 on port 4433
|
|
@@ -82,7 +85,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
82
85
|
server:
|
|
83
86
|
|
|
84
87
|
```bash
|
|
85
|
-
npx arcane-os@0.
|
|
88
|
+
npx arcane-os@0.31.0 new hello-speech --path ./hello-speech --target browser
|
|
86
89
|
cd hello-speech
|
|
87
90
|
npm install
|
|
88
91
|
```
|
|
@@ -74,11 +74,11 @@ export function getBrowserDeviceSettings(navigatorObject = globalThis.navigator)
|
|
|
74
74
|
|
|
75
75
|
// Only an explicit browser adapter type or fallback flag establishes its class.
|
|
76
76
|
// Vendor names and powerPreference (including Chromium's echoed request) do not.
|
|
77
|
-
export function describeBrowserGpu(info,
|
|
77
|
+
export function describeBrowserGpu(info, fallbackAdapter) {
|
|
78
78
|
const adapterType = is.string(info?.type) ? info.type : null;
|
|
79
79
|
const isFallbackAdapter = is.boolean(info?.isFallbackAdapter)
|
|
80
80
|
? info.isFallbackAdapter
|
|
81
|
-
: is.boolean(
|
|
81
|
+
: is.boolean(fallbackAdapter) ? fallbackAdapter : null;
|
|
82
82
|
let performanceStatus = 'unknown';
|
|
83
83
|
if (isFallbackAdapter === true || adapterType === 'CPU') performanceStatus = 'fallback';
|
|
84
84
|
else if (adapterType === 'discrete GPU') performanceStatus = 'discrete';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import Is from 'strong-type';
|
|
2
|
+
import {arcaneLogging} from '../logging.mjs';
|
|
3
|
+
|
|
4
|
+
const is = new Is(false);
|
|
5
|
+
const twinChatURL = 'https://inference.do-ai.run/v1/chat/completions';
|
|
6
|
+
|
|
7
|
+
/** A stateless TWiN request; browser AI shares the HTTP and format owners below. */
|
|
8
|
+
export async function fetchRequest({
|
|
9
|
+
twinKey,
|
|
10
|
+
model,
|
|
11
|
+
messages = [],
|
|
12
|
+
structuredOutput = false,
|
|
13
|
+
tools = [],
|
|
14
|
+
toolChoice = 'auto',
|
|
15
|
+
parallelToolCalls,
|
|
16
|
+
reasoningEffort,
|
|
17
|
+
signal = null,
|
|
18
|
+
id = Date.now(),
|
|
19
|
+
onRequest = function observeTWiNRequest(){},
|
|
20
|
+
onResponse = function observeTWiNResponse(){}
|
|
21
|
+
} = {}){
|
|
22
|
+
if(signal?.aborted){
|
|
23
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
24
|
+
}
|
|
25
|
+
if(!is.string(twinKey) || !twinKey){
|
|
26
|
+
const error = new Error('AI provider is not configured.');
|
|
27
|
+
error.code = 'AI_PROVIDER_NOT_CONFIGURED';
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
if(!is.string(model) || !model){
|
|
31
|
+
throw new TypeError('TWiN fetchRequest requires an explicit model.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const request = {model, messages, stream:false};
|
|
35
|
+
const format = structuredOutputFormat(structuredOutput);
|
|
36
|
+
if(format){
|
|
37
|
+
request.response_format = openAIResponseFormat(format);
|
|
38
|
+
}
|
|
39
|
+
if(tools.length){
|
|
40
|
+
request.tools = tools;
|
|
41
|
+
request.tool_choice = toolChoice;
|
|
42
|
+
if(parallelToolCalls !== undefined){
|
|
43
|
+
request.parallel_tool_calls = parallelToolCalls;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if(reasoningEffort){
|
|
47
|
+
request.reasoning_effort = reasoningEffort;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try{
|
|
51
|
+
await onRequest(
|
|
52
|
+
request,
|
|
53
|
+
id,
|
|
54
|
+
{operation:'fetch', transport:'http', destination:twinChatURL}
|
|
55
|
+
);
|
|
56
|
+
if(signal?.aborted){
|
|
57
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
58
|
+
}
|
|
59
|
+
const response = await fetchJSONResponse(
|
|
60
|
+
twinChatURL,
|
|
61
|
+
{
|
|
62
|
+
method:'POST',
|
|
63
|
+
credentials:'omit',
|
|
64
|
+
headers:{
|
|
65
|
+
'Content-Type':'application/json',
|
|
66
|
+
Authorization:`Bearer ${twinKey}`
|
|
67
|
+
},
|
|
68
|
+
body:JSON.stringify(request),
|
|
69
|
+
...(signal ? {signal} : {})
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
if(signal?.aborted){
|
|
73
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
74
|
+
}
|
|
75
|
+
await onResponse(response, id, false);
|
|
76
|
+
if(signal?.aborted){
|
|
77
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
78
|
+
}
|
|
79
|
+
return response;
|
|
80
|
+
}catch(error){
|
|
81
|
+
if(isAIRequestAbort(error, signal)){
|
|
82
|
+
throw normalizeAIRequestAbort(error);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function isAIRequestAbort(error, signal){
|
|
89
|
+
return signal?.aborted
|
|
90
|
+
|| error?.name === 'AbortError'
|
|
91
|
+
|| error?.code === 'ARCANE_REQUEST_ABORTED'
|
|
92
|
+
|| error?.code === 'ARCANE_AI_REQUEST_ABORTED'
|
|
93
|
+
|| error?.code === 'AI_REQUEST_ABORTED';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function normalizeAIRequestAbort(error){
|
|
97
|
+
if(error?.code === 'ARCANE_AI_REQUEST_ABORTED'){
|
|
98
|
+
return error;
|
|
99
|
+
}
|
|
100
|
+
const normalized = new Error('The AI request was cancelled.', {cause:error});
|
|
101
|
+
normalized.name = 'AbortError';
|
|
102
|
+
normalized.code = 'ARCANE_AI_REQUEST_ABORTED';
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function structuredOutputFormat(value = false){
|
|
107
|
+
if(value === false || value === null || value === undefined){
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if(value === true || value === 'json'){
|
|
111
|
+
return 'json';
|
|
112
|
+
}
|
|
113
|
+
if(
|
|
114
|
+
is.object(value)
|
|
115
|
+
&& !is.array(value)
|
|
116
|
+
&& (
|
|
117
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
118
|
+
|| Object.getPrototypeOf(value) === null
|
|
119
|
+
)
|
|
120
|
+
){
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
const error = new TypeError(
|
|
124
|
+
'AI structured output must be enabled with true, json, or a JSON Schema object.'
|
|
125
|
+
);
|
|
126
|
+
error.code = 'AI_STRUCTURED_OUTPUT_INVALID';
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function openAIResponseFormat(format){
|
|
131
|
+
if(format === 'json'){
|
|
132
|
+
return {type:'json_object'};
|
|
133
|
+
}
|
|
134
|
+
if(format){
|
|
135
|
+
return {
|
|
136
|
+
type:'json_schema',
|
|
137
|
+
json_schema:{name:'structured_response', strict:true, schema:format}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Shared with browser streaming; consume no successful body at this boundary. */
|
|
144
|
+
export async function fetchHTTPResponse(url, options){
|
|
145
|
+
const {signal} = options;
|
|
146
|
+
const retryDelayMs = 3000;
|
|
147
|
+
try{
|
|
148
|
+
while(true){
|
|
149
|
+
if(signal?.aborted){
|
|
150
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
151
|
+
}
|
|
152
|
+
const response = await fetch(url, options);
|
|
153
|
+
if(signal?.aborted){
|
|
154
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
155
|
+
}
|
|
156
|
+
if(response.ok){
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
const contentType = response.headers.get('content-type') || '';
|
|
160
|
+
const error = contentType.includes('application/json')
|
|
161
|
+
? await response.json()
|
|
162
|
+
: await response.text();
|
|
163
|
+
if(signal?.aborted){
|
|
164
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
165
|
+
}
|
|
166
|
+
const message = is.string(error)
|
|
167
|
+
? error
|
|
168
|
+
: error?.error?.message ?? error?.message;
|
|
169
|
+
if(
|
|
170
|
+
response.status !== 429
|
|
171
|
+
|| !is.string(message)
|
|
172
|
+
|| !message.toLowerCase().includes('overload')
|
|
173
|
+
){
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
arcaneLogging.warn(
|
|
177
|
+
`${message}\nRetrying in ${retryDelayMs / 1000} seconds`,
|
|
178
|
+
error
|
|
179
|
+
);
|
|
180
|
+
await new Promise(function waitForOverloadRetry(resolve, reject){
|
|
181
|
+
function finishRetryDelay(){
|
|
182
|
+
signal?.removeEventListener('abort', cancelRetryDelay);
|
|
183
|
+
resolve();
|
|
184
|
+
}
|
|
185
|
+
function cancelRetryDelay(){
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
signal.removeEventListener('abort', cancelRetryDelay);
|
|
188
|
+
reject(normalizeAIRequestAbort(signal.reason));
|
|
189
|
+
}
|
|
190
|
+
const timer = setTimeout(finishRetryDelay, retryDelayMs);
|
|
191
|
+
signal?.addEventListener('abort', cancelRetryDelay, {once:true});
|
|
192
|
+
if(signal?.aborted){
|
|
193
|
+
cancelRetryDelay();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}catch(error){
|
|
198
|
+
if(isAIRequestAbort(error, signal)){
|
|
199
|
+
throw normalizeAIRequestAbort(error);
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Return the entire parsed completion without selecting or rewriting choices. */
|
|
206
|
+
export async function fetchJSONResponse(url, options){
|
|
207
|
+
const {signal} = options;
|
|
208
|
+
try{
|
|
209
|
+
const response = await fetchHTTPResponse(url, options);
|
|
210
|
+
if(signal?.aborted){
|
|
211
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
212
|
+
}
|
|
213
|
+
const contentType = response.headers.get('content-type') || '';
|
|
214
|
+
if(!contentType.includes('application/json')){
|
|
215
|
+
throw new TypeError(
|
|
216
|
+
`AI request returned ${contentType || 'an unknown content type'} instead of JSON.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const completion = await response.json();
|
|
220
|
+
if(signal?.aborted){
|
|
221
|
+
throw normalizeAIRequestAbort(signal.reason);
|
|
222
|
+
}
|
|
223
|
+
return completion;
|
|
224
|
+
}catch(error){
|
|
225
|
+
if(isAIRequestAbort(error, signal)){
|
|
226
|
+
throw normalizeAIRequestAbort(error);
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -1,8 +1,125 @@
|
|
|
1
1
|
# TWiN Cloud: one request
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Use `fetchRequest` from `arcane-os/ai/twin-cloud` for a complete TWiN Cloud
|
|
4
|
+
request in Node or a browser with an explicit key and model. It imports no
|
|
5
|
+
browser profile, DOM, user singleton, or storage, and starts no work on import.
|
|
6
|
+
The existing browser `AI.js` interface remains available for applications that
|
|
7
|
+
already use its provider selection, lifecycle, and speech. TWiN Cloud is that
|
|
8
|
+
interface's default remote LLM service, named `TWIN`; speech stays on device
|
|
9
|
+
and does not use the TWiN access key.
|
|
10
|
+
|
|
11
|
+
## Node: explicit key, model, and structured result
|
|
12
|
+
|
|
13
|
+
Install the published `arcane-os` package in the Node project. Keep the key in
|
|
14
|
+
the application's existing server configuration, outside source control and
|
|
15
|
+
diagnostics. In this example, `server-config.json` is that caller-owned local
|
|
16
|
+
configuration file with a `twinKey` property; add its exact path to `.gitignore`
|
|
17
|
+
before creating it. The SDK does not discover or write this file.
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import serverConfig from './server-config.json' with {type: 'json'};
|
|
21
|
+
import {fetchRequest} from 'arcane-os/ai/twin-cloud';
|
|
22
|
+
|
|
23
|
+
const response = await fetchRequest({
|
|
24
|
+
twinKey: serverConfig.twinKey,
|
|
25
|
+
model: 'openai-gpt-oss-20b',
|
|
26
|
+
messages: [{
|
|
27
|
+
role: 'user',
|
|
28
|
+
content: 'Explain why the moon-powered toaster keeps burning breakfast. Return HTML and plain text.'
|
|
29
|
+
}],
|
|
30
|
+
structuredOutput: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
html: {type: 'string'},
|
|
34
|
+
text: {type: 'string'}
|
|
35
|
+
},
|
|
36
|
+
required: ['html', 'text'],
|
|
37
|
+
additionalProperties: false
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(response);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`model` is required and remains exactly the supplied identifier. This function
|
|
45
|
+
does not select the browser profile's default model. The example's prompt and
|
|
46
|
+
`html`/`text` schema are caller-owned data, not SDK business logic. Changing
|
|
47
|
+
those fields changes the requested result without changing the SDK.
|
|
48
|
+
|
|
49
|
+
The resolved value is the complete parsed provider JSON, including every
|
|
50
|
+
choice and provider field. The SDK does not extract only the first message,
|
|
51
|
+
parse its content into a second object, or replace the response with an
|
|
52
|
+
application-specific record. The supplied schema becomes
|
|
53
|
+
`response_format: {type:'json_schema', json_schema:{name:'structured_response',
|
|
54
|
+
strict:true, schema:...}}`. `structuredOutput:true` or `'json'` instead selects
|
|
55
|
+
`response_format:{type:'json_object'}`; omission leaves structured output off.
|
|
56
|
+
|
|
57
|
+
## Shared request behavior
|
|
58
|
+
|
|
59
|
+
The focused API accepts complete `messages`, optional `tools`, `toolChoice`,
|
|
60
|
+
`parallelToolCalls`, and `reasoningEffort` in addition to the explicit
|
|
61
|
+
`twinKey` and `model`. Tool options use the existing chat-completion wire fields
|
|
62
|
+
`tools`, `tool_choice`, and `parallel_tool_calls` when `tools` is nonempty.
|
|
63
|
+
A supplied nonempty `reasoningEffort` uses the provider's `reasoning_effort`
|
|
64
|
+
field; omission preserves its default. No output limit is added by this API.
|
|
65
|
+
The function neither executes tools nor adds provider-response envelope
|
|
66
|
+
validation.
|
|
67
|
+
|
|
68
|
+
Optional `id`, `onRequest(request,id,metadata)`, and
|
|
69
|
+
`onResponse(response,id,false)` follow the complete-response `AI.fetchRequest`
|
|
70
|
+
callback shape. The request callback runs before dispatch; the response
|
|
71
|
+
callback receives the complete parsed result before it is returned. Omitted
|
|
72
|
+
`id` uses `Date.now()`; request metadata is
|
|
73
|
+
`{operation:'fetch',transport:'http',destination:'https://inference.do-ai.run/v1/chat/completions'}`.
|
|
74
|
+
The key is
|
|
75
|
+
transport authentication, not part of either callback's message payload. Keep
|
|
76
|
+
credentials out of application logging as well.
|
|
77
|
+
|
|
78
|
+
Only HTTP `429` with a message containing `overload` (case-insensitive) repeats automatically,
|
|
79
|
+
after `3000` milliseconds. Another overload repeats the same complete request;
|
|
80
|
+
other HTTP failures do not become an automatic retry loop. Pass a fresh
|
|
81
|
+
`AbortController`'s `signal` and call `abort()` to cancel. Cancellation during
|
|
82
|
+
the request, response-body read, retry wait, or callback settlement prevents
|
|
83
|
+
successful result delivery and rejects with `ARCANE_AI_REQUEST_ABORTED`.
|
|
84
|
+
Other HTTP failures throw the complete parsed JSON error body or text body.
|
|
85
|
+
A missing key uses `AI_PROVIDER_NOT_CONFIGURED`, a missing explicit model
|
|
86
|
+
throws `TypeError`, and an unsupported `structuredOutput` input uses
|
|
87
|
+
`AI_STRUCTURED_OUTPUT_INVALID`.
|
|
88
|
+
|
|
89
|
+
The SDK retains no request or response history between calls and uses no
|
|
90
|
+
DBOPFS, chat entity, or memory extraction. Each call's `messages` are its
|
|
91
|
+
complete caller-supplied context. The caller decides whether and how to keep
|
|
92
|
+
the result; this stateless transport adds no saved conversation or migration.
|
|
93
|
+
|
|
94
|
+
Browser applications can use this same focused import through their generated
|
|
95
|
+
managed import map. Browser Fetch and CORS behavior still apply. Importing it
|
|
96
|
+
does not instantiate `AI`, read saved preferences, configure speech, or change
|
|
97
|
+
the existing `arcane-os/ai` browser entry.
|
|
98
|
+
|
|
99
|
+
### Shared low-level integration helpers
|
|
100
|
+
|
|
101
|
+
The same module also exports the helpers used by browser `AI.js`. Ordinary
|
|
102
|
+
callers use `fetchRequest`; these exports let SDK transport integration share
|
|
103
|
+
the existing implementation rather than maintain another retry or body reader.
|
|
104
|
+
|
|
105
|
+
| Export | Contract |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `fetchHTTPResponse(url,options)` | Uses the caller's Fetch options, overload retry and cancellation; returns a successful `Response` with its body unconsumed. |
|
|
108
|
+
| `fetchJSONResponse(url,options)` | Uses that HTTP owner, requires `application/json`, and returns the complete parsed body without selecting choices. |
|
|
109
|
+
| `structuredOutputFormat(value=false)` | Maps false/null/undefined to null, true/`'json'` to `'json'`, and preserves a supplied plain JSON Schema object. Other inputs use `AI_STRUCTURED_OUTPUT_INVALID`. |
|
|
110
|
+
| `openAIResponseFormat(format)` | Maps the normalized value to `json_object`, strict `json_schema` named `structured_response`, or null. |
|
|
111
|
+
| `isAIRequestAbort(error,signal)` | Recognizes an aborted signal, `AbortError`, or the existing Arcane AI/request cancellation codes. |
|
|
112
|
+
| `normalizeAIRequestAbort(error)` | Preserves an existing `ARCANE_AI_REQUEST_ABORTED` error or creates that `AbortError` with the original value as its cause. |
|
|
113
|
+
|
|
114
|
+
These helpers start no work on import. HTTP helpers require explicit URL and
|
|
115
|
+
options; they do not add a key, model, browser state, or retained conversation.
|
|
116
|
+
Overload warnings use the shared console logger and preserve the complete
|
|
117
|
+
provider error.
|
|
118
|
+
|
|
119
|
+
## Existing browser AI interface
|
|
120
|
+
|
|
121
|
+
The following browser example uses the same managed imports as the
|
|
122
|
+
[browser speech quick start](browser-speech.md).
|
|
6
123
|
|
|
7
124
|
## Install and import
|
|
8
125
|
|
|
@@ -78,6 +78,7 @@ version; WebKitGTK availability must not be generalized to macOS.
|
|
|
78
78
|
| Read host identity, capabilities, storage, preferences, appearance, or platform state | `globalThis.Arcane` | **Cross-host** where the method is implemented and admitted | Promise behavior and `Arcane.Error` are normalized. Result fields are normalized unless the method explicitly documents a platform-dependent snapshot. |
|
|
79
79
|
| Use local AI without coupling app code to Ollama HTTP | `Arcane.localAI`, `Arcane.ai`, or `/arcane/modules/Ollama.js` | Primarily **Native**; Android exposes a narrower admitted inference projection | Admission, errors, and managed-operation events are normalized. Direct Ollama response envelopes remain **Provider-native**. |
|
|
80
80
|
| Use TWiN Cloud from the renderer profile | `/arcane/modules/AI.js` | **Cloud** from an allowed browser/native renderer | High-level chat behavior is normalized by the module. The TWiN access key authenticates remote LLM chat; raw provider diagnostics remain provider-specific. No automatic cloud fallback is inferred from local failure. |
|
|
81
|
+
| Send one TWiN Cloud request with an explicit key and model | `fetchRequest` from `arcane-os/ai/twin-cloud` | **Node** and **Browser**, using standard Fetch and a remote HTTPS provider | Keeps complete messages and returns the full parsed provider JSON. Shared structured-output mapping, overload-only HTTP 429 retry after 3000 ms, and cancellation match browser TWiN transport. No browser profile, AI/user singleton, DBOPFS, or retained request history is created. |
|
|
81
82
|
| Use speech through one application helper | `/arcane/modules/AI.js` and `Arcane.speech` | **Browser** or **Native** | The helper keeps audio on device: Whisper owns STT and Kokoro owns TTS. It automatically cleans only the outbound speech-input copy and normalizes application-facing audio/text behavior while browser and native request/response plumbing differs below that boundary. |
|
|
82
83
|
| Inspect or manage raw Ollama models | `Arcane.ollama` or `/arcane/modules/Ollama.js` | **Native** desktop Core for management; narrower Android inference only | Wrapper method names, errors, streaming correlation, and admission are Arcane-controlled. Direct Ollama success envelopes are intentionally provider-native. |
|
|
83
84
|
| Use native terminal, installation, user, provisioning, or machine controls | matching `Arcane.*` namespace | **Native** and app/capability restricted | Calls and errors use the common bridge contract. Platform results can be host-specific and are marked in the method guide. |
|
|
@@ -240,6 +241,14 @@ snapshot.
|
|
|
240
241
|
|
|
241
242
|
### Provider-native within an Arcane boundary
|
|
242
243
|
|
|
244
|
+
[`arcane-os/ai/twin-cloud`](ai/twin-cloud.md) accepts an explicit `twinKey` and
|
|
245
|
+
`model`, with the same named `fetchRequest` import in Node and managed browsers.
|
|
246
|
+
It preserves complete provider response JSON while mapping the supplied
|
|
247
|
+
structured-output, tool, and reasoning options to the TWiN wire contract.
|
|
248
|
+
The SDK adds no output cap or persisted context. Cancellation applies during
|
|
249
|
+
request, body read, overload retry wait, and callback settlement. The existing
|
|
250
|
+
profile-backed browser `AI.js` entry and its lifecycle remain separate.
|
|
251
|
+
|
|
243
252
|
Direct `Arcane.ollama.chat()`, `generate()`, `show()`, `embed()`, and lifecycle
|
|
244
253
|
methods return complete Ollama-compatible envelopes. Arcane still owns error
|
|
245
254
|
normalization, chunk correlation, and host transport, but it does
|
package/docs/reference/cli.md
CHANGED
|
@@ -120,8 +120,9 @@ directory as a repository. Native target scaffolds also retain `browser` and
|
|
|
120
120
|
include the required icon. The result reports the workspace, app, descriptor,
|
|
121
121
|
target, and created paths.
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
SDK directly.
|
|
123
|
+
Each standalone app's root is its repository root. The default `--apps-root .`
|
|
124
|
+
uses the installed npm SDK directly. Explicit `--apps-root apps` selects a
|
|
125
|
+
multi-app workspace with each app beneath `apps/<id>`. Root setup
|
|
125
126
|
does not install dependencies or copy a runtime: run `npm install`, then
|
|
126
127
|
`npm run import-map`. Until installation, its result reports the import map as
|
|
127
128
|
pending with reason `sdk-install-required`.
|
|
@@ -152,7 +153,7 @@ idempotent only for files whose existing content satisfies the scaffold
|
|
|
152
153
|
contract.
|
|
153
154
|
|
|
154
155
|
`--apps-root .` selects root setup for a standalone workspace. Omission retains
|
|
155
|
-
the configured layout, or
|
|
156
|
+
the configured layout, or `.` for a new standalone configuration. `init` never moves an
|
|
156
157
|
existing application or converts the integrated Arcane OS layout.
|
|
157
158
|
|
|
158
159
|
### Example
|
|
@@ -217,8 +218,9 @@ the workspace does not already identify exactly one. The command accepts no
|
|
|
217
218
|
positional arguments and supports app scope only. `arcane-os import-map` is the
|
|
218
219
|
identical executable alias.
|
|
219
220
|
|
|
220
|
-
The generated artifact is
|
|
221
|
-
`apps/<id>/modules/arcane.importmap.json
|
|
221
|
+
The generated artifact is `modules/arcane.importmap.json` at a standalone
|
|
222
|
+
app's repository root, or `apps/<id>/modules/arcane.importmap.json` for an
|
|
223
|
+
explicit multi-app workspace. Its exact JSON is also installed in
|
|
222
224
|
the configured entry and every other admitted browser document as `<script
|
|
223
225
|
type="importmap" data-arcane-import-map>` before module loading. The complete
|
|
224
226
|
runtime map derives its entries from the selected runtime and browser-runtime
|
|
@@ -228,15 +230,13 @@ portable runtime subpaths such as `arcane-os/preference-store` and
|
|
|
228
230
|
modules. The result reports the complete map written to the selected
|
|
229
231
|
application; no fixed entry count is a release contract.
|
|
230
232
|
|
|
231
|
-
For `appsRoot: "."`, the
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
[root-only generated output](pwa.md#root-only-generated-output) before changing
|
|
239
|
-
the URLs needed by previously installed apps.
|
|
233
|
+
For `appsRoot: "."`, managed imports use the installed package paths and enabled
|
|
234
|
+
PWA files are written beside the root entry. The SDK generates no nested
|
|
235
|
+
`apps/<id>/` redirects or duplicate PWA files and no repository-root `arcane/`
|
|
236
|
+
projection. App and installation identity, saved data, and selected authored
|
|
237
|
+
files remain unchanged. The same layout applies to `arcane dev` and
|
|
238
|
+
`arcane package`. Generated and offline app files are committed; GitHub Actions
|
|
239
|
+
consume those committed files. See [root generated output](pwa.md#root-generated-output).
|
|
240
240
|
|
|
241
241
|
SDK `0.5.17` preserves the physical workspace route count and ordered include
|
|
242
242
|
list. External and modern integrated routes require `components`, `css`,
|
|
@@ -531,8 +531,9 @@ npm exec -- arcane check --app hello-world
|
|
|
531
531
|
|
|
532
532
|
Creates one complete browser release beneath `dist/<id>/`, preserving the prior
|
|
533
533
|
output until the replacement is complete. It consumes saved source and managed
|
|
534
|
-
import maps,
|
|
535
|
-
shared route destinations.
|
|
534
|
+
import maps, keeps standalone app files at the output root, and retains the
|
|
535
|
+
configured shared route destinations. Explicit multi-app workspaces retain
|
|
536
|
+
their selected app beneath `apps/<id>/`. When selected shared content supplies no root
|
|
536
537
|
`index.html`, the SDK generates one that opens the selected app entry.
|
|
537
538
|
Source document bases and resource URLs therefore retain their development
|
|
538
539
|
layout. Packaging does not run tests or checks automatically.
|
|
@@ -542,11 +543,10 @@ selected SDK content directly from `node_modules`. Only the portable output
|
|
|
542
543
|
receives copies; no workspace `arcane/` projection is required. Its runtime URLs,
|
|
543
544
|
managed import-map targets, and PWA inventory destinations match source serving.
|
|
544
545
|
|
|
545
|
-
With `appsRoot: "."`,
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
packaged installation identity. Omission or `true` preserves existing behavior.
|
|
546
|
+
With `appsRoot: "."`, the planned and actual output retain root-relative app
|
|
547
|
+
files and direct npm package paths. The SDK adds no nested app redirects or
|
|
548
|
+
duplicate PWA worker/inventory files. Explicitly selected authored resources
|
|
549
|
+
and the default packaged installation identity remain unchanged.
|
|
550
550
|
|
|
551
551
|
```text
|
|
552
552
|
arcane package [--app <id>] [--dry-run]
|
|
@@ -898,10 +898,10 @@ Resend key for all its requests. An absent named key never falls back to the
|
|
|
898
898
|
default account. Existing root `RESEND_API_KEY` and
|
|
899
899
|
`MAIL_PROFILES[name].RESEND_API_KEY` remain fallbacks when the corresponding
|
|
900
900
|
nested key property is absent. A nested property containing null or an empty
|
|
901
|
-
string means the selected key is absent and takes precedence over a
|
|
901
|
+
string means the selected key is absent and takes precedence over a root or profile key.
|
|
902
902
|
|
|
903
903
|
Set and delete preserve the file's other settings and profile containers. New
|
|
904
|
-
keys are written to the nested mail member. Existing
|
|
904
|
+
keys are written to the nested mail member. Existing root or profile keys are updated
|
|
905
905
|
in place unless the selected nested key property exists; delete removes both
|
|
906
906
|
representations of only the selected key. Status returns the selected profile,
|
|
907
907
|
`provider:'resend'`, `storage:'.arcane.env.json'`, and `exists`. Delete returns
|
|
@@ -963,7 +963,7 @@ paths. It consumes the configured provider timeout and retry guidance.
|
|
|
963
963
|
|
|
964
964
|
The Resend credential comes from the selected `.arcane.env.json` entry.
|
|
965
965
|
An explicit `--profile` overrides `arcane.config.json.mail.profile`; omitting
|
|
966
|
-
both selects the default `mail.apiKey`, with the
|
|
966
|
+
both selects the default `mail.apiKey`, with the root-key fallback described
|
|
967
967
|
above. Neither the key nor report content is accepted through argv or process
|
|
968
968
|
environment variables.
|
|
969
969
|
|
|
@@ -1006,7 +1006,7 @@ Add the listener's certificate paths to `arcane.config.json`:
|
|
|
1006
1006
|
|
|
1007
1007
|
Supply an existing PEM certificate chain and its private key. Paths resolve
|
|
1008
1008
|
relative to the selected configuration directory, or may be absolute. They
|
|
1009
|
-
belong to the listener regardless of the provider key selected.
|
|
1009
|
+
belong to the listener regardless of the provider key selected. Existing root
|
|
1010
1010
|
`MAIL_TLS_CERT_PATH` and `MAIL_TLS_KEY_PATH` in `.arcane.env.json` remain
|
|
1011
1011
|
fallbacks for omitted config paths. Explicit programmatic `certPath` and
|
|
1012
1012
|
`keyPath` options override those files. Missing TLS settings name the fields
|