arcane-os 0.29.0 → 0.30.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 +25 -0
- package/README.md +8 -4
- package/browser-runtime/ai/twin-cloud.mjs +230 -0
- package/docs/architecture.md +20 -5
- package/docs/reference/ai/twin-cloud.md +120 -3
- package/docs/reference/asset-versioning.md +7 -6
- package/docs/reference/availability-and-normalization.md +9 -0
- package/docs/reference/inventory/package-api.json +102 -4
- package/docs/reference/pwa.md +4 -3
- package/docs/reference/sdk-api.md +286 -4
- package/package.json +3 -2
- package/runtime/arcane/modules/AI.js +14 -154
- package/schemas/arcane-app-bundle.schema.json +1 -1
- package/src/import-map.mjs +6 -8
- package/src/release-bundle.mjs +100 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
- Add the public `arcane-os/ai/twin-cloud` subpath with stateless `fetchRequest`
|
|
6
|
+
for Node and browser callers. Supply the TWiN key, model, messages, optional
|
|
7
|
+
structured-output schema and cancellation signal explicitly. The result is
|
|
8
|
+
the complete parsed provider completion, with no browser startup, saved
|
|
9
|
+
conversation, hidden model default or output cap.
|
|
10
|
+
- Share the existing TWiN HTTP, structured JSON, overload retry and cancellation
|
|
11
|
+
implementation with the browser AI owner while preserving its public methods
|
|
12
|
+
and lifecycle. Only overload responses with HTTP 429 use the existing
|
|
13
|
+
three-second retry; other failures retain their complete provider response.
|
|
14
|
+
|
|
15
|
+
## 0.29.1
|
|
16
|
+
|
|
17
|
+
- Preserve complete long and non-ASCII filenames in application release bundles
|
|
18
|
+
through standard per-file PAX path extensions. Public bundle creation and
|
|
19
|
+
verification retain logical payload paths and complete file content without
|
|
20
|
+
application-side renaming. Ordinary USTAR representation remains supported,
|
|
21
|
+
and the existing SDK-version metadata contract is unchanged. Bundles using
|
|
22
|
+
extended paths require the updated SDK reader or another PAX-capable reader.
|
|
23
|
+
- Preserve application-authored resource URL queries, including `v`, encoded
|
|
24
|
+
and repeated fields, empty query segments, and fragments. Import-map refresh,
|
|
25
|
+
serving, packaging, and PWA delivery now change only SDK-owned `arcaneVersion`
|
|
26
|
+
fields through the existing shared transformer.
|
|
27
|
+
|
|
3
28
|
## 0.29.0
|
|
4
29
|
|
|
5
30
|
- Add `legacyAppPaths: false` to `arcane-packager.json` for root applications
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.30.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
|
|
|
@@ -604,9 +604,13 @@ prior file until the replacement is complete. Cancellation or failure before
|
|
|
604
604
|
commit restores the prior output when that can be done without overwriting a
|
|
605
605
|
concurrent change. A conflicting or uncertain path is preserved for inspection.
|
|
606
606
|
|
|
607
|
-
The archive uses
|
|
608
|
-
|
|
609
|
-
|
|
607
|
+
The archive uses USTAR+gzip with per-file PAX path extensions for long or
|
|
608
|
+
non-ASCII filenames and publishes its v1 JSON contract at
|
|
609
|
+
`arcane-os/schemas/arcane-app-bundle.json`. Logical filenames and content remain
|
|
610
|
+
complete; ordinary USTAR paths retain their existing representation, and bundle
|
|
611
|
+
metadata keeps its existing SDK-version matching contract. Readers must support
|
|
612
|
+
PAX path extensions to consume bundles that need them. When the
|
|
613
|
+
user explicitly selects bundle verification, it rejects malformed archives, links, devices,
|
|
610
614
|
unsafe or colliding paths, unsupported archive members, trailing data, and
|
|
611
615
|
inconsistent descriptor or inventory structure. These checks reject corrupt
|
|
612
616
|
selected artifacts; they do not impose byte-count, hash, provenance, or
|
|
@@ -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
|
+
}
|
package/docs/architecture.md
CHANGED
|
@@ -273,16 +273,31 @@ External repository delivery adds a distinct schema-1
|
|
|
273
273
|
`arcane-app-release-bundle` envelope. Bundle creation uses an authored schema-2
|
|
274
274
|
`arcane-app.json`; a synthesized package or registry projection remains valid for integrated
|
|
275
275
|
packaging but is not used for an external bundle. The
|
|
276
|
-
|
|
276
|
+
logical file inventory contains exactly `ARCANE_APP_BUNDLE.json`, canonical `arcane-app.json`,
|
|
277
277
|
`payload/ARCANE_APP_RELEASE.json`, and the release inventory beneath `payload/`
|
|
278
278
|
in that order. The envelope adds no repository-only source or build tooling
|
|
279
279
|
beyond that selected release inventory. Individual apps remain responsible for
|
|
280
280
|
their authored source policy.
|
|
281
281
|
|
|
282
|
-
The bundle contract
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
The bundle contract retains schema 1 and the `ustar+gzip` format identifier.
|
|
283
|
+
Ordinary representable ASCII paths use the existing USTAR headers. Long paths
|
|
284
|
+
and non-ASCII names use a [POSIX PAX](https://docs.oracle.com/cd/E86824_01/html/E54763/pax-1.html)
|
|
285
|
+
per-file `x` header containing the complete
|
|
286
|
+
UTF-8 `path`, immediately before that file's regular header and content. This
|
|
287
|
+
transport framing is not a payload member and never appears in the logical
|
|
288
|
+
inventory or `readFile()` results. Path spelling and file content remain unchanged;
|
|
289
|
+
the existing portable-path rules still apply. The public
|
|
290
|
+
`createCanonicalUstarHeader()` helper remains a single USTAR header and retains
|
|
291
|
+
its format-local field limits; complete bundles use `createAppReleaseBundle()`.
|
|
292
|
+
|
|
293
|
+
The bundle reader consumes these `path` extensions for the following file only,
|
|
294
|
+
as well as ordinary USTAR entries. Other PAX attributes and archive member
|
|
295
|
+
types remain outside this bundle profile. Older SDK readers without PAX support
|
|
296
|
+
cannot read bundles that require these extensions; update the consuming SDK
|
|
297
|
+
before importing one. The existing same-SDK-version bundle condition is unchanged.
|
|
298
|
+
Explicit bundle verification parses the selected archive without extraction and
|
|
299
|
+
rejects genuinely malformed structures, unsafe or colliding paths, unsupported
|
|
300
|
+
members, and incompatible bundle generations. These corrupt-artifact
|
|
286
301
|
checks do not create byte-count, content-hash, provenance, or admission gates
|
|
287
302
|
for ordinary development, packaging, serving, or running.
|
|
288
303
|
|
|
@@ -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
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Applications that enable [PWA delivery](pwa.md) use clean local resource URLs.
|
|
4
4
|
Their generated offline manifest and service worker own the selected application
|
|
5
5
|
and SDK release information. In that mode, the delivery transformer removes
|
|
6
|
-
|
|
6
|
+
only the SDK-owned `arcaneVersion` field, preserving authored query fields and fragments.
|
|
7
7
|
The behavior below continues to apply when PWA delivery is disabled and to native
|
|
8
8
|
packages. Workspace runtime materialization remains usable by either target;
|
|
9
9
|
the selected browser delivery applies its PWA URL policy.
|
|
@@ -15,11 +15,12 @@ SDK package metadata, not a timestamp, content measurement, or application
|
|
|
15
15
|
constant.
|
|
16
16
|
|
|
17
17
|
For example, an existing `./arcane/modules/HTMLImport.js?v=6#module` reference
|
|
18
|
-
becomes `./arcane/modules/HTMLImport.js?arcaneVersion=${version}#module`.
|
|
19
|
-
`arcaneVersion` is the
|
|
20
|
-
|
|
21
|
-
Regenerating for another SDK release replaces that
|
|
22
|
-
|
|
18
|
+
becomes `./arcane/modules/HTMLImport.js?v=6&arcaneVersion=${version}#module`.
|
|
19
|
+
`arcaneVersion` is the SDK's resource version field: transformation updates its
|
|
20
|
+
first existing value and removes duplicate `arcaneVersion` fields, or appends it
|
|
21
|
+
when absent. Regenerating for another SDK release replaces only that SDK value.
|
|
22
|
+
Authored fields, including `v`, encoded keys and values, repeated or empty query
|
|
23
|
+
segments, their source spelling, and fragments remain intact.
|
|
23
24
|
|
|
24
25
|
## Public tooling
|
|
25
26
|
|
|
@@ -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
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
7
7
|
"moduleSystem": "ESM"
|
|
8
8
|
},
|
|
9
|
-
"memberCount":
|
|
9
|
+
"memberCount": 217,
|
|
10
10
|
"runtimeSubpathPatterns": {
|
|
11
11
|
"arcane-os/modules/*": "./runtime/arcane/modules/*",
|
|
12
12
|
"arcane-os/entities/*": "./runtime/arcane/entities/*"
|
|
@@ -464,14 +464,14 @@
|
|
|
464
464
|
"name": "createAppReleaseBundle",
|
|
465
465
|
"displayName": "createAppReleaseBundle()",
|
|
466
466
|
"kind": "function",
|
|
467
|
-
"signature": "async createAppReleaseBundle({
|
|
467
|
+
"signature": "async createAppReleaseBundle({releaseRoot, appDescriptor, outputPath, overwrite=false, signal, onEvent}={})",
|
|
468
468
|
"entrypoints": [
|
|
469
469
|
"arcane-os",
|
|
470
470
|
"arcane-os/release-bundle"
|
|
471
471
|
],
|
|
472
472
|
"primaryImport": "arcane-os",
|
|
473
473
|
"group": "Packaging and release bundles",
|
|
474
|
-
"summary": "Writes one deterministic USTAR+gzip external application bundle
|
|
474
|
+
"summary": "Writes one deterministic USTAR+gzip external application bundle, using per-file POSIX PAX path extensions for complete long or non-ASCII filenames.",
|
|
475
475
|
"availability": "Node",
|
|
476
476
|
"protocol": "SDK packager and deterministic bundle contract",
|
|
477
477
|
"normalization": "Normalized SDK validation with complete canonical archive and release content"
|
|
@@ -2164,7 +2164,7 @@
|
|
|
2164
2164
|
],
|
|
2165
2165
|
"primaryImport": "arcane-os",
|
|
2166
2166
|
"group": "Packaging and release bundles",
|
|
2167
|
-
"summary": "Parses
|
|
2167
|
+
"summary": "Parses one deterministic app bundle without extraction, supporting ordinary USTAR entries and complete per-file POSIX PAX paths within the existing same-SDK-version contract.",
|
|
2168
2168
|
"availability": "Node",
|
|
2169
2169
|
"protocol": "SDK packager and deterministic bundle contract",
|
|
2170
2170
|
"normalization": "Normalized SDK validation with complete canonical archive and release content"
|
|
@@ -3395,6 +3395,104 @@
|
|
|
3395
3395
|
"availability": "Node and browser",
|
|
3396
3396
|
"protocol": "Shared user.developer preference",
|
|
3397
3397
|
"normalization": "Returns null until target.user.ready is true, then whether target.user.developer is exactly true; returns false if preference access throws"
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
"id": "twin-cloud:fetchRequest",
|
|
3401
|
+
"name": "fetchRequest",
|
|
3402
|
+
"displayName": "fetchRequest()",
|
|
3403
|
+
"kind": "function",
|
|
3404
|
+
"signature": "async fetchRequest(options={})",
|
|
3405
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3406
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3407
|
+
"group": "TWiN Cloud requests",
|
|
3408
|
+
"summary": "Sends one complete TWiN Cloud request with an explicit caller-owned key and model.",
|
|
3409
|
+
"availability": "Node and Browser; remote HTTPS provider",
|
|
3410
|
+
"protocol": "TWiN Cloud complete-response chat",
|
|
3411
|
+
"normalization": "Preserves complete messages and parsed provider JSON; maps explicit structured-output, tool, and reasoning options; shares overload-only HTTP 429 retry after 3000 ms and cancellation; retains no browser profile, storage, or request history"
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
"id": "twin-cloud:fetchHTTPResponse",
|
|
3415
|
+
"name": "fetchHTTPResponse",
|
|
3416
|
+
"displayName": "fetchHTTPResponse()",
|
|
3417
|
+
"kind": "function",
|
|
3418
|
+
"signature": "async fetchHTTPResponse(url,options)",
|
|
3419
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3420
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3421
|
+
"group": "Shared AI transport integration",
|
|
3422
|
+
"summary": "Returns a successful unconsumed Fetch Response through the shared AI HTTP owner.",
|
|
3423
|
+
"availability": "Node and Browser",
|
|
3424
|
+
"protocol": "Shared AI HTTP transport",
|
|
3425
|
+
"normalization": "Preserves caller Fetch options and complete error bodies; repeats only overload HTTP 429 after 3000 ms; signal cancellation uses ARCANE_AI_REQUEST_ABORTED"
|
|
3426
|
+
},
|
|
3427
|
+
{
|
|
3428
|
+
"id": "twin-cloud:fetchJSONResponse",
|
|
3429
|
+
"name": "fetchJSONResponse",
|
|
3430
|
+
"displayName": "fetchJSONResponse()",
|
|
3431
|
+
"kind": "function",
|
|
3432
|
+
"signature": "async fetchJSONResponse(url,options)",
|
|
3433
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3434
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3435
|
+
"group": "Shared AI transport integration",
|
|
3436
|
+
"summary": "Reads the complete successful provider JSON through the shared HTTP owner.",
|
|
3437
|
+
"availability": "Node and Browser",
|
|
3438
|
+
"protocol": "Shared AI HTTP transport",
|
|
3439
|
+
"normalization": "Requires application/json, retains every parsed field without envelope validation, and checks cancellation before delivery"
|
|
3440
|
+
},
|
|
3441
|
+
{
|
|
3442
|
+
"id": "twin-cloud:structuredOutputFormat",
|
|
3443
|
+
"name": "structuredOutputFormat",
|
|
3444
|
+
"displayName": "structuredOutputFormat()",
|
|
3445
|
+
"kind": "function",
|
|
3446
|
+
"signature": "structuredOutputFormat(value=false)",
|
|
3447
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3448
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3449
|
+
"group": "Shared AI transport integration",
|
|
3450
|
+
"summary": "Normalizes the existing structured-output option while preserving a supplied schema.",
|
|
3451
|
+
"availability": "Node and Browser",
|
|
3452
|
+
"protocol": "Shared AI structured-output options",
|
|
3453
|
+
"normalization": "False/null/undefined return null, true/json return json, a plain schema object is returned unchanged, and other inputs use AI_STRUCTURED_OUTPUT_INVALID"
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
"id": "twin-cloud:openAIResponseFormat",
|
|
3457
|
+
"name": "openAIResponseFormat",
|
|
3458
|
+
"displayName": "openAIResponseFormat()",
|
|
3459
|
+
"kind": "function",
|
|
3460
|
+
"signature": "openAIResponseFormat(format)",
|
|
3461
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3462
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3463
|
+
"group": "Shared AI transport integration",
|
|
3464
|
+
"summary": "Maps normalized structured-output settings to chat-completion response_format.",
|
|
3465
|
+
"availability": "Node and Browser",
|
|
3466
|
+
"protocol": "Shared AI structured-output options",
|
|
3467
|
+
"normalization": "Returns json_object, strict json_schema named structured_response with the original schema, or null; does not validate schema contents"
|
|
3468
|
+
},
|
|
3469
|
+
{
|
|
3470
|
+
"id": "twin-cloud:isAIRequestAbort",
|
|
3471
|
+
"name": "isAIRequestAbort",
|
|
3472
|
+
"displayName": "isAIRequestAbort()",
|
|
3473
|
+
"kind": "function",
|
|
3474
|
+
"signature": "isAIRequestAbort(error,signal)",
|
|
3475
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3476
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3477
|
+
"group": "Shared AI transport integration",
|
|
3478
|
+
"summary": "Recognizes an aborted signal or an existing AI request cancellation error.",
|
|
3479
|
+
"availability": "Node and Browser",
|
|
3480
|
+
"protocol": "Shared AI cancellation",
|
|
3481
|
+
"normalization": "Returns a boolean for AbortError or ARCANE_REQUEST_ABORTED, ARCANE_AI_REQUEST_ABORTED, and AI_REQUEST_ABORTED; changes no operation"
|
|
3482
|
+
},
|
|
3483
|
+
{
|
|
3484
|
+
"id": "twin-cloud:normalizeAIRequestAbort",
|
|
3485
|
+
"name": "normalizeAIRequestAbort",
|
|
3486
|
+
"displayName": "normalizeAIRequestAbort()",
|
|
3487
|
+
"kind": "function",
|
|
3488
|
+
"signature": "normalizeAIRequestAbort(error)",
|
|
3489
|
+
"entrypoints": ["arcane-os/ai/twin-cloud"],
|
|
3490
|
+
"primaryImport": "arcane-os/ai/twin-cloud",
|
|
3491
|
+
"group": "Shared AI transport integration",
|
|
3492
|
+
"summary": "Returns the common AI AbortError while preserving the original cause.",
|
|
3493
|
+
"availability": "Node and Browser",
|
|
3494
|
+
"protocol": "Shared AI cancellation",
|
|
3495
|
+
"normalization": "Preserves an existing ARCANE_AI_REQUEST_ABORTED value or returns an AbortError with that code and the supplied cause"
|
|
3398
3496
|
}
|
|
3399
3497
|
]
|
|
3400
3498
|
}
|