@womp/spark-agent 3.0.0 → 3.0.2

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/README.md CHANGED
@@ -1,372 +1,372 @@
1
- # @womp/spark-agent
2
-
3
- Portable AI agent for Womp applications and Kakapo scenes with model-authored JavaScript.
4
-
5
- Spark Agent uses AI SDK 7 `UIMessage` and `UIMessageChunk` as its public chat protocol. Generated
6
- JavaScript uses the host's executor and capabilities. Native browser execution and isolated QuickJS
7
- execution use the same model loop. The host owns conversations, artifacts, authorization, and connections.
8
-
9
- ## Requirements
10
-
11
- - Node.js 22.13 or newer
12
- - AI SDK 7
13
- - `@ai-sdk/react` 4 for React clients
14
-
15
- ```bash
16
- npm install @womp/spark-agent
17
- ```
18
-
19
- ## Package entry points
20
-
21
- - `@womp/spark-agent` — browser-safe model loop, two tools, persistence hooks, and shared contracts
22
- - `@womp/spark-agent/browser` — native JavaScript executor and local chat transport
23
- - `@womp/spark-agent/capabilities` — capability composition and skill contracts
24
- - `@womp/spark-agent/kakapo` — direct Kakapo runtime and scene capability
25
- - `@womp/spark-agent/quickjs` — optional isolated JavaScript executor
26
- - `@womp/spark-agent/ui` — browser-safe UI message types, schemas, and model catalog
27
- - `@womp/spark-agent/client` — `SparkChatTransport` and HTTP client types
28
- - `@womp/spark-agent/server` — run service, store interfaces, executor, and development stores
29
- - `@womp/spark-agent/express` — mountable Express 5 router
30
-
31
- The root, `/browser`, `/ui`, `/capabilities`, and `/client` entry points do not import QuickJS,
32
- Kakapo, Express, or React at runtime.
33
-
34
- ## Running in a host application
35
-
36
- `runSparkAgent` accepts a configured model, model messages, an executor, a capability composition,
37
- and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
38
- Skills carry instructions, declarations, and JavaScript examples. The native executor runs in the
39
- page realm; it is not a sandbox and cannot interrupt a synchronous infinite loop.
40
-
41
- Compose only supported capabilities with `composeCapabilities({ scene, account, ... })`. Each
42
- capability supplies its skills and `createApi(context)`; the returned API is bound under `womp`.
43
- Every invocation receives a cancellation signal and an awaited `recordReceipt` hook. Capabilities
44
- own their transactions and must check cancellation at their commit boundary.
45
-
46
- For a real user interaction, call `context.waitForUser(label, async signal => ...)` outside mutation
47
- locks. The run stays active with `requires_action` progress and resumes the same invocation after
48
- input. Native and QuickJS executors exclude this wait from their execution budget; service work
49
- continues to consume time, and QuickJS guest computation still counts even if an interaction was
50
- not awaited. Abort settles the wait and late answers cannot revive it. The UI host must also reject
51
- competing requests for a shared dialog and match callbacks to the original interaction identity.
52
- Custom executors must honor the optional host-owned `userWait` argument to `execute`.
53
-
54
- Persist the user message before starting a run. Supply `persistence.beforeToolCall`,
55
- `persistence.recordReceipt`, and `persistence.afterToolCall` for awaited storage barriers.
56
- Supply `persistence.saveMessage` to save the canonical UI message at each model-step boundary and
57
- at termination, including reasoning and provider metadata. Spark awaits each checkpoint before the
58
- next model request. Consume the run's stream while awaiting its result; the result settles after
59
- the terminal checkpoint. A host run owner must keep consuming when a view detaches.
60
- A failed barrier aborts the loop. The default model-step limit is 100; a limit returns
61
- `status: "incomplete"`. A host must not automatically restart to bypass that limit.
62
-
63
- Before each model request Spark applies an estimated input budget (`maxInputTokens`, default 64,000).
64
- It first removes duplicate skill bodies and execution observations, then abbreviates older skill reads
65
- while retaining the latest workflow/reference pair. The agent must read an abbreviated skill again
66
- before using its API. Older completed scripts can be shortened only when their committed receipts and
67
- results remain available and their inputs are not tied to provider reasoning/signatures. Requests,
68
- tool pairs, targets, artifacts, receipts, errors, and unresolved work remain in context; persisted
69
- history is never shortened. If this cannot fit, the run returns `incomplete` with `context-limit` and
70
- an actionable explanation, without another model request or automatic replay.
71
-
72
- When older successful execution results contain images, their binary content can be omitted from model
73
- input while preserving artifact references, receipts, and observations. The latest visual result,
74
- user-provided media, failed/uncertain results, and provider-owned content remain intact. The agent can
75
- inspect an older artifact again with `emitImage` or `emitFile`; stored history retains the full result.
76
- Call metadata copied by the SDK onto local tool results is preserved during this trimming; it does not
77
- prevent compaction of the host-produced image bytes. Original model calls and signatures are unchanged.
78
-
79
- Token sizing is approximate (UTF-8 text bytes / 3 plus an 8,192-token allowance per image/file and
80
- 2,048 for tool schemas/framing). This is an application input budget, not provider capacity metadata.
81
- Hosts should choose it alongside the selected model's output budget and verify their deployed models;
82
- the estimate cannot guarantee a provider accepts every document or image size.
83
-
84
- Screenshot and file APIs return serializable artifact references. `emitImage` and `emitFile` resolve
85
- them through the host's artifact resolver. Keep references resolvable when reloading history, and
86
- pass `createSparkTools(...)` to AI SDK's `convertToModelMessages` to reconstruct multimodal results.
87
- Kakapo skill declarations are generated from its actual typed API during the build and packaged as
88
- JavaScript strings; they do not need filesystem or Markdown loading in the browser.
89
-
90
- ## Local frontend
91
-
92
- `LocalSparkChatTransport` accepts a host callback returning a `SparkAgentRun` from `runSparkAgent`.
93
- The callback receives the standard AI SDK chat request, including its messages and abort signal.
94
- It persists the initiating message, resolves the model and capabilities, and starts the shared loop.
95
- Keep ownership of the active run outside the React renderer. Local browser runs end when the page
96
- closes; `reconnectToStream` returns `null`.
97
-
98
- ## Remote frontend
99
-
100
- ```tsx
101
- import { useChat } from "@ai-sdk/react";
102
- import { SparkChatTransport } from "@womp/spark-agent/client";
103
- import {
104
- sparkDataPartSchemas,
105
- sparkMessageMetadataSchema,
106
- type SparkUIMessage,
107
- } from "@womp/spark-agent/ui";
108
-
109
- const transport = new SparkChatTransport({ api: "/agent" });
110
-
111
- const chat = useChat<SparkUIMessage>({
112
- id: conversationId,
113
- messages: initialMessages,
114
- transport,
115
- resume: true,
116
- messageMetadataSchema: sparkMessageMetadataSchema,
117
- dataPartSchemas: sparkDataPartSchemas,
118
- });
119
- ```
120
-
121
- Render `message.parts`. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool
122
- outputs, approvals, and step boundaries. Spark adds typed `data-console`, `data-artifact`,
123
- `data-run-status`, `data-step-metrics`, and `data-execution-receipt` parts. Receipts are emitted before
124
- their storage barrier, keeping known effects visible even when saving the tool result fails.
125
-
126
- An explicit Stop action must cancel the durable run before stopping local observation:
127
-
128
- ```ts
129
- await transport.cancelRun(conversationId);
130
- await chat.stop();
131
- ```
132
-
133
- Closing or reloading the page only detaches the SSE subscriber. It does not cancel the run.
134
-
135
- ## HTTP and SSE
136
-
137
- `SparkChatTransport` hides the durable run lifecycle from `useChat`:
138
-
139
- ```text
140
- POST /agent/conversations/:conversationId/messages
141
- GET /agent/runs/:runId/stream?after=0
142
- POST /agent/runs/:runId/cancel
143
- ```
144
-
145
- The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
146
-
147
- ```ts
148
- type StoredSparkChunk = {
149
- sequence: number;
150
- conversationId: string;
151
- runId: string;
152
- createdAt: string;
153
- chunk: SparkUIMessageChunk;
154
- };
155
- ```
156
-
157
- The transport unwraps each envelope and returns the contained `UIMessageChunk` to `useChat`.
158
- Active assistant messages are replayed from sequence zero when reconnecting because `useChat`
159
- reconstructs the in-progress message from the resumed stream.
160
-
161
- ## Embedding in an Express backend
162
-
163
- Spark does not call `listen()` when imported. The host owns its HTTP server and mounts the router:
164
-
165
- ```ts
166
- import { createSparkAgentRouter } from "@womp/spark-agent/express";
167
- import { SparkAgentService, createSparkRunExecutor } from "@womp/spark-agent/server";
168
-
169
- const service = new SparkAgentService({
170
- store,
171
- executeRun: createSparkRunExecutor({
172
- resolveModel,
173
- createHost,
174
- }),
175
- });
176
-
177
- app.use(
178
- "/agent",
179
- userAccess,
180
- createSparkAgentRouter({
181
- service,
182
- artifactStore,
183
- resolveOwnerId: (request) => request.user.id,
184
- }),
185
- );
186
- ```
187
-
188
- `MemorySparkAgentStore` and `MemorySparkArtifactStore` are development implementations. Production
189
- hosts must supply durable implementations backed by their database/object storage and must preserve
190
- the ordering contract of `appendRunChunk()`.
191
-
192
- ## Standalone development server
193
-
194
- The package includes a local server bound to `127.0.0.1:8787`:
195
-
196
- ```bash
197
- spark-agent
198
- ```
199
-
200
- Configure the process with:
201
-
202
- - `OPENAI_API_KEY`
203
- - `ANTHROPIC_API_KEY`
204
- - `GEMINI_API_KEY`
205
- - `KAKAPO_URL` (defaults to `ws://127.0.0.1:5502`)
206
- - `PORT` (defaults to `8787`)
207
- - `SPARK_STORAGE_PATH` (defaults to `.spark/spark.sqlite`)
208
- - `SPARK_SCENE_IDENTITY` (optional stable identity for the scene behind `KAKAPO_URL`)
209
-
210
- The CLI automatically loads `.env` from its working directory when the file exists. Copy
211
- `.env.example` to `.env`, then add the provider key you intend to use. Existing process environment
212
- variables take precedence over values loaded from the file.
213
-
214
- For repository development, `yarn dev` starts the Express server and Vite demo together. Open
215
- `http://127.0.0.1:5173`.
216
-
217
- Standalone scene edits use the existing `scene_state_patch` command followed by `save_scene` after
218
- the patch is acknowledged. The durable operation journal records the separate mutation and save
219
- outcomes. A failed or unknown save stops that operation with an explicit error, retaining any
220
- applied work without replay. The two commands are not an atomic transaction, and a renderer save
221
- acknowledgement proves only renderer-local history, not durable backend storage.
222
-
223
- The standalone server persists conversations, run streams, and screenshot bytes in SQLite. Only
224
- one local process can execute against a database. After a terminated process is replaced, startup
225
- preserves recorded output and marks abandoned queued/running runs incomplete without replaying
226
- scene edits. Pending approvals remain pending. Full operation reconciliation and run continuation
227
- are still being integrated. Node 22 reports an experimental warning for its built-in SQLite module.
228
-
229
- Set `SPARK_SCENE_IDENTITY` only when the host can verify that the value names the same durable
230
- scene across renderer reconnects. Without it, Spark uses a process-scoped identity and does not
231
- authorize interrupted scene mutations to continue after a server restart.
232
-
233
- `createStandaloneSparkAgentApp(options)` is asynchronous. Await it before listening, and call
234
- `await app.dispose()` on shutdown to settle active runs and release storage ownership.
235
-
236
- This development branch currently installs `kakapo-sdk` from the sibling checkout. Run `yarn build`
237
- in `../kakapo-sdk` before installing Spark dependencies. A registry version must replace this local
238
- dependency before release; no new package has been published by this migration.
239
-
240
- For the explicit fictional account profile, use `yarn dev:demo-account` or
241
- `spark-agent --profile demo-account`. It adds the `account` skill and `womp.account.read`
242
- beside the real local Kakapo scene API. Profile reads identify themselves as fictional fixtures;
243
- credits, billing, orders, and other account services report unavailable. The default composition
244
- omits account entirely. Neither profile supplies fake generation jobs, imports, or billing permission.
245
- Embedded development servers can select `developmentProfile: "demo-account"` explicitly.
246
-
247
- The same profile supplies two actual local material resources: `Development: Matte Blue` and
248
- `Development: Polished Copper`. They are bundled JSON files under `src/server/fixtures/materials`
249
- and imported through the normal SDK draft, not simulated scene effects. In `scene.edit`, await
250
- `api.materialImport(name)`, then assign its numeric ID. The default profile has no library service.
251
-
252
- ## Run cost estimates
253
-
254
- Prompt caching is enabled for every listed model: Claude Opus 5 and Fable 5 use
255
- automatic 5-minute caching, and GPT-6 Astra uses implicit caching with a 30-minute TTL.
256
- The providers manage the cache as message history grows across tool steps; no local
257
- response cache is used. Cache hits require matching prefixes, sufficient prompt length,
258
- and an unexpired entry. Cache writes can cost more than uncached input, while subsequent
259
- cache reads are cheaper. The UI displays actual reported reads/writes, not assumed savings.
260
- See [Claude caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
261
- and [OpenAI caching](https://developers.openai.com/api/docs/guides/prompt-caching).
262
- Restart the server after changing these settings; existing runs keep their original configuration.
263
-
264
- The chat displays estimated API token cost in USD, updated after every completed model step.
265
- Hover or focus the amount to inspect uncached input, cache reads/writes, and output costs. Output usage
266
- already includes reasoning tokens. Image input is included in the provider's input token count.
267
-
268
- `src/pricing.ts` contains standard direct API rates verified on September 9, 2026:
269
-
270
- | Model | Input / 1M | Cache read / 1M | Cache write / 1M | Output / 1M |
271
- | -------------- | ---------- | --------------- | --------------------- | ----------- |
272
- | GPT-6 Astra | $10 | $1 | $12.50 | $50 |
273
- | Claude Opus 5 | $5 | $0.50 | $6.25 (5m), $10 (1h) | $25 |
274
- | Claude Fable 5 | $10 | $1 | $12.50 (5m), $20 (1h) | $50 |
275
-
276
- Astra requests above 272,000 input tokens use 2x input/cache rates and 1.5x output rates.
277
- This threshold is applied to each request before summing run costs. Claude's full context uses
278
- standard rates. Sources: [Astra pricing](https://developers.openai.com/api/docs/models/gpt-6-astra),
279
- [Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing).
280
-
281
- Each `data-step-metrics` part stores a `cost` snapshot with the model, rate date, source, token
282
- categories, rates, and amounts. The UI sums those stored amounts, so reconnection does not double
283
- count steps and future price changes do not reprice recorded runs. Update the rate table and its
284
- verification date when provider pricing changes.
285
-
286
- The unfinished step is excluded until its usage arrives. Cancelled/failed runs show a partial
287
- estimate. Missing usage, unknown models, missing cache TTLs, or provider sub-inferences without
288
- separate attribution are marked unavailable, never silently priced as zero. Historical runs
289
- without step pricing remain unavailable. Estimates exclude taxes, negotiated discounts, and scene
290
- hosting; the provider bill is authoritative. Spark requests OpenAI's standard service tier.
291
-
292
- ## Execution security
293
-
294
- The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js
295
- or browser globals and receives only the composed capability APIs. Imports, filesystem access,
296
- network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
297
- execution time, source size, host calls, and data crossing the host bridge. Browser hosts can instead
298
- choose native page execution, with the page-global access and interruption limitations described above.
299
-
300
- QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
301
- boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
302
- session and tool call, enforce quotas, and avoid passing privileged functions through the runtime
303
- bridge.
304
-
305
- ## Validation
306
-
307
- ```bash
308
- yarn build
309
- yarn typecheck
310
- yarn test
311
- yarn web:build
312
- ```
313
-
314
- ## Release procedure
315
-
316
- Releases use a manual package publication; no separate release service is required.
317
-
318
- ### Migration to 0.3
319
-
320
- - The package root is browser-safe. Import `KakapoRuntime`, `createWompSceneApi`, and its enums
321
- from `/kakapo`, and `QuickJSJavaScriptExecutor` from `/quickjs`.
322
- - `runSparkAgent` takes the host's selected model, executor, capability composition, artifact
323
- resolver, and persistence hooks. The host owns connection setup and disposal. The model sees
324
- only `read_skill` and `execute_javascript`; domain methods live under the supplied `womp` APIs.
325
- - Replace fixed scene/screenshot tool history with the host's history adapter. Use
326
- `convertSparkMessages` when rebuilding model input from current Spark UI messages so artifact
327
- references become model-visible content. Preserve old records; do not replay historical tools.
328
- - Custom server executors receive `uiMessages` and convert them using their actual capability
329
- tools. `SparkRunExecutionContext.modelMessages` has been removed.
330
- - Custom JavaScript executors must honor cancellation and host-owned user waits. Callback-based
331
- scene edits and artifact references work with both the native and QuickJS executors.
332
-
333
- ### 0.3.1 context defaults
334
-
335
- - The default input context budget is now 128,000 estimated tokens. Hosts can still
336
- set `maxInputTokens` explicitly; this budget does not change the provider's context limit.
337
- - Images, including images in tool results, use a 512-token estimate instead of the
338
- general 8,192-token file allowance. Encoded media bytes remain excluded from text
339
- estimates. Token accounting is heuristic; no API or storage migration is required.
340
-
341
- ### Publishing
342
-
343
- 1. Choose an unused package version after checking `npm view @womp/spark-agent versions --json`.
344
- Update `package.json` and record public API changes and migration notes. Use a new minor version
345
- for incompatible changes while the package is below 1.0.
346
- 2. On a supported Node version, run `yarn install --frozen-lockfile`, `yarn typecheck`, `yarn test`,
347
- and `yarn web:build`. Do not bypass dependency engine checks. Commit the release changes and
348
- record the commit used to build the package.
349
- 3. Run `yarn pack --filename spark-agent-release.tgz`. Inspect the archive contents and install
350
- that archive in the consuming application. Verify packaged declarations and skills, browser
351
- import boundaries, the production build, and the applicable integration cases without source
352
- aliases. Keep the archive outside Git and do not rebuild or change it after verification.
353
- 4. Publish the verified archive with `npm publish ./spark-agent-release.tgz --access public`.
354
- If the response is uncertain, inspect the registry's version and integrity before retrying.
355
- Verify the published version and archive integrity, then tag the recorded commit as `v<version>`
356
- and push the release commit and tag. An already published version cannot be replaced; corrections
357
- need a new version.
358
- 5. In the consuming application, use `yarn add @womp/spark-agent@<version> --exact`. Commit its
359
- manifest and lockfile together, and verify a clean frozen-lockfile installation and production
360
- build. Remove the temporary archive dependency. Application deployment and merge requirements
361
- remain separate from package publication.
362
-
363
- ## Layout
364
-
365
- - `src/agent.ts` — AI SDK stream and Spark tool orchestration
366
- - `src/ui.ts` — browser-safe protocol types and validation schemas
367
- - `src/client.ts` — durable `ChatTransport` implementation
368
- - `src/server/` — service, storage contracts, Express adapter, artifacts, and standalone composition
369
- - `src/runtime.ts` — Kakapo scene connections and edit transactions
370
- - `src/quickjs-executor.ts` — isolated JavaScript execution and host bridge
371
- - `src/womp-scene-api.ts` — Womp-compatible Kakapo scene API
372
- - `web/src/App.tsx` — reference `useChat<SparkUIMessage>` application
1
+ # @womp/spark-agent
2
+
3
+ Portable AI agent for Womp applications and Kakapo scenes with model-authored JavaScript.
4
+
5
+ Spark Agent uses AI SDK 7 `UIMessage` and `UIMessageChunk` as its public chat protocol. Generated
6
+ JavaScript uses the host's executor and capabilities. Native browser execution and isolated QuickJS
7
+ execution use the same model loop. The host owns conversations, artifacts, authorization, and connections.
8
+
9
+ ## Requirements
10
+
11
+ - Node.js 22.13 or newer
12
+ - AI SDK 7
13
+ - `@ai-sdk/react` 4 for React clients
14
+
15
+ ```bash
16
+ npm install @womp/spark-agent
17
+ ```
18
+
19
+ ## Package entry points
20
+
21
+ - `@womp/spark-agent` — browser-safe model loop, two tools, persistence hooks, and shared contracts
22
+ - `@womp/spark-agent/browser` — native JavaScript executor and local chat transport
23
+ - `@womp/spark-agent/capabilities` — capability composition and skill contracts
24
+ - `@womp/spark-agent/kakapo` — direct Kakapo runtime and scene capability
25
+ - `@womp/spark-agent/quickjs` — optional isolated JavaScript executor
26
+ - `@womp/spark-agent/ui` — browser-safe UI message types, schemas, and model catalog
27
+ - `@womp/spark-agent/client` — `SparkChatTransport` and HTTP client types
28
+ - `@womp/spark-agent/server` — run service, store interfaces, executor, and development stores
29
+ - `@womp/spark-agent/express` — mountable Express 5 router
30
+
31
+ The root, `/browser`, `/ui`, `/capabilities`, and `/client` entry points do not import QuickJS,
32
+ Kakapo, Express, or React at runtime.
33
+
34
+ ## Running in a host application
35
+
36
+ `runSparkAgent` accepts a configured model, model messages, an executor, a capability composition,
37
+ and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
38
+ Skills carry instructions, declarations, and JavaScript examples. The native executor runs in the
39
+ page realm; it is not a sandbox and cannot interrupt a synchronous infinite loop.
40
+
41
+ Compose only supported capabilities with `composeCapabilities({ scene, account, ... })`. Each
42
+ capability supplies its skills and `createApi(context)`; the returned API is bound under `womp`.
43
+ Every invocation receives a cancellation signal and an awaited `recordReceipt` hook. Capabilities
44
+ own their transactions and must check cancellation at their commit boundary.
45
+
46
+ For a real user interaction, call `context.waitForUser(label, async signal => ...)` outside mutation
47
+ locks. The run stays active with `requires_action` progress and resumes the same invocation after
48
+ input. Native and QuickJS executors exclude this wait from their execution budget; service work
49
+ continues to consume time, and QuickJS guest computation still counts even if an interaction was
50
+ not awaited. Abort settles the wait and late answers cannot revive it. The UI host must also reject
51
+ competing requests for a shared dialog and match callbacks to the original interaction identity.
52
+ Custom executors must honor the optional host-owned `userWait` argument to `execute`.
53
+
54
+ Persist the user message before starting a run. Supply `persistence.beforeToolCall`,
55
+ `persistence.recordReceipt`, and `persistence.afterToolCall` for awaited storage barriers.
56
+ Supply `persistence.saveMessage` to save the canonical UI message at each model-step boundary and
57
+ at termination, including reasoning and provider metadata. Spark awaits each checkpoint before the
58
+ next model request. Consume the run's stream while awaiting its result; the result settles after
59
+ the terminal checkpoint. A host run owner must keep consuming when a view detaches.
60
+ A failed barrier aborts the loop. The default model-step limit is 100; a limit returns
61
+ `status: "incomplete"`. A host must not automatically restart to bypass that limit.
62
+
63
+ Before each model request Spark applies an estimated input budget (`maxInputTokens`, default 64,000).
64
+ It first removes duplicate skill bodies and execution observations, then abbreviates older skill reads
65
+ while retaining the latest workflow/reference pair. The agent must read an abbreviated skill again
66
+ before using its API. Older completed scripts can be shortened only when their committed receipts and
67
+ results remain available and their inputs are not tied to provider reasoning/signatures. Requests,
68
+ tool pairs, targets, artifacts, receipts, errors, and unresolved work remain in context; persisted
69
+ history is never shortened. If this cannot fit, the run returns `incomplete` with `context-limit` and
70
+ an actionable explanation, without another model request or automatic replay.
71
+
72
+ When older successful execution results contain images, their binary content can be omitted from model
73
+ input while preserving artifact references, receipts, and observations. The latest visual result,
74
+ user-provided media, failed/uncertain results, and provider-owned content remain intact. The agent can
75
+ inspect an older artifact again with `emitImage` or `emitFile`; stored history retains the full result.
76
+ Call metadata copied by the SDK onto local tool results is preserved during this trimming; it does not
77
+ prevent compaction of the host-produced image bytes. Original model calls and signatures are unchanged.
78
+
79
+ Token sizing is approximate (UTF-8 text bytes / 3 plus an 8,192-token allowance per image/file and
80
+ 2,048 for tool schemas/framing). This is an application input budget, not provider capacity metadata.
81
+ Hosts should choose it alongside the selected model's output budget and verify their deployed models;
82
+ the estimate cannot guarantee a provider accepts every document or image size.
83
+
84
+ Screenshot and file APIs return serializable artifact references. `emitImage` and `emitFile` resolve
85
+ them through the host's artifact resolver. Keep references resolvable when reloading history, and
86
+ pass `createSparkTools(...)` to AI SDK's `convertToModelMessages` to reconstruct multimodal results.
87
+ Kakapo skill declarations are generated from its actual typed API during the build and packaged as
88
+ JavaScript strings; they do not need filesystem or Markdown loading in the browser.
89
+
90
+ ## Local frontend
91
+
92
+ `LocalSparkChatTransport` accepts a host callback returning a `SparkAgentRun` from `runSparkAgent`.
93
+ The callback receives the standard AI SDK chat request, including its messages and abort signal.
94
+ It persists the initiating message, resolves the model and capabilities, and starts the shared loop.
95
+ Keep ownership of the active run outside the React renderer. Local browser runs end when the page
96
+ closes; `reconnectToStream` returns `null`.
97
+
98
+ ## Remote frontend
99
+
100
+ ```tsx
101
+ import { useChat } from "@ai-sdk/react";
102
+ import { SparkChatTransport } from "@womp/spark-agent/client";
103
+ import {
104
+ sparkDataPartSchemas,
105
+ sparkMessageMetadataSchema,
106
+ type SparkUIMessage,
107
+ } from "@womp/spark-agent/ui";
108
+
109
+ const transport = new SparkChatTransport({ api: "/agent" });
110
+
111
+ const chat = useChat<SparkUIMessage>({
112
+ id: conversationId,
113
+ messages: initialMessages,
114
+ transport,
115
+ resume: true,
116
+ messageMetadataSchema: sparkMessageMetadataSchema,
117
+ dataPartSchemas: sparkDataPartSchemas,
118
+ });
119
+ ```
120
+
121
+ Render `message.parts`. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool
122
+ outputs, approvals, and step boundaries. Spark adds typed `data-console`, `data-artifact`,
123
+ `data-run-status`, `data-step-metrics`, and `data-execution-receipt` parts. Receipts are emitted before
124
+ their storage barrier, keeping known effects visible even when saving the tool result fails.
125
+
126
+ An explicit Stop action must cancel the durable run before stopping local observation:
127
+
128
+ ```ts
129
+ await transport.cancelRun(conversationId);
130
+ await chat.stop();
131
+ ```
132
+
133
+ Closing or reloading the page only detaches the SSE subscriber. It does not cancel the run.
134
+
135
+ ## HTTP and SSE
136
+
137
+ `SparkChatTransport` hides the durable run lifecycle from `useChat`:
138
+
139
+ ```text
140
+ POST /agent/conversations/:conversationId/messages
141
+ GET /agent/runs/:runId/stream?after=0
142
+ POST /agent/runs/:runId/cancel
143
+ ```
144
+
145
+ The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
146
+
147
+ ```ts
148
+ type StoredSparkChunk = {
149
+ sequence: number;
150
+ conversationId: string;
151
+ runId: string;
152
+ createdAt: string;
153
+ chunk: SparkUIMessageChunk;
154
+ };
155
+ ```
156
+
157
+ The transport unwraps each envelope and returns the contained `UIMessageChunk` to `useChat`.
158
+ Active assistant messages are replayed from sequence zero when reconnecting because `useChat`
159
+ reconstructs the in-progress message from the resumed stream.
160
+
161
+ ## Embedding in an Express backend
162
+
163
+ Spark does not call `listen()` when imported. The host owns its HTTP server and mounts the router:
164
+
165
+ ```ts
166
+ import { createSparkAgentRouter } from "@womp/spark-agent/express";
167
+ import { SparkAgentService, createSparkRunExecutor } from "@womp/spark-agent/server";
168
+
169
+ const service = new SparkAgentService({
170
+ store,
171
+ executeRun: createSparkRunExecutor({
172
+ resolveModel,
173
+ createHost,
174
+ }),
175
+ });
176
+
177
+ app.use(
178
+ "/agent",
179
+ userAccess,
180
+ createSparkAgentRouter({
181
+ service,
182
+ artifactStore,
183
+ resolveOwnerId: (request) => request.user.id,
184
+ }),
185
+ );
186
+ ```
187
+
188
+ `MemorySparkAgentStore` and `MemorySparkArtifactStore` are development implementations. Production
189
+ hosts must supply durable implementations backed by their database/object storage and must preserve
190
+ the ordering contract of `appendRunChunk()`.
191
+
192
+ ## Standalone development server
193
+
194
+ The package includes a local server bound to `127.0.0.1:8787`:
195
+
196
+ ```bash
197
+ spark-agent
198
+ ```
199
+
200
+ Configure the process with:
201
+
202
+ - `OPENAI_API_KEY`
203
+ - `ANTHROPIC_API_KEY`
204
+ - `GEMINI_API_KEY`
205
+ - `KAKAPO_URL` (defaults to `ws://127.0.0.1:5502`)
206
+ - `PORT` (defaults to `8787`)
207
+ - `SPARK_STORAGE_PATH` (defaults to `.spark/spark.sqlite`)
208
+ - `SPARK_SCENE_IDENTITY` (optional stable identity for the scene behind `KAKAPO_URL`)
209
+
210
+ The CLI automatically loads `.env` from its working directory when the file exists. Copy
211
+ `.env.example` to `.env`, then add the provider key you intend to use. Existing process environment
212
+ variables take precedence over values loaded from the file.
213
+
214
+ For repository development, `yarn dev` starts the Express server and Vite demo together. Open
215
+ `http://127.0.0.1:5173`.
216
+
217
+ Standalone scene edits use the existing `scene_state_patch` command followed by `save_scene` after
218
+ the patch is acknowledged. The durable operation journal records the separate mutation and save
219
+ outcomes. A failed or unknown save stops that operation with an explicit error, retaining any
220
+ applied work without replay. The two commands are not an atomic transaction, and a renderer save
221
+ acknowledgement proves only renderer-local history, not durable backend storage.
222
+
223
+ The standalone server persists conversations, run streams, and screenshot bytes in SQLite. Only
224
+ one local process can execute against a database. After a terminated process is replaced, startup
225
+ preserves recorded output and marks abandoned queued/running runs incomplete without replaying
226
+ scene edits. Pending approvals remain pending. Full operation reconciliation and run continuation
227
+ are still being integrated. Node 22 reports an experimental warning for its built-in SQLite module.
228
+
229
+ Set `SPARK_SCENE_IDENTITY` only when the host can verify that the value names the same durable
230
+ scene across renderer reconnects. Without it, Spark uses a process-scoped identity and does not
231
+ authorize interrupted scene mutations to continue after a server restart.
232
+
233
+ `createStandaloneSparkAgentApp(options)` is asynchronous. Await it before listening, and call
234
+ `await app.dispose()` on shutdown to settle active runs and release storage ownership.
235
+
236
+ This development branch currently installs `kakapo-sdk` from the sibling checkout. Run `yarn build`
237
+ in `../kakapo-sdk` before installing Spark dependencies. A registry version must replace this local
238
+ dependency before release; no new package has been published by this migration.
239
+
240
+ For the explicit fictional account profile, use `yarn dev:demo-account` or
241
+ `spark-agent --profile demo-account`. It adds the `account` skill and `womp.account.read`
242
+ beside the real local Kakapo scene API. Profile reads identify themselves as fictional fixtures;
243
+ credits, billing, orders, and other account services report unavailable. The default composition
244
+ omits account entirely. Neither profile supplies fake generation jobs, imports, or billing permission.
245
+ Embedded development servers can select `developmentProfile: "demo-account"` explicitly.
246
+
247
+ The same profile supplies two actual local material resources: `Development: Matte Blue` and
248
+ `Development: Polished Copper`. They are bundled JSON files under `src/server/fixtures/materials`
249
+ and imported through the normal SDK draft, not simulated scene effects. In `scene.edit`, await
250
+ `api.materialImport(name)`, then assign its numeric ID. The default profile has no library service.
251
+
252
+ ## Run cost estimates
253
+
254
+ Prompt caching is enabled for every listed model: Claude Opus 5 and Fable 5 use
255
+ automatic 5-minute caching, and GPT-6 Astra uses implicit caching with a 30-minute TTL.
256
+ The providers manage the cache as message history grows across tool steps; no local
257
+ response cache is used. Cache hits require matching prefixes, sufficient prompt length,
258
+ and an unexpired entry. Cache writes can cost more than uncached input, while subsequent
259
+ cache reads are cheaper. The UI displays actual reported reads/writes, not assumed savings.
260
+ See [Claude caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
261
+ and [OpenAI caching](https://developers.openai.com/api/docs/guides/prompt-caching).
262
+ Restart the server after changing these settings; existing runs keep their original configuration.
263
+
264
+ The chat displays estimated API token cost in USD, updated after every completed model step.
265
+ Hover or focus the amount to inspect uncached input, cache reads/writes, and output costs. Output usage
266
+ already includes reasoning tokens. Image input is included in the provider's input token count.
267
+
268
+ `src/pricing.ts` contains standard direct API rates verified on September 9, 2026:
269
+
270
+ | Model | Input / 1M | Cache read / 1M | Cache write / 1M | Output / 1M |
271
+ | -------------- | ---------- | --------------- | --------------------- | ----------- |
272
+ | GPT-6 Astra | $10 | $1 | $12.50 | $50 |
273
+ | Claude Opus 5 | $5 | $0.50 | $6.25 (5m), $10 (1h) | $25 |
274
+ | Claude Fable 5 | $10 | $1 | $12.50 (5m), $20 (1h) | $50 |
275
+
276
+ Astra requests above 272,000 input tokens use 2x input/cache rates and 1.5x output rates.
277
+ This threshold is applied to each request before summing run costs. Claude's full context uses
278
+ standard rates. Sources: [Astra pricing](https://developers.openai.com/api/docs/models/gpt-6-astra),
279
+ [Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing).
280
+
281
+ Each `data-step-metrics` part stores a `cost` snapshot with the model, rate date, source, token
282
+ categories, rates, and amounts. The UI sums those stored amounts, so reconnection does not double
283
+ count steps and future price changes do not reprice recorded runs. Update the rate table and its
284
+ verification date when provider pricing changes.
285
+
286
+ The unfinished step is excluded until its usage arrives. Cancelled/failed runs show a partial
287
+ estimate. Missing usage, unknown models, missing cache TTLs, or provider sub-inferences without
288
+ separate attribution are marked unavailable, never silently priced as zero. Historical runs
289
+ without step pricing remain unavailable. Estimates exclude taxes, negotiated discounts, and scene
290
+ hosting; the provider bill is authoritative. Spark requests OpenAI's standard service tier.
291
+
292
+ ## Execution security
293
+
294
+ The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js
295
+ or browser globals and receives only the composed capability APIs. Imports, filesystem access,
296
+ network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
297
+ execution time, source size, host calls, and data crossing the host bridge. Browser hosts can instead
298
+ choose native page execution, with the page-global access and interruption limitations described above.
299
+
300
+ QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
301
+ boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
302
+ session and tool call, enforce quotas, and avoid passing privileged functions through the runtime
303
+ bridge.
304
+
305
+ ## Validation
306
+
307
+ ```bash
308
+ yarn build
309
+ yarn typecheck
310
+ yarn test
311
+ yarn web:build
312
+ ```
313
+
314
+ ## Release procedure
315
+
316
+ Releases use a manual package publication; no separate release service is required.
317
+
318
+ ### Migration to 0.3
319
+
320
+ - The package root is browser-safe. Import `KakapoRuntime`, `createWompSceneApi`, and its enums
321
+ from `/kakapo`, and `QuickJSJavaScriptExecutor` from `/quickjs`.
322
+ - `runSparkAgent` takes the host's selected model, executor, capability composition, artifact
323
+ resolver, and persistence hooks. The host owns connection setup and disposal. The model sees
324
+ only `read_skill` and `execute_javascript`; domain methods live under the supplied `womp` APIs.
325
+ - Replace fixed scene/screenshot tool history with the host's history adapter. Use
326
+ `convertSparkMessages` when rebuilding model input from current Spark UI messages so artifact
327
+ references become model-visible content. Preserve old records; do not replay historical tools.
328
+ - Custom server executors receive `uiMessages` and convert them using their actual capability
329
+ tools. `SparkRunExecutionContext.modelMessages` has been removed.
330
+ - Custom JavaScript executors must honor cancellation and host-owned user waits. Callback-based
331
+ scene edits and artifact references work with both the native and QuickJS executors.
332
+
333
+ ### 0.3.1 context defaults
334
+
335
+ - The default input context budget is now 128,000 estimated tokens. Hosts can still
336
+ set `maxInputTokens` explicitly; this budget does not change the provider's context limit.
337
+ - Images, including images in tool results, use a 512-token estimate instead of the
338
+ general 8,192-token file allowance. Encoded media bytes remain excluded from text
339
+ estimates. Token accounting is heuristic; no API or storage migration is required.
340
+
341
+ ### Publishing
342
+
343
+ 1. Choose an unused package version after checking `npm view @womp/spark-agent versions --json`.
344
+ Update `package.json` and record public API changes and migration notes. Use a new minor version
345
+ for incompatible changes while the package is below 1.0.
346
+ 2. On a supported Node version, run `yarn install --frozen-lockfile`, `yarn typecheck`, `yarn test`,
347
+ and `yarn web:build`. Do not bypass dependency engine checks. Commit the release changes and
348
+ record the commit used to build the package.
349
+ 3. Run `yarn pack --filename spark-agent-release.tgz`. Inspect the archive contents and install
350
+ that archive in the consuming application. Verify packaged declarations and skills, browser
351
+ import boundaries, the production build, and the applicable integration cases without source
352
+ aliases. Keep the archive outside Git and do not rebuild or change it after verification.
353
+ 4. Publish the verified archive with `npm publish ./spark-agent-release.tgz --access public`.
354
+ If the response is uncertain, inspect the registry's version and integrity before retrying.
355
+ Verify the published version and archive integrity, then tag the recorded commit as `v<version>`
356
+ and push the release commit and tag. An already published version cannot be replaced; corrections
357
+ need a new version.
358
+ 5. In the consuming application, use `yarn add @womp/spark-agent@<version> --exact`. Commit its
359
+ manifest and lockfile together, and verify a clean frozen-lockfile installation and production
360
+ build. Remove the temporary archive dependency. Application deployment and merge requirements
361
+ remain separate from package publication.
362
+
363
+ ## Layout
364
+
365
+ - `src/agent.ts` — AI SDK stream and Spark tool orchestration
366
+ - `src/ui.ts` — browser-safe protocol types and validation schemas
367
+ - `src/client.ts` — durable `ChatTransport` implementation
368
+ - `src/server/` — service, storage contracts, Express adapter, artifacts, and standalone composition
369
+ - `src/runtime.ts` — Kakapo scene connections and edit transactions
370
+ - `src/quickjs-executor.ts` — isolated JavaScript execution and host bridge
371
+ - `src/womp-scene-api.ts` — Womp-compatible Kakapo scene API
372
+ - `web/src/App.tsx` — reference `useChat<SparkUIMessage>` application