@plurnk/plurnk-service 1.3.4 → 1.3.6
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/INSTALL.md +3 -1
- package/SPEC.md +116 -12
- package/dist/build-info.json +1 -1
- package/dist/core/Engine.d.ts +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +14 -10
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +6 -8
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +7 -39
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/fork.js +2 -2
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +3 -3
- package/dist/digest/Digest.js +6 -6
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +2 -2
- package/dist/schemes/exec-env.d.ts +0 -1
- package/dist/schemes/exec-env.d.ts.map +1 -1
- package/dist/schemes/exec-env.js +13 -9
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/server/Daemon.d.ts +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +1 -1
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/envelope.d.ts +2 -2
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.sql +2 -2
- package/migrations/0000-00-00.01_schema.sql +13 -13
- package/package.json +16 -19
package/INSTALL.md
CHANGED
|
@@ -63,7 +63,9 @@ Each mirrors a `# --- section ---` in the floor; consult the floor for exact def
|
|
|
63
63
|
- **Loop control / Engine rails** — `PLURNK_SERVICE_MAX_TURNS` (−1 = uncapped), `_MAX_COMMANDS`, `_MAX_STRIKES`, `_MIN_CYCLES`, `_MAX_CYCLE_PERIOD`, `_RPC_TIMEOUT`, `_LOOP_TIMEOUT`, `_PROPOSAL_TIMEOUT_MS`, `_EXEC_WAIT_MS`, `_EXEC_KILL_GRACE_MS`, `_SESSION_RUNS_MAX_ACTIVE`. Guardrails; the shipped values are sane.
|
|
64
64
|
- **Git** — `PLURNK_SERVICE_GIT_ALLOWED` (0 = hard sandbox lockout), `_GIT_AUTO`.
|
|
65
65
|
- **Packet / reference docs** — `PLURNK_SERVICE_FILES_ITEMS` (turn-1 file catalog cap), `_PROMPT_PREVIEW_CHARS`, `_DOCS_EXCLUDE`, `_PACKET_INJECT` (operator markdown section), `_POLICY`/`_PROJECT`/`_REQUIREMENTS` (policy + footer overrides; unset = the seeded/packaged defaults), `_MD_<alias>` (inject a markdown doc as a turn-0 entry).
|
|
66
|
-
- **Providers** —
|
|
66
|
+
- **Providers** — the portable provider knobs and defaults are defined by
|
|
67
|
+
`@plurnk/plurnk-providers/.env.defaults`; any provider knob may take an alias
|
|
68
|
+
suffix that wins over the bare fallback.
|
|
67
69
|
- **The window partition** — `PLURNK_SERVICE_CONTEXT_WINDOW`/`_REASONING`/`_ASSISTANT`/`_SAFETY` (see Couplings).
|
|
68
70
|
- **Plugins** — bare `PLURNK_PLUGINS_TRUSTED_ONLY` (0/unset = load all installed; a value = `@plurnk/*` plus an allowlist).
|
|
69
71
|
- **Semantic search** — `PLURNK_SERVICE_SEMANTIC_TOP_K` (markerless result count), `_SEMANTIC_CHUNK_TOKENS`/`_CHUNK_OVERLAP` (service-side chunking), `PLURNK_SERVICE_EMBED_DISABLE` (FTS-only), `PLURNK_MIMETYPES_EMBED_WORKERS` (the embedder's pool — mimetypes-owned).
|
package/SPEC.md
CHANGED
|
@@ -96,11 +96,11 @@ Dependency direction (from root to leaf):
|
|
|
96
96
|
|
|
97
97
|
- **`plurnk-grammar`** — root. Owns the JSON-Schema contracts (Packet, TelemetryEvent, AST shapes), the ANTLR parser that turns model output into `PlurnkStatement[]`, and `PlurnkParseError` with its `toTelemetryEvent()` helper. Nothing in the ecosystem can speak the DSL without it; everything else pins it exactly.
|
|
98
98
|
- **Framework siblings** consume grammar and define their own author-facing contracts:
|
|
99
|
-
- `plurnk-providers` — Provider/Alias types,
|
|
99
|
+
- `plurnk-providers` — Provider/Alias types, AI SDK adaptation, Models.dev-backed provider resolution, local endpoint probes, `Mock`, and normalized usage. Ordinary vendor protocols belong to official AI SDK providers; only genuinely new protocol bindings require a provider plugin.
|
|
100
100
|
- `plurnk-mimetypes` — handler base classes, discovery, the fitting algorithm, and the match primitives (`queryGlob`/`queryRegex`/`queryJsonpathObject`/`queryXpathString`) the service's matcher dispatches over (§matcher-dispatch). Handler children are per-mimetype: `plurnk-mimetypes-text-{python,typescript,markdown,html,csv,plain}`, `plurnk-mimetypes-application-{json,yaml,toml,pdf}`, …
|
|
101
101
|
- `plurnk-schemes` — scheme-author types (`SchemeManifest`, `WriterTier`, `LoopFlags`), result-shape contracts (`EntryResult` / `ProposalResult` / `PassthroughResult`), slicing primitives, matcher helpers, `schemeError(...)` constructor. Future scheme children: `plurnk-schemes-http`, `plurnk-schemes-git`, …
|
|
102
102
|
- `plurnk-execs` — `BaseExecutor`, `SubprocessExecutor`, runtime resolver, discovery. Children declare runtimes: `plurnk-execs-sh`, future `plurnk-execs-search`, `plurnk-execs-node`, …
|
|
103
|
-
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), discovers installed mimetype handlers
|
|
103
|
+
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), resolves model providers, discovers installed mimetype handlers and executor siblings at boot, and exposes an in-process seam to client-interface modules. Packet assembly is service-owned (§packet).
|
|
104
104
|
- **`plurnk`** (client) — terminal UI consuming the AG-UI+ client surface. Renders daemon events and contains no engine logic.
|
|
105
105
|
|
|
106
106
|
The grammar is the contract. The frameworks consume the contract and add author-facing surfaces. The service consumes the frameworks and runs the engine. The client consumes the service and renders to humans. Each tier is its own published package; each tier's evolution happens in its own repo.
|
|
@@ -217,6 +217,102 @@ stateDiagram-v2
|
|
|
217
217
|
|
|
218
218
|
The lifecycle store admits only the guarded transitions shown above: `100 → 102`, `102 → 202`, `202 → 100`, and any unresolved state (`100`, `102`, `202`) to a terminal status. Terminal state is immutable. The queue drain owns claim, the dispatcher owns model-requested park/conclusion, the daemon owns wake/cancellation/restart reconciliation, and the engine owns policy terminals. A racing transition that loses observes the durable winner; it does not overwrite it or report the requested state as fact. {§worker-lifecycle-state-machine}
|
|
219
219
|
|
|
220
|
+
Streams are independently durable subscriptions owned by a worker. Payload and
|
|
221
|
+
lifecycle are orthogonal: zero bytes is a valid payload for both success and
|
|
222
|
+
failure, while the closed subscription and its status are the terminal fact.
|
|
223
|
+
|
|
224
|
+
```mermaid
|
|
225
|
+
stateDiagram-v2
|
|
226
|
+
[*] --> Open: executor registers subscription
|
|
227
|
+
Open --> Open: append chunk
|
|
228
|
+
Open --> Closed: driver succeeds
|
|
229
|
+
Open --> Failed: driver fails
|
|
230
|
+
Open --> Cancelled: worker or stream is cancelled
|
|
231
|
+
Closed --> Observed: terminal delta enters a packet
|
|
232
|
+
Failed --> Observed: terminal delta enters a packet
|
|
233
|
+
Cancelled --> Observed: terminal delta enters a packet
|
|
234
|
+
Observed --> [*]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Subscription state at `SEND[202]` | Terminal observation already in a packet | Result |
|
|
238
|
+
|---|---:|---|
|
|
239
|
+
| open | no | park; polling or closure may wake it |
|
|
240
|
+
| closed, any status, empty or non-empty | no | continue directly to the observation turn |
|
|
241
|
+
| closed, any status, empty or non-empty | yes | no stream obligation remains |
|
|
242
|
+
| cancelled as part of worker cancellation | irrelevant | terminate the cancelled worker; never resurrect it |
|
|
243
|
+
|
|
244
|
+
{§worker-lifecycle-subscription-matrix}
|
|
245
|
+
|
|
246
|
+
Polling observes a still-open stream; it never changes ownership or manufactures
|
|
247
|
+
completion. Closure is always a wake edge regardless of polling mode.
|
|
248
|
+
|
|
249
|
+
| EXEC poll marker | While open | On closure |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| omitted | exponential-backoff observation wakes | resume once with terminal observation |
|
|
252
|
+
| positive `P` | fixed-cadence observation wakes every `P` seconds | cancel cadence; resume once |
|
|
253
|
+
| zero | no observation wakes | resume once |
|
|
254
|
+
| turn-scoped `<0>` | reap at the next pre-turn boundary | surface the terminal outcome |
|
|
255
|
+
|
|
256
|
+
{§worker-lifecycle-poll-matrix}
|
|
257
|
+
|
|
258
|
+
The structured-concurrency sequence is identical whether a child performs an
|
|
259
|
+
EXEC, retrieval, or pure inference. Intermediate child status is private to the
|
|
260
|
+
child. Only the child's terminal loop result crosses the parent edge.
|
|
261
|
+
|
|
262
|
+
```mermaid
|
|
263
|
+
sequenceDiagram
|
|
264
|
+
participant P as Parent loop
|
|
265
|
+
participant C as Child loop
|
|
266
|
+
participant S as Child stream
|
|
267
|
+
P->>C: WORK or FORK
|
|
268
|
+
P->>P: SEND[202] parks on live child
|
|
269
|
+
C->>S: EXEC opens subscription
|
|
270
|
+
C->>C: SEND[202] parks on live stream
|
|
271
|
+
loop backoff, fixed cadence, or explicit arrival
|
|
272
|
+
S-->>C: optional progress observation
|
|
273
|
+
C->>C: continue or park
|
|
274
|
+
end
|
|
275
|
+
S-->>C: terminal transition
|
|
276
|
+
C->>C: terminal delta enters packet
|
|
277
|
+
C->>P: child SEND terminal becomes collect delta
|
|
278
|
+
P->>P: resume same parked loop
|
|
279
|
+
P->>P: observe child result and continue
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
| Child state at parent wait | Child result delivered to parent | Result |
|
|
283
|
+
|---|---:|---|
|
|
284
|
+
| queued, running, or parked on its own live obligation | no | parent parks |
|
|
285
|
+
| terminal during the parent's turn | no | owed wake; parent continues to the result packet |
|
|
286
|
+
| terminal before the parent's wait | no | parent continues directly to the result packet |
|
|
287
|
+
| terminal | yes | child obligation is drained |
|
|
288
|
+
| cancelled or failed terminal | no | same wake/delivery path as success; outcome remains non-2xx |
|
|
289
|
+
|
|
290
|
+
{§worker-lifecycle-child-matrix}
|
|
291
|
+
|
|
292
|
+
A stream's close status and a loop's terminal status are separate layers. A
|
|
293
|
+
stream may close 4xx/5xx and wake its worker to recover. Model `SEND[4xx/5xx]`
|
|
294
|
+
reports a failed action and continues; `SEND[200]` concludes successfully and
|
|
295
|
+
`SEND[499]` explicitly abandons the worker. Only a concluded loop crosses the
|
|
296
|
+
parent edge as the child's result.
|
|
297
|
+
|
|
298
|
+
```mermaid
|
|
299
|
+
flowchart TD
|
|
300
|
+
W[Worker cancellation] --> L[Terminalize unresolved loops]
|
|
301
|
+
W --> C[Cancel descendants]
|
|
302
|
+
W --> S[Enumerate durable open subscriptions]
|
|
303
|
+
S --> H[Invoke each live cancellation handle]
|
|
304
|
+
H --> T[Persist terminal channel and subscription state]
|
|
305
|
+
T --> N[Publish conclusion without resurrection]
|
|
306
|
+
C --> L
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Restart applies the same ownership rule: accepted queued loops are reclaimable;
|
|
310
|
+
in-flight provider calls and subscriptions belonged to the vanished process and
|
|
311
|
+
become explicit failures. A park whose child remains live stays parked; a park
|
|
312
|
+
whose obligations settled during reconciliation is requeued in place so it can
|
|
313
|
+
observe their terminal results. No effect is replayed across an unknown
|
|
314
|
+
boundary.
|
|
315
|
+
|
|
220
316
|
- **One drain advances a worker.** At most one drain is registered for a worker at any instant: a `loop.run` or wake on a worker with a live drain folds in (active→next-turn) or enqueues a loop that drain claims, never a second parallel drain. A drain's start and its empty-queue teardown relinquish the worker under one per-worker lock, so the teardown's re-claim cannot race a concurrent start into a double-drain. {§worker-lifecycle-single-drain}
|
|
221
317
|
- **Cancellation is recursive and reaps every held stream.** `loop.cancel`, worker `KILL`, shutdown, and a worker's `SEND[499]` terminalize every unresolved loop in the cancelled worker subtree and iterate each worker's durable open-subscription rows, invoking each exact callable owner from the process-local live registry. The durable rows answer *what is held*; the live registry answers *how this process tears it down*; the abort signal is a fast-path optimization. There is no implicit detachment. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. The teardown abort is bounded: the executor sends a polite signal then SIGKILL after a consumer-set grace (`PLURNK_SERVICE_EXEC_KILL_GRACE_MS`). A model `KILL[code]` on one live stream instead delivers exactly that signal once (bare `KILL` → the executor's SIGHUP default, `KILL[9]` → SIGKILL). {§worker-lifecycle-total-reap}
|
|
222
318
|
- **A stream's kill binds to the scope it captured at spawn.** A stream captures the worker's cancellation scope as it registers and wires its kill to it, re-checking `aborted` AFTER wiring — no check-then-listen gap can drop an abort that lands mid-registration. Because the scope is replaced only once aborted, a captured-then-replaced scope is necessarily already aborted, so replacement never strands a live stream. {§worker-lifecycle-exec-epoch-bound}
|
|
@@ -239,11 +335,11 @@ Three entry points:
|
|
|
239
335
|
|
|
240
336
|
- `provider.generate({messages, signal})` — once per turn; returns `{ assistant: { content, reasoning, usage, finishReason, model }, assistantRaw, meta? }`. **Engine parses `assistant.content`** into `PlurnkStatement[]` via `@plurnk/plurnk-grammar`. {§provider-surface-generate}
|
|
241
337
|
- `provider.countTokens(text)` — synchronous, called at write-time (§tokenomics) and render-time. Non-negative integer. {§provider-surface-counttokens}
|
|
242
|
-
- `provider.
|
|
338
|
+
- `provider.calculateCost(usage)` — once per completed turn; estimated USD. Engine writes to `turns.usage_cost_usd`; triggers cascade to `workers.cost_usd` / `workspaces.cost_usd`. {§provider-surface-calculate-cost}
|
|
243
339
|
|
|
244
340
|
Plus immutable identity: `provider.contextWindow` (token total, or `null` → "no budget info"), read by the budget {§provider-surface-identity}; and `provider.model` — the instance identity the deferred model-switch recompute compares (§tokenomics), exposed but not yet consumed here.
|
|
245
341
|
|
|
246
|
-
**Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag
|
|
342
|
+
**Metadata passthrough (provider → client).** `generate` may return an open `meta: Record<string, unknown>` bag. The service stores it unenforced per turn (`turns.meta`, `json_valid` only — no schema) and forwards the latest turn's blob to the client on the loop usage payload (`loop.run` result / `loop/terminated`, §methods). The service never reads a field within it. Providers own their metadata shapes; monetary values carry an explicit amount and currency rather than an implied unit. Absent → `{}`. The mirror direction (client → provider, the self-identified `client` id) rides `generate({client})` (§attribution). {§meta-passthrough}
|
|
247
343
|
|
|
248
344
|
### §provider-guarantees Engine → provider guarantees
|
|
249
345
|
|
|
@@ -273,7 +369,13 @@ Deferred (#249): grounding the attribution value in real per-turn value flow rat
|
|
|
273
369
|
|
|
274
370
|
### §provider-instantiation Provider instantiation
|
|
275
371
|
|
|
276
|
-
Model alias parsing
|
|
372
|
+
Model alias parsing and provider construction live in
|
|
373
|
+
[`@plurnk/plurnk-providers`](https://github.com/plurnk/plurnk-providers).
|
|
374
|
+
`src/core/ProviderInstantiate.ts` delegates to that owner and adds only
|
|
375
|
+
service-side caching, per-loop selection, context-cap handling, and local GBNF
|
|
376
|
+
verification. Cataloged providers use Models.dev metadata and official AI SDK
|
|
377
|
+
bindings; an operator declaration covers an uncataloged compatible endpoint;
|
|
378
|
+
plugin discovery is the last protocol-extension seam. {§provider-instantiation-alias-resolution}
|
|
277
379
|
|
|
278
380
|
**Optional local GBNF is verified at boot.** {§grammar-enforcement-verified-at-boot}
|
|
279
381
|
The ANTLR grammar always defines and validates the PLURNK language. Separately,
|
|
@@ -296,7 +398,7 @@ PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
|
|
|
296
398
|
PLURNK_MODEL=gemma
|
|
297
399
|
```
|
|
298
400
|
|
|
299
|
-
First path segment = provider
|
|
401
|
+
First path segment = provider name; rest = provider-native model id.
|
|
300
402
|
|
|
301
403
|
### §mock-provider Mock provider (sibling fixture)
|
|
302
404
|
|
|
@@ -812,7 +914,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
|
|
|
812
914
|
**Lives in SQL:**
|
|
813
915
|
- Render queries — log assembly + the manifest catalog.
|
|
814
916
|
- Cross-scope path collision (CHECK/trigger → 409).
|
|
815
|
-
- Cost rollups (denormalized
|
|
917
|
+
- Cost rollups (denormalized USD; atomic on turn close).
|
|
816
918
|
- Sequence number issuance (1-based per grammar).
|
|
817
919
|
- Entry-vs-log integrity.
|
|
818
920
|
|
|
@@ -837,10 +939,10 @@ Scoped-package scan with manifest field:
|
|
|
837
939
|
|
|
838
940
|
1. Each package declares its kind:
|
|
839
941
|
```json
|
|
840
|
-
{ "name": "@
|
|
841
|
-
"plurnk": { "kind": "provider", "name": "
|
|
942
|
+
{ "name": "@acme/ai-provider",
|
|
943
|
+
"plurnk": { "kind": "provider", "name": "acme" } }
|
|
842
944
|
```
|
|
843
|
-
2. Boot scans
|
|
945
|
+
2. Boot scans installed scoped and unscoped packages, filters by `plurnk` field, and dynamic-imports matches.
|
|
844
946
|
3. Load order: deterministic alphabetical.
|
|
845
947
|
4. Collision on `(kind, name)` is fail-hard.
|
|
846
948
|
5. Operator flow: `npm i @plurnk/plurnk-<kind>-<name> && plurnk start`. Zero config.
|
|
@@ -855,7 +957,9 @@ Env vars configure installed plugins; never declare existence. Filesystem is the
|
|
|
855
957
|
|
|
856
958
|
Plugin discovery (§plugin-discovery) registers whatever's in `node_modules/@plurnk/*`.
|
|
857
959
|
|
|
858
|
-
**Providers
|
|
960
|
+
**Providers:** `@plurnk/plurnk-providers` resolves the Models.dev catalog,
|
|
961
|
+
operator declarations, local adapters, and finally installed AI SDK provider
|
|
962
|
+
plugins. `Mock` is its integration fixture. Core contains no vendor protocol.
|
|
859
963
|
|
|
860
964
|
**Mimetypes in-tree:** none. Framework + handlers are all siblings.
|
|
861
965
|
|
|
@@ -1322,7 +1426,7 @@ type Packet = {
|
|
|
1322
1426
|
|
|
1323
1427
|
The wire projection (`PacketWire.renderSlot`) groups sections by slot into the system + user ChatMessages; the digest re-renders the same stored sections byte-for-byte.
|
|
1324
1428
|
|
|
1325
|
-
**The lexicon has two standards, split by audience.** {§lexicon} The OPERATOR/WIRE/STORAGE layers follow the industry lexicon — where a concept is standard, its standard name (OpenAI's for provider quantities: `contextWindow`, `reasoning`, `completion`, `finish_reason`, usage nouns). The MODEL-FACING packet follows the training distribution — ops mirror HTTP/shell, `display` mirrors CSS, jsonplurnk is JSON — because that is the standard the model has actually read; renaming packet vocabulary to API-speak trades load-bearing resonance for a standard the model never sees at that layer. A standard name on a NON-standard concept is the worst case (a label that lies with credentials): `promptBudget` is deliberately not `contextWindow` because it holds window-minus-reserves. **Deliberate keeps** (each a plurnk-native concept with no honest standard name): `worker`/`loop`/`turn` — the OS process paradigm; the industry's countable ("run") was retired EMPTY and the client seam translates `loop` → AG-UI RUN events; `packet` — the assembled address space, a kernel concept, not a "request"; `
|
|
1429
|
+
**The lexicon has two standards, split by audience.** {§lexicon} The OPERATOR/WIRE/STORAGE layers follow the industry lexicon — where a concept is standard, its standard name (OpenAI's for provider quantities: `contextWindow`, `reasoning`, `completion`, `finish_reason`, usage nouns). The MODEL-FACING packet follows the training distribution — ops mirror HTTP/shell, `display` mirrors CSS, jsonplurnk is JSON — because that is the standard the model has actually read; renaming packet vocabulary to API-speak trades load-bearing resonance for a standard the model never sees at that layer. A standard name on a NON-standard concept is the worst case (a label that lies with credentials): `promptBudget` is deliberately not `contextWindow` because it holds window-minus-reserves. **Deliberate keeps** (each a plurnk-native concept with no honest standard name): `worker`/`loop`/`turn` — the OS process paradigm; the industry's countable ("run") was retired EMPTY and the client seam translates `loop` → AG-UI RUN events; `packet` — the assembled address space, a kernel concept, not a "request"; `costUsd` — no standard cost field exists; US dollar integers avoid float cents; `PLURNK_SERVICE_SAFETY` — the ruler's packing margin, a service fact no provider owns; the `chars/2` ruler — model-agnostic BY DESIGN (§tokenomics-agnostic-ruler). Retired terms stay retired: the lexicon guard fails CI on `thinking`, the `session` noun, `contextSize`, `decodeBudget`, and the moved partition knob names — drift is caught at commit time, not the next audit. <!-- lexicon-allow: this section QUOTES the retired terms it bans -->
|
|
1326
1430
|
|
|
1327
1431
|
**Sealed reasoning rides the mirror row.** {§sealed-reasoning-carrier} A provider's ENCRYPTED reasoning — the OpenAI Responses reasoning-item LIST (`[{id, subtype, encrypted: [{data, format}]}]` — a turn can carry N distinct-id items; normalized by the provider tier: id + subtype from the wire, never synthesized) — lands VERBATIM in the model mirror row's `attrs.reasoning` — per turn, on the same `log/entry` broadcast and `readLog` read the client seam already consumes; agui projects one correlated `REASONING_ENCRYPTED_VALUE` span per item (#482). A cross-lane conformance test drives core's real write through agui's real Translator, so a future non-meeting seam is a red pre-push gate, not a shipped-broken main. The blobs are never decoded, never synthesized, and never rendered into a packet — the packet renderer reads no foreign attrs keys, so the model never pays tokens for ciphertext it cannot read. Readable text keeps riding `assistant.reasoning`; the two never mix.
|
|
1328
1432
|
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"package":"@plurnk/plurnk-service","version":"1.3.
|
|
1
|
+
{"package":"@plurnk/plurnk-service","version":"1.3.6","revision":"19e299ab4fd23ba077100e0426667f9f6404897c","dirty":false}
|
package/dist/core/Engine.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.js
CHANGED
|
@@ -365,7 +365,7 @@ export default class Engine {
|
|
|
365
365
|
}
|
|
366
366
|
// Per-loop usage totals (#197): SUM the loop's turns (usage is stored per
|
|
367
367
|
// turn, §tokenomics). Surfaced on loop.run + loop/terminated so clients render real
|
|
368
|
-
// token/cost numbers.
|
|
368
|
+
// token/cost numbers. costUsd is the stored USD unit.
|
|
369
369
|
// #345 — the client-facing budget denominator, ONE meaning on every surface: the prompt
|
|
370
370
|
// budget the packet actually lives under (effective window minus the partition reserves),
|
|
371
371
|
// the same number loop-usage stores per turn. providers.list advertised the raw KV and the
|
|
@@ -390,7 +390,7 @@ export default class Engine {
|
|
|
390
390
|
return {
|
|
391
391
|
promptTokens: row?.prompt ?? 0,
|
|
392
392
|
completionTokens: row?.completion ?? 0,
|
|
393
|
-
|
|
393
|
+
costUsd: row?.cost_usd ?? 0,
|
|
394
394
|
// #263 — the last turn's prompt tokens = current window occupancy (gauge numerator), NOT the
|
|
395
395
|
// summed promptTokens above, which overcounts a context that grows across turns.
|
|
396
396
|
contextTokens: row?.context ?? 0,
|
|
@@ -974,7 +974,7 @@ export default class Engine {
|
|
|
974
974
|
const hardPacket = this.#packets.completePacket(requestPacket, { content: "", ops: [], reasoning: null }, null, provider);
|
|
975
975
|
await this.#db.engine_close_turn.run({
|
|
976
976
|
id: turnId, status: 413, packet: JSON.stringify(hardPacket),
|
|
977
|
-
usage_prompt: 0, usage_completion: 0, usage_reasoning: 0, usage_cached: 0,
|
|
977
|
+
usage_prompt: 0, usage_completion: 0, usage_reasoning: 0, usage_cached: 0, usage_cost_usd: 0,
|
|
978
978
|
usage_prompt_budget: this.#packets.promptBudgetFor(provider), // #274 — the PROMPT BUDGET (window − reserves), even on a hard-413 turn: the gauge denominator the packet actually lives under
|
|
979
979
|
finish_reason: "budget_hard_stop", model: provider.model, meta: "{}",
|
|
980
980
|
});
|
|
@@ -1189,11 +1189,11 @@ export default class Engine {
|
|
|
1189
1189
|
usage_completion: usage.completion,
|
|
1190
1190
|
usage_reasoning: usage.reasoning,
|
|
1191
1191
|
usage_cached: usage.cached,
|
|
1192
|
-
|
|
1192
|
+
usage_cost_usd: provider.calculateCost(usage), // §provider-surface-calculate-cost
|
|
1193
1193
|
usage_prompt_budget: this.#packets.promptBudgetFor(provider), // #274 — the PROMPT BUDGET (window − reserves): the raw n_ctx overstated usable room by the reserve total
|
|
1194
1194
|
finish_reason: finishReason,
|
|
1195
1195
|
model,
|
|
1196
|
-
// #252 — opaque provider→client metadata passthrough (
|
|
1196
|
+
// #252 — opaque provider→client metadata passthrough (for example, the
|
|
1197
1197
|
// provider normalized), plus the ONE service-authored carve-out: the engine's rail
|
|
1198
1198
|
// keys ({§rail-truth-engine-verdict}) merge over any transitional provider railsMeta.
|
|
1199
1199
|
meta: JSON.stringify({ ...(response.meta ?? {}), ...(railKeys ?? {}) }),
|
|
@@ -1491,16 +1491,20 @@ export default class Engine {
|
|
|
1491
1491
|
// The cursor-terminal race (owner's dogfood find): a channel written in one final
|
|
1492
1492
|
// burst gets fully shown FOLDED while still active; the close then has zero new
|
|
1493
1493
|
// bytes and the auto-OPEN terminal delta never fired — the model was never shown
|
|
1494
|
-
// the conclusion of a stream whose result it already holds folded.
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
1497
|
-
|
|
1494
|
+
// the conclusion of a stream whose result it already holds folded. The same
|
|
1495
|
+
// observation is required when the stream produced zero bytes: completion is
|
|
1496
|
+
// information independently of payload. Emit the terminal marker ONCE: open,
|
|
1497
|
+
// terse, carrying the close status (§tokenomics-fetch-fits-free).
|
|
1498
|
+
if (closed && priorAttrs.terminal !== true) {
|
|
1499
|
+
const pointer = cursor > 0
|
|
1500
|
+
? `full output already delivered above; READ ${ch.runtime}://${ch.coord}${visibleFragment === null ? "" : `#${visibleFragment}`} to revisit`
|
|
1501
|
+
: "stream produced no output";
|
|
1498
1502
|
await this.#db.engine_insert_stream_delta.run({
|
|
1499
1503
|
worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence: fromSequence + written,
|
|
1500
1504
|
scheme: ch.runtime, pathname: ch.coord, fragment: visibleFragment,
|
|
1501
1505
|
rx: JSON.stringify({
|
|
1502
1506
|
status: ch.close_status ?? 200,
|
|
1503
|
-
content: `[ stream closed (${ch.close_status ?? 200}) —
|
|
1507
|
+
content: `[ stream closed (${ch.close_status ?? 200}) — ${pointer} ]`,
|
|
1504
1508
|
mimetype: "text/stream",
|
|
1505
1509
|
}),
|
|
1506
1510
|
attrs: JSON.stringify({ streamEnd: ch.content.length, terminal: true }),
|