arcane-os 0.84.0 → 0.85.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.
@@ -0,0 +1,220 @@
1
+ # Existing model observation
2
+
3
+ Model observation reads the existing renderer and Core owners selected by the
4
+ application. It exposes their current lifecycle, progress, retained errors and
5
+ actual operation identities to an explicitly selected developer observer.
6
+ Model loading, inference, selection, cancellation and saved application state
7
+ remain with those owners. Observation creates no model, accessor or Core runtime.
8
+
9
+ ## Compose the existing Core
10
+
11
+ ```js
12
+ import {createModelObservationService} from 'arcane-os/core/model-observation';
13
+
14
+ const observation = createModelObservationService({
15
+ runtime: host.runtime,
16
+ localAI: existingLocalAIService,
17
+ image: existingImageService,
18
+ decisions: existingDecisionService
19
+ });
20
+ host.runtime.registerService(observation);
21
+ ```
22
+
23
+ `host` and the three services in this example are the application's existing
24
+ instances. `localAI`, `image` and `decisions` are optional and default to `null`.
25
+ Supply their actual service definitions, including `current()` and `methods`.
26
+ The constructor neither looks up nor starts a service. Registration can follow
27
+ `startCoreHost()` on its returned runtime, before opening an additional listener;
28
+ it requires no model readiness barrier.
29
+
30
+ The service is named `model-observation` and exposes `current()`, `dispose()` and
31
+ the fixed Core methods below. `dispose()` retires only its observation requests
32
+ and subscriptions. The host continues to own the Core and model services.
33
+
34
+ An external local caller can connect through the explicitly selected
35
+ [`startCoreListener`](core-shared-host.md) endpoint on that same runtime.
36
+ For a packaged window, `native.launchContext.coreListener: {endpoint}` selects
37
+ that same-runtime listener. Before binding it, the generated Core entry registers
38
+ `model-observation` with the actual existing `services` definitions that expose
39
+ `localai.status`, `image.status` and `decisions.status`; absent owners are `null`.
40
+ The observer reuses those model-service owners and creates no accessor or model.
41
+ The application supplies its renderer attachment separately.
42
+ The observation module does not discover endpoints, launch a host, reconnect,
43
+ change a profile, or create a second runtime. Native DOM inspection and actions
44
+ retain their separate [app-control contract](native-app-control.md).
45
+
46
+ ## Attach existing renderer owners
47
+
48
+ ```js
49
+ import * as aiRuntimeState from 'arcane-os/ai-runtime-state';
50
+ import {attachModelObservation} from 'arcane-os/ai/model-observation';
51
+
52
+ const attachment = attachModelObservation({
53
+ client: existingCoreClient,
54
+ aiRuntimeState,
55
+ imageRuntime: existingImageAccessor,
56
+ decisionModel: existingBrowserDecisionModel,
57
+ modelController: existingModelController,
58
+ signal: pageLifetime.signal,
59
+ onError: function reportObservationFailure(error) {
60
+ developerDiagnostics.report(error);
61
+ }
62
+ });
63
+ await attachment.ready;
64
+ ```
65
+
66
+ Each optional owner defaults to `null`. Pass the instances already used by the
67
+ application. Creating another image accessor would describe that new accessor's
68
+ requests rather than the existing work. The supported owner methods are:
69
+
70
+ | Input | Read | Subscribe |
71
+ |---|---|---|
72
+ | `aiRuntimeState` | `getAIRuntimeState()` | `subscribeAIRuntimeState()` |
73
+ | `imageRuntime` | `current()` | `subscribe()` |
74
+ | `decisionModel` | `status()` | `subscribe()` |
75
+ | `modelController` | `status()` | `on('statechange')`, `on('progress')` |
76
+
77
+ The attachment returns `{ready, closed, current, close}`. `ready` resolves with
78
+ `{attachmentRequestId, coreRequestId}` after Core acknowledges the attachment.
79
+ `current()` returns the latest complete renderer snapshot read by this
80
+ attachment, or `null` when it was cancelled before reading. Subscriptions begin
81
+ after acknowledgement; the attachment then reads and publishes current state
82
+ again to cover changes during that handshake.
83
+
84
+ Caller abort, `pagehide`, retirement of the exact installed Core client,
85
+ transport failure, or `close()` removes the observation subscriptions and
86
+ cancels only this attachment's observation RPCs. A supplied client remains
87
+ caller-owned. The attachment never follows a replacement client or automatically
88
+ reattaches. Native document retirement also cancels its long-lived Core request.
89
+ Core then removes the renderer record; late publication to that retired
90
+ attachment fails honestly. Detachment does not unload or cancel a model.
91
+
92
+ `close()` returns `closed`, which settles after the attachment's observation
93
+ RPC promises settle locally. It does not claim that a remote model or host has
94
+ closed. Transport and observation errors remain available through `onError`
95
+ and the rejected public promises. With `onError` omitted they reach the developer
96
+ console. Normal cancellation, including cancellation initiated by the existing
97
+ client or native document owner, closes observation without a model error. When
98
+ cancellation precedes acknowledgement, `ready` rejects with the cancellation
99
+ while `closed` resolves after the observation RPCs settle.
100
+
101
+ ## Read or subscribe from an existing client
102
+
103
+ ```js
104
+ import {subscribeModelObservation} from 'arcane-os/ai/model-observation';
105
+
106
+ const current = await client.invoke('model.observation.current', {}, {signal});
107
+ const observation = subscribeModelObservation({
108
+ client,
109
+ signal,
110
+ onState: function showRetainedOwners(snapshot) {
111
+ developerDiagnostics.show(snapshot);
112
+ },
113
+ onDiagnostic: function showModelFailure(record) {
114
+ developerDiagnostics.report(record);
115
+ },
116
+ onError: function showObservationFailure(error) {
117
+ developerDiagnostics.report(error);
118
+ }
119
+ });
120
+ await observation.ready;
121
+ ```
122
+
123
+ `onState` is required. The returned `{ready, closed, current, close}` has the same
124
+ observation-only lifecycle as the renderer attachment. `ready` resolves with
125
+ the first current-state replay; `current()` holds the latest complete snapshot,
126
+ initially `null`. `onDiagnostic` explicitly selects live correlated Core model
127
+ failure frames. Omitting it requests state observation only. Callback failures
128
+ close that observation and reach `onError`; callback promises remain
129
+ caller-owned and may await `close()` without becoming its completion barrier.
130
+
131
+ Every snapshot is:
132
+
133
+ ```js
134
+ {
135
+ core: existingCoreRuntime.current(),
136
+ native: {localAI, image, decisions},
137
+ renderers: [{attachmentRequestId, coreRequestId, owners: {
138
+ aiRuntimeState, imageRuntime, decisionModel, modelController
139
+ }}]
140
+ }
141
+ ```
142
+
143
+ The values represent complete supplied-owner snapshots. Absent optional owners
144
+ are `null`; absent renderer attachments produce an empty array. Core state also
145
+ contains its real active observation requests, identified by their actual
146
+ methods, alongside other active requests. Observation does not relabel them as
147
+ model work. Snapshots are current observations of independent owners, rather
148
+ than an atomic transaction across all owners.
149
+
150
+ ## Fixed transport and correlation
151
+
152
+ | Method/event | Contract |
153
+ |---|---|
154
+ | `model.observation.current` | Empty parameters; returns the current snapshot without model refresh or readiness waits. |
155
+ | `model.observation.watch` | `{diagnostics:false}` by default; remains pending until cancellation. |
156
+ | `model.observation.state` | Targeted watch event `{watchRequestId,snapshot}`; the first event acknowledges the watch and replays current state. |
157
+ | `model.observation.error` | Targeted diagnostic `{watchRequestId,source:'core',method,frame}` preserving the complete original failed response frame. |
158
+ | `model.observation.renderer.attach` | `{snapshot}`; remains pending to own the renderer publication lifetime. |
159
+ | `model.observation.renderer.attached` | Targeted acknowledgement `{attachmentRequestId,coreRequestId}`. |
160
+ | `model.observation.renderer.publish` | `{attachmentRequestId,coreRequestId,snapshot}`; replaces that attachment's current snapshot and returns both attachment identities. |
161
+
162
+ Both helpers register listeners before invoking. Existing
163
+ [`client.invoke`](core-client.md) supplies `onRequest({requestId})` before send;
164
+ long-lived observation requests use `timeoutMs:0`. Their acknowledgement is a
165
+ targeted request event, while the invocation promise remains pending.
166
+
167
+ `attachmentRequestId` and `watchRequestId` are the actual client-visible Core
168
+ request identities supplied by service context `clientRequestId`.
169
+ `coreRequestId` is the actual internal Core frame identity;
170
+ renderer attachments are keyed by that value so clients with identical local
171
+ request IDs cannot collide. The renderer echoes both values when publishing.
172
+ `snapshot.core.activeRequests[].id` and diagnostic `frame.id` use internal Core
173
+ identities. They can differ from client-visible IDs under connection routing.
174
+ None of these observation/transport IDs is a model-operation ID.
175
+
176
+ Core supplies both metadata fields for direct runtime calls, stdio connections,
177
+ `startCoreListener()` connections and the existing `startSharedCoreHost()` path.
178
+ Each connection preserves the original incoming request ID as `clientRequestId`
179
+ independently of its established `requestId` mapping. Direct calls use the
180
+ incoming frame ID for both identities. Observation requires these actual
181
+ context fields; it does not infer them from `requestId` or rewrite event
182
+ payloads to manufacture correlation.
183
+
184
+ The exact `methods` keys on the supplied native service definitions select the
185
+ Core operations whose live failures can be observed. The observer tracks their
186
+ actual active request IDs while a watch exists. Core publishes request removal
187
+ before emitting its response; the observer retains that correlation until the
188
+ response arrives, then removes it. Removing the final watcher releases frame
189
+ observation and remaining transient correlation. It retains no failure history.
190
+
191
+ ## Evidence and lifecycle limits
192
+
193
+ - AIRuntimeState exposes its existing llm/stt/tts records, including the reported
194
+ operation ID. Its retained errors contain only code/message. Concurrent role
195
+ work is not a complete request enumeration in this snapshot.
196
+ - The existing image accessor exposes its own active method, stream ID, status
197
+ and progress records. Its retained error is preserved; that error has no
198
+ reliable explicit stream association. Constructor-only diagnostic callbacks
199
+ cannot be retroactively attached through this API.
200
+ - Browser and native decision snapshots retain their existing errors and active
201
+ request counts. A count does not supply missing per-evaluation IDs.
202
+ - ModelController status exposes its existing state/progress and code/message
203
+ error. Its load/unload event IDs are not retained by `status()`, and are not
204
+ presented as inference IDs or reconstructed history here.
205
+ - Core supplies its real active request identities and complete live failed
206
+ response frames. A watch cannot recover responses that settled before it
207
+ observed them. Retained owner errors remain available in current snapshots.
208
+
209
+ Renderer `Error` values in owner error fields use the existing Core error
210
+ serializer at the JSON boundary; other owner fields remain unchanged. A value
211
+ the selected transport cannot encode fails through the observation error path
212
+ rather than being replaced with shortened or invented content. Observation
213
+ does not alter model inputs, outputs, prompts or their persistence policy.
214
+
215
+ The shared JavaScript contract is portable wherever the existing Core client
216
+ and supplied owners are available. Endpoint and native-host availability belong
217
+ to their documented adapters. Developer observation remains separate from
218
+ ordinary user status, app-owned stage/avatar association and actual model
219
+ execution verification. This capability does not establish that an inference
220
+ failure has been corrected.
@@ -55,6 +55,8 @@ runtime layouts.
55
55
  | `arcane-os/core/contracts` | Shared Core protocol, frame, method and diagnostic error contracts. |
56
56
  | `arcane-os/core/classic-source` | Node generator for the canonical classic-script Core client projection. |
57
57
  | `arcane-os/core/runtime` | App-neutral native Core dispatcher and service lifecycle. |
58
+ | `arcane-os/core/model-observation` | Passive observation of an existing Core and its actual native model service owners. |
59
+ | `arcane-os/ai/model-observation` | Attach existing renderer model owners and subscribe through an existing Core client. |
58
60
  | `arcane-os/core/development` | App-service composition and document-owned Core transport during source development, independently of local AI. |
59
61
  | `arcane-os/core/stdio` | Framed Node stdio transport with graceful runtime drain. |
60
62
  | `arcane-os/core/host` | Node-only reusable Core host lifecycle, app-selected native launch defaults and shared native location resolution. |
@@ -420,6 +422,9 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
420
422
  | `McpProtocolError` | class | `arcane-os/mcp/stdio` | MCP STDIO | Node |
421
423
  | `createCoreClassicSource()` | function | `arcane-os/core/classic-source` | Core classic client projection | Node; generated source runs in browser and native WebView renderers |
422
424
  | `createCoreRuntime()` | function | `arcane-os/core/runtime` | Native Core runtime | Node on Windows, Linux and macOS native hosts; Android requires host adaptation |
425
+ | `createModelObservationService()` | function | `arcane-os/core/model-observation` | Existing model observation | Node with an existing Core runtime and optional existing native model services |
426
+ | `attachModelObservation()` | function | `arcane-os/ai/model-observation` | Existing model observation | JavaScript with an existing Core client and optional existing renderer owners |
427
+ | `subscribeModelObservation()` | function | `arcane-os/ai/model-observation` | Existing model observation | JavaScript with an existing Core client and composed observation service |
423
428
  | `encodeCoreFrame()` | function | `arcane-os/core/stdio` | Native Core stdio transport | Node |
424
429
  | `createCoreFrameDecoder()` | function | `arcane-os/core/stdio` | Native Core stdio transport | Node |
425
430
  | `startCoreStdio()` | function | `arcane-os/core/stdio` | Native Core stdio transport | Node |
@@ -7395,13 +7400,17 @@ Import it from `arcane-os/core/runtime`. The runtime exposes `current()`,
7395
7400
  `subscribe()`, `onFrame()`, `emit()`, `registerService()`, `getService()`, `start()`, `handle()`
7396
7401
  and idempotent `close()`.
7397
7402
 
7398
- `runtime.handle(frame, {contextRequestId})` optionally preserves a transport's
7399
- original client ID in handler `context.requestId`, while `context.coreRequestId`
7400
- always exposes the actual internal `frame.id`. Active requests, cancellation,
7401
- response IDs and request-event envelopes retain that internal ID. Ordinary
7402
- `handle(frame)` exposes both context IDs equal to `frame.id`. This metadata
7403
- does not change parameters or event data; transports own client-facing protocol
7404
- correlation. See [the runtime contract](core-runtime.md#state-and-frames).
7403
+ `runtime.handle(frame, {contextRequestId, contextClientRequestId})` accepts
7404
+ optional nonempty transport metadata strings. Handler `requestId` remains
7405
+ `contextRequestId ?? frame.id`; additive `clientRequestId` is
7406
+ `contextClientRequestId ?? contextRequestId ?? frame.id`; `coreRequestId` always
7407
+ exposes the actual internal `frame.id`. Direct `handle(frame)` exposes all three
7408
+ equal. Stdio and both IPC paths supply the actual client identity separately,
7409
+ preserving the owning shared-host adapter's established internal `requestId`.
7410
+ Active requests, cancellation, response IDs and request-event envelopes retain
7411
+ the internal ID. Parameters, results and event data stay unchanged; transports
7412
+ own client-facing protocol correlation. See
7413
+ [the runtime contract](core-runtime.md#state-and-frames).
7405
7414
 
7406
7415
  `await runtime.getService(name)` and service `context.getService(name)` return
7407
7416
  the actual registered service after its shared startup. They wait for that
@@ -7588,6 +7597,80 @@ const transport = startCoreStdio({runtime});
7588
7597
  await transport.closed;
7589
7598
  ```
7590
7599
 
7600
+ ## createModelObservationService()
7601
+
7602
+ ```text
7603
+ createModelObservationService({runtime,localAI=null,image=null,decisions=null}={})
7604
+ ```
7605
+
7606
+ Import from `arcane-os/core/model-observation`. Supply the existing Core runtime
7607
+ and actual native service definitions, including their `current()` and `methods`.
7608
+ Returns `{name:'model-observation',current,dispose,methods}`. Register it on that
7609
+ runtime after ordinary host creation and before opening an additional listener.
7610
+ Construction starts no service, model, worker or readiness wait.
7611
+
7612
+ The four methods are `model.observation.current`, `model.observation.watch`,
7613
+ `model.observation.renderer.attach` and `model.observation.renderer.publish`.
7614
+ `current()` returns `{core,native:{localAI,image,decisions},renderers}` from the
7615
+ supplied owners. Absent model owners are `null`; absent attachments produce `[]`.
7616
+ The watch replays current state and remains pending until cancellation. Its
7617
+ `diagnostics:false` default excludes live failure frames; explicit `true`
7618
+ observes complete failed Core responses for the supplied owners' method keys.
7619
+ It retains no failure history and invents no model-operation identities.
7620
+
7621
+ Renderer attachment and watch acknowledgements use actual `clientRequestId`;
7622
+ attachments are keyed by the actual `coreRequestId` to distinguish clients with
7623
+ equal local IDs. `dispose()` retires observation requests/subscriptions only.
7624
+ Core still owns model services and shutdown. See
7625
+ [complete methods, events and evidence limits](model-observation.md).
7626
+
7627
+ ## attachModelObservation()
7628
+
7629
+ ```text
7630
+ attachModelObservation({client,aiRuntimeState=null,imageRuntime=null,decisionModel=null,modelController=null,signal,onError}={})
7631
+ ```
7632
+
7633
+ Import from `arcane-os/ai/model-observation`. Pass the existing Core client and
7634
+ the application-owned instances already doing the work. Supported owner methods
7635
+ are AIRuntimeState's `getAIRuntimeState/subscribeAIRuntimeState`, image runtime's
7636
+ `current/subscribe`, decision model's `status/subscribe`, and ModelController's
7637
+ `status/on`. It creates no replacement owner or transport.
7638
+
7639
+ Returns `{ready,closed,current,close}`. `ready` resolves with the Core-supplied
7640
+ `{attachmentRequestId,coreRequestId}` acknowledgement. Subscriptions then publish
7641
+ complete current owner snapshots, including a fresh read after acknowledgement.
7642
+ `current()` returns the latest renderer snapshot, initially `null`. The existing
7643
+ Core error serializer represents owner `Error` fields at the JSON boundary;
7644
+ other fields remain unchanged.
7645
+
7646
+ Caller abort, page retirement, retirement of the exact installed client,
7647
+ transport failure or `close()` retires only observation RPCs/subscriptions.
7648
+ `close()` returns `closed`; it never closes the supplied client or unloads models.
7649
+ Normal cancellation closes cleanly; cancellation before acknowledgement rejects
7650
+ `ready`. Other errors reject public promises and reach `onError`, defaulting to
7651
+ complete console diagnostics. A replacement client requires a new explicit
7652
+ attachment. Keep attachment readiness independent of page rendering. See
7653
+ [renderer ownership and lifecycle](model-observation.md#attach-existing-renderer-owners).
7654
+
7655
+ ## subscribeModelObservation()
7656
+
7657
+ ```text
7658
+ subscribeModelObservation({client,signal,onState,onDiagnostic,onError}={})
7659
+ ```
7660
+
7661
+ Import from `arcane-os/ai/model-observation`. Requires an existing client and
7662
+ `onState`; returns `{ready,closed,current,close}`. `ready` resolves with the first
7663
+ current-state snapshot and `current()` retains the latest snapshot, initially
7664
+ `null`. Providing `onDiagnostic` explicitly selects live complete correlated Core
7665
+ model failure frames; omission selects state only. These are developer
7666
+ diagnostics, not ordinary user status or saved conversation content.
7667
+
7668
+ The observer uses the same observation-only cancellation/error lifecycle as
7669
+ the attachment. Callback failures retire it and reach `onError`; callback
7670
+ promises may await `close()` without becoming its completion barrier. It starts
7671
+ no model, refresh, polling, endpoint discovery, host or reconnect. See
7672
+ [observation records and limits](model-observation.md#read-or-subscribe-from-an-existing-client).
7673
+
7591
7674
  ## createPortableNativeProvider()
7592
7675
 
7593
7676
  ### Overview
@@ -7621,6 +7704,13 @@ published SDK/runtime dependencies and the canonical Core entry/client into a
7621
7704
  fresh output directory. Service factories are imported only when a real native
7622
7705
  host later launches the generated Core entry, never during packaging.
7623
7706
 
7707
+ Explicit `native.launchContext.coreListener:{endpoint}` also composes one passive
7708
+ model observation service after `startCoreHost()` and before listener binding,
7709
+ using that runtime and existing services exposing `localai.status`, `image.status`
7710
+ and `decisions.status`. Missing owners remain `null`; renderer attachment stays
7711
+ application-owned. Ordinary stdio and shared/headless branches gain no model
7712
+ startup or readiness barrier. See [native observation composition](core-native-packaging.md#launch-time-locations).
7713
+
7624
7714
  An optional `appDescriptor.native.window` record carries `width`, `height` and
7625
7715
  `resizable` into `manifest.window`. Dimensions are positive integral logical
7626
7716
  client units; `resizable` is an optional boolean. Omitted fields remain omitted.
@@ -8848,7 +8938,7 @@ borrowed listener implements no `core.host.shutdown` operation; use client
8848
8938
  `close()` for disconnection and the existing runtime's owner for host shutdown.
8849
8939
 
8850
8940
  Each connection owns response/event correlation and request cancellation.
8851
- Handler `requestId` preserves the client ID; `coreRequestId` is the distinct
8941
+ Handler `requestId` and `clientRequestId` preserve the client ID; `coreRequestId` is the distinct
8852
8942
  internal dispatch ID. Parameters, results and event data, including
8853
8943
  `data.requestId`, remain unchanged. Runtime-state snapshots retain internal
8854
8944
  active IDs; no service event is invented or retained for later replay.
@@ -8867,6 +8957,9 @@ try {
8867
8957
  `host` is the application's existing stdio host. Generated native entries
8868
8958
  select this attachment with `native.launchContext.coreListener:{endpoint}`,
8869
8959
  preserving their actual runtime, service set, profile, origin and state root.
8960
+ That generated opt-in registers the passive observation service from existing
8961
+ native model owners before binding; direct `startCoreListener()` itself never
8962
+ registers services. Renderer owners are attached separately by the application.
8870
8963
  Omit `sharedHost`; the two options describe alternative lifetimes. Full
8871
8964
  contract: [existing-runtime listener](core-shared-host.md#attach-to-an-existing-window-owned-core).
8872
8965
 
@@ -8894,6 +8987,11 @@ service-owned. The supplied signal begins the same shutdown. `closed` rejects
8894
8987
  on shutdown failure; `onError` observes transport/background failures, with
8895
8988
  complete console diagnostics by default.
8896
8989
 
8990
+ The owning adapter preserves its existing internal handler `requestId`, equal
8991
+ to `coreRequestId`, and its matching `data.requestId` compatibility mapping.
8992
+ Additive `clientRequestId` exposes the original client ID independently; other
8993
+ parameters, results and event data remain unchanged.
8994
+
8897
8995
  ### Availability and example
8898
8996
 
8899
8997
  Node supports app-selected named pipes on Windows and Unix-domain sockets on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.84.0",
3
+ "version": "0.85.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -43,6 +43,7 @@
43
43
  "./ai/core-onnx": "./browser-runtime/ai/core-onnx.mjs",
44
44
  "./ai/core-image": "./browser-runtime/ai/core-image.mjs",
45
45
  "./ai/core-model-assets": "./browser-runtime/ai/core-model-assets.mjs",
46
+ "./ai/model-observation": "./browser-runtime/ai/model-observation.mjs",
46
47
  "./ai/browser-decisions": "./browser-runtime/ai/browser-decisions.mjs",
47
48
  "./ai/tool-text-stream": "./browser-runtime/ai/tool-text-stream.mjs",
48
49
  "./ai/twin-cloud": "./browser-runtime/ai/twin-cloud.mjs",
@@ -75,6 +76,7 @@
75
76
  "./core/image": "./src/core/services/image.mjs",
76
77
  "./core/speech": "./src/core/services/speech.mjs",
77
78
  "./core/model-assets": "./src/core/services/model-assets.mjs",
79
+ "./core/model-observation": "./src/core/services/model-observation.mjs",
78
80
  "./diarization": "./src/diarization/index.mjs",
79
81
  "./diarization/build": "./src/diarization/build.mjs",
80
82
  "./local-ai": "./src/local-ai/install.mjs",
@@ -137,7 +137,10 @@ export function createCoreRuntimeConnection({
137
137
  try {
138
138
  return await runtime.handle(
139
139
  {...frame, id: record.internalId},
140
- preserveContextRequestId ? {contextRequestId: record.id} : undefined
140
+ {
141
+ contextRequestId: preserveContextRequestId ? record.id : undefined,
142
+ contextClientRequestId: record.id
143
+ }
141
144
  );
142
145
  } finally {
143
146
  releaseRequest();
@@ -218,7 +218,7 @@ class CoreRuntime {
218
218
  return this.current();
219
219
  }
220
220
 
221
- async handle(frame, {contextRequestId} = {}) {
221
+ async handle(frame, {contextRequestId, contextClientRequestId} = {}) {
222
222
  if (frame?.protocol !== CORE_PROTOCOL) throw failure('INVALID_RPC_REQUEST', 'Unknown Core protocol.');
223
223
  if (frame.type === 'control') {
224
224
  if (frame.control === 'runtime.replay') {
@@ -238,7 +238,10 @@ class CoreRuntime {
238
238
  if (contextRequestId !== undefined && (!is.string(contextRequestId) || !contextRequestId)) {
239
239
  throw failure('INVALID_RPC_REQUEST', 'A transport context request id must be a nonempty string.');
240
240
  }
241
- const task = this.#answer(frame, contextRequestId);
241
+ if (contextClientRequestId !== undefined && (!is.string(contextClientRequestId) || !contextClientRequestId)) {
242
+ throw failure('INVALID_RPC_REQUEST', 'A transport client request id must be a nonempty string.');
243
+ }
244
+ const task = this.#answer(frame, contextRequestId, contextClientRequestId);
242
245
  this.#responses.add(task);
243
246
  try {
244
247
  return await task;
@@ -247,7 +250,7 @@ class CoreRuntime {
247
250
  }
248
251
  }
249
252
 
250
- async #answer(frame, contextRequestId) {
253
+ async #answer(frame, contextRequestId, contextClientRequestId) {
251
254
  let result;
252
255
  let error;
253
256
  let ok = true;
@@ -257,7 +260,7 @@ class CoreRuntime {
257
260
  if (frame.method === 'system.ping') result = {ok: true};
258
261
  else if (frame.method === 'version.current') result = this.#version;
259
262
  else if (frame.method === 'app.current') result = this.#application;
260
- else result = await this.#request(frame, contextRequestId);
263
+ else result = await this.#request(frame, contextRequestId, contextClientRequestId);
261
264
  } catch (caught) {
262
265
  ok = false;
263
266
  error = caught;
@@ -273,7 +276,7 @@ class CoreRuntime {
273
276
  return response;
274
277
  }
275
278
 
276
- #request(frame, contextRequestId) {
279
+ #request(frame, contextRequestId, contextClientRequestId) {
277
280
  const registration = this.#methods.get(frame.method);
278
281
  if (!registration) {
279
282
  const namespace = frame.method.split('.')[0];
@@ -303,8 +306,13 @@ class CoreRuntime {
303
306
  const result = await handle.call(
304
307
  service.definition,
305
308
  frame.parameters,
306
- {...runtime.#serviceContext(service, frame.id),
307
- requestId: contextRequestId ?? frame.id, coreRequestId: frame.id, signal: controller.signal}
309
+ {
310
+ ...runtime.#serviceContext(service, frame.id),
311
+ requestId: contextRequestId ?? frame.id,
312
+ clientRequestId: contextClientRequestId ?? contextRequestId ?? frame.id,
313
+ coreRequestId: frame.id,
314
+ signal: controller.signal
315
+ }
308
316
  );
309
317
  aborted(controller);
310
318
  return result;