@womp/spark-agent 4.2.2 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +457 -457
- package/dist/agent-run.d.ts +1 -1
- package/dist/capabilities/flow.js +3 -3
- package/dist/capabilities/geometry.js +14 -14
- package/dist/capabilities/openscad-instructions.d.ts +1 -1
- package/dist/capabilities/openscad-instructions.js +2 -1
- package/dist/capabilities/operations.js +33 -32
- package/dist/capabilities/scene.js +76 -71
- package/dist/client.js +8 -0
- package/dist/geometry/schemas/backend-schema.json +3393 -3393
- package/dist/geometry/schemas/flowir-dialect.json +1023 -1023
- package/dist/geometry.js +6 -6
- package/dist/models.d.ts +12 -5
- package/dist/models.js +16 -7
- package/dist/pricing.js +15 -0
- package/dist/prompt.d.ts +1 -1
- package/dist/prompt.js +65 -70
- package/dist/scene-metadata.js +8 -8
- package/dist/sdk-scene-host.js +1 -3
- package/dist/server/express.js +9 -1
- package/dist/server/fixtures/materials/matte-blue.json +14 -14
- package/dist/server/fixtures/materials/polished-copper.json +14 -14
- package/dist/server/message-fold.d.ts +59 -0
- package/dist/server/message-fold.js +345 -0
- package/dist/server/scene-initialization.d.ts +3 -0
- package/dist/server/scene-initialization.js +44 -0
- package/dist/server/service.d.ts +18 -1
- package/dist/server/service.js +25 -17
- package/dist/server/sqlite-store.js +37 -37
- package/dist/server/standalone.js +3 -0
- package/dist/womp-scene-api.d.ts +2 -2
- package/dist/womp-scene-api.js +6 -7
- package/package.json +139 -139
- package/.env.example +0 -6
package/README.md
CHANGED
|
@@ -1,457 +1,457 @@
|
|
|
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
|
-
## Run a prompt from the command line
|
|
20
|
-
|
|
21
|
-
With the Spark server already running:
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
yarn agent-run --prompt "Make a cube" --model gpt-5.6-luna --reasoning max
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Use `--conversation <id>` to continue a thread and `--output <new-directory>` to save
|
|
28
|
-
`result.json`, `transcript.json` and original run artifacts under `artifacts/`.
|
|
29
|
-
For local runs, use the git-ignored `.spark/` directory, with a fresh subdirectory per run:
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
yarn agent-run --prompt "Make rainbow-colored boxes" --output .spark/agent-runs/rainbow-boxes
|
|
33
|
-
yarn agent-run --prompt "Model this object using both views" --image front.png --image side.jpg --output .spark/agent-runs/reference-model
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Repeat `--image <path>` for local PNG, JPEG, WebP or GIF references, in argument order.
|
|
37
|
-
The required prompt and all images are sent in one user message. Files, format signatures,
|
|
38
|
-
and the full JSON request size (including base64 overhead) are checked before contacting the server.
|
|
39
|
-
The CLI uses the server's default 25 MiB request limit; a custom server or provider may impose
|
|
40
|
-
stricter limits. Images are never resized, and remote URLs are not accepted.
|
|
41
|
-
With `--output`, original bytes are saved under `inputs/`; `input.json` and the result's `input`
|
|
42
|
-
field list ordered original filenames, MIME types and relative paths. The submitted message in
|
|
43
|
-
`transcript.json` uses those paths instead of inline data. Generated artifacts stay in `artifacts/`.
|
|
44
|
-
|
|
45
|
-
Without output, artifacts remain server references. The CLI never clears the scene or
|
|
46
|
-
takes extra screenshots. Defaults are `gpt-6-astra`, `high`, and
|
|
47
|
-
`--api http://127.0.0.1:8787/agent`; unsupported settings are rejected.
|
|
48
|
-
|
|
49
|
-
Progress goes to stderr; one JSON result goes to stdout. Use `yarn --silent agent-run`
|
|
50
|
-
or the installed `agent-run` executable when parsing stdout (Yarn otherwise adds banners).
|
|
51
|
-
Usage comes from the provider, with per-step USD estimates; missing usage is not estimated.
|
|
52
|
-
Exit codes: 0 completed, 1 run/transport/export failure, 2 invalid arguments,
|
|
53
|
-
3 requires user action (continue in the UI), 130 interrupted. Interrupts attempt to cancel
|
|
54
|
-
only this invocation's run; a failed cancellation is reported and the run may still be active.
|
|
55
|
-
Output directories must not already exist. Artifact manifests include IDs, media types,
|
|
56
|
-
server URLs and relative paths; failed downloads retain their references and report errors.
|
|
57
|
-
Legacy inline base64 uploads in transcripts are replaced with `[inline data omitted]`.
|
|
58
|
-
|
|
59
|
-
## Package entry points
|
|
60
|
-
|
|
61
|
-
- `@womp/spark-agent` — browser-safe model loop, two tools, persistence hooks, and shared contracts
|
|
62
|
-
- `@womp/spark-agent/browser` — native JavaScript executor and local chat transport
|
|
63
|
-
- `@womp/spark-agent/capabilities` — capability composition and skill contracts
|
|
64
|
-
- `@womp/spark-agent/kakapo` — direct Kakapo runtime and scene capability
|
|
65
|
-
- `@womp/spark-agent/quickjs` — optional isolated JavaScript executor
|
|
66
|
-
- `@womp/spark-agent/ui` — browser-safe UI message types, schemas, and model catalog
|
|
67
|
-
- `@womp/spark-agent/client` — `SparkChatTransport` and HTTP client types
|
|
68
|
-
- `@womp/spark-agent/server` — run service, store interfaces, executor, and development stores
|
|
69
|
-
- `@womp/spark-agent/express` — mountable Express 5 router
|
|
70
|
-
|
|
71
|
-
The root, `/browser`, `/ui`, `/capabilities`, and `/client` entry points do not import QuickJS,
|
|
72
|
-
Kakapo, Express, or React at runtime.
|
|
73
|
-
|
|
74
|
-
## Running in a host application
|
|
75
|
-
|
|
76
|
-
`runSparkAgent` accepts a configured model, model messages, an executor, a capability composition,
|
|
77
|
-
and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
|
|
78
|
-
Skills carry instructions, declarations, and JavaScript examples. The native executor runs in the
|
|
79
|
-
page realm; it is not a sandbox and cannot interrupt a synchronous infinite loop.
|
|
80
|
-
|
|
81
|
-
Compose only supported capabilities with `composeCapabilities({ scene, account, ... })`. Each
|
|
82
|
-
capability supplies its skills and `createApi(context)`; the returned API is bound under `womp`.
|
|
83
|
-
Every invocation receives a cancellation signal and an awaited `recordReceipt` hook. Capabilities
|
|
84
|
-
own their transactions and must check cancellation at their commit boundary.
|
|
85
|
-
|
|
86
|
-
For a real user interaction, call `context.waitForUser(label, async signal => ...)` outside mutation
|
|
87
|
-
locks. The run stays active with `requires_action` progress and resumes the same invocation after
|
|
88
|
-
input. Native and QuickJS executors exclude this wait from their execution budget; service work
|
|
89
|
-
continues to consume time, and QuickJS guest computation still counts even if an interaction was
|
|
90
|
-
not awaited. Abort settles the wait and late answers cannot revive it. The UI host must also reject
|
|
91
|
-
competing requests for a shared dialog and match callbacks to the original interaction identity.
|
|
92
|
-
Custom executors must honor the optional host-owned `userWait` argument to `execute`.
|
|
93
|
-
|
|
94
|
-
Persist the user message before starting a run. Supply `persistence.beforeToolCall`,
|
|
95
|
-
`persistence.recordReceipt`, and `persistence.afterToolCall` for awaited storage barriers.
|
|
96
|
-
Supply `persistence.saveMessage` to save the canonical UI message at each model-step boundary and
|
|
97
|
-
at termination, including reasoning and provider metadata. Spark awaits each checkpoint before the
|
|
98
|
-
next model request. Consume the run's stream while awaiting its result; the result settles after
|
|
99
|
-
the terminal checkpoint. A host run owner must keep consuming when a view detaches.
|
|
100
|
-
A failed barrier aborts the loop. The default model-step limit is 100; a limit returns
|
|
101
|
-
`status: "incomplete"`. A host must not automatically restart to bypass that limit.
|
|
102
|
-
|
|
103
|
-
Spark does not estimate or limit input tokens or automatically shorten conversation history.
|
|
104
|
-
The provider enforces its context limit; rejections follow the normal provider-error path.
|
|
105
|
-
Provider-reported usage and cost tracking are unchanged.
|
|
106
|
-
|
|
107
|
-
Screenshot and file APIs return serializable artifact references. `emitImage` and `emitFile` resolve
|
|
108
|
-
them through the host's artifact resolver. Keep references resolvable when reloading history, and
|
|
109
|
-
pass `createSparkTools(...)` to AI SDK's `convertToModelMessages` to reconstruct multimodal results.
|
|
110
|
-
Kakapo skill declarations are generated from its actual typed API during the build and packaged as
|
|
111
|
-
JavaScript strings; they do not need filesystem or Markdown loading in the browser.
|
|
112
|
-
|
|
113
|
-
## Local frontend
|
|
114
|
-
|
|
115
|
-
`LocalSparkChatTransport` accepts a host callback returning a `SparkAgentRun` from `runSparkAgent`.
|
|
116
|
-
The callback receives the standard AI SDK chat request, including its messages and abort signal.
|
|
117
|
-
It persists the initiating message, resolves the model and capabilities, and starts the shared loop.
|
|
118
|
-
Keep ownership of the active run outside the React renderer. Local browser runs end when the page
|
|
119
|
-
closes; `reconnectToStream` returns `null`.
|
|
120
|
-
|
|
121
|
-
## Remote frontend
|
|
122
|
-
|
|
123
|
-
```tsx
|
|
124
|
-
import { useChat } from "@ai-sdk/react";
|
|
125
|
-
import { SparkChatTransport } from "@womp/spark-agent/client";
|
|
126
|
-
import {
|
|
127
|
-
sparkDataPartSchemas,
|
|
128
|
-
sparkMessageMetadataSchema,
|
|
129
|
-
type SparkUIMessage,
|
|
130
|
-
} from "@womp/spark-agent/ui";
|
|
131
|
-
|
|
132
|
-
const transport = new SparkChatTransport({ api: "/agent" });
|
|
133
|
-
|
|
134
|
-
const chat = useChat<SparkUIMessage>({
|
|
135
|
-
id: conversationId,
|
|
136
|
-
messages: initialMessages,
|
|
137
|
-
transport,
|
|
138
|
-
resume: true,
|
|
139
|
-
messageMetadataSchema: sparkMessageMetadataSchema,
|
|
140
|
-
dataPartSchemas: sparkDataPartSchemas,
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Render `message.parts`. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool
|
|
145
|
-
outputs, approvals, and step boundaries. Spark adds typed `data-console`, `data-artifact`,
|
|
146
|
-
`data-run-status`, `data-step-metrics`, and `data-execution-receipt` parts. Receipts are emitted before
|
|
147
|
-
their storage barrier, keeping known effects visible even when saving the tool result fails.
|
|
148
|
-
|
|
149
|
-
An explicit Stop action must cancel the durable run before stopping local observation:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
await transport.cancelRun(conversationId);
|
|
153
|
-
await chat.stop();
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Closing or reloading the page only detaches the SSE subscriber. It does not cancel the run.
|
|
157
|
-
|
|
158
|
-
## HTTP and SSE
|
|
159
|
-
|
|
160
|
-
`SparkChatTransport` hides the durable run lifecycle from `useChat`:
|
|
161
|
-
|
|
162
|
-
```text
|
|
163
|
-
POST /agent/conversations/:conversationId/messages
|
|
164
|
-
PATCH /agent/conversations/:conversationId/settings
|
|
165
|
-
GET /agent/runs/:runId/stream?after=0
|
|
166
|
-
POST /agent/runs/:runId/cancel
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Conversation settings contain `modelId` and `reasoningEffort`. The standalone UI saves selector
|
|
170
|
-
changes immediately through `transport.saveConversationSettings(id, settings)` and restores them
|
|
171
|
-
per thread. Older threads fall back to their last recorded model (or the default) and medium effort;
|
|
172
|
-
unsupported choices fall back to supported settings. Assistant footers show the model recorded on
|
|
173
|
-
that turn, independently of the selector.
|
|
174
|
-
|
|
175
|
-
Custom stores must implement `saveConversationSettings` as a settings-only update, and preserve
|
|
176
|
-
stored settings during `saveConversation` so streaming history saves cannot overwrite preferences.
|
|
177
|
-
The built-in memory and SQLite stores do this without a database migration.
|
|
178
|
-
|
|
179
|
-
The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
type StoredSparkChunk = {
|
|
183
|
-
sequence: number;
|
|
184
|
-
conversationId: string;
|
|
185
|
-
runId: string;
|
|
186
|
-
createdAt: string;
|
|
187
|
-
chunk: SparkUIMessageChunk;
|
|
188
|
-
};
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
The transport unwraps each envelope and returns the contained `UIMessageChunk` to `useChat`.
|
|
192
|
-
Active assistant messages are replayed from sequence zero when reconnecting because `useChat`
|
|
193
|
-
reconstructs the in-progress message from the resumed stream.
|
|
194
|
-
|
|
195
|
-
## Embedding in an Express backend
|
|
196
|
-
|
|
197
|
-
Spark does not call `listen()` when imported. The host owns its HTTP server and mounts the router:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
import { createSparkAgentRouter } from "@womp/spark-agent/express";
|
|
201
|
-
import { SparkAgentService, createSparkRunExecutor } from "@womp/spark-agent/server";
|
|
202
|
-
|
|
203
|
-
const service = new SparkAgentService({
|
|
204
|
-
store,
|
|
205
|
-
executeRun: createSparkRunExecutor({
|
|
206
|
-
resolveModel,
|
|
207
|
-
createHost,
|
|
208
|
-
}),
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
app.use(
|
|
212
|
-
"/agent",
|
|
213
|
-
userAccess,
|
|
214
|
-
createSparkAgentRouter({
|
|
215
|
-
service,
|
|
216
|
-
artifactStore,
|
|
217
|
-
resolveOwnerId: (request) => request.user.id,
|
|
218
|
-
}),
|
|
219
|
-
);
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
`MemorySparkAgentStore` and `MemorySparkArtifactStore` are development implementations. Production
|
|
223
|
-
hosts must supply durable implementations backed by their database/object storage and must preserve
|
|
224
|
-
the ordering contract of `appendRunChunk()`.
|
|
225
|
-
|
|
226
|
-
## Standalone development server
|
|
227
|
-
|
|
228
|
-
The package includes a local server bound to `127.0.0.1:8787`:
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
spark-agent
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Configure the process with:
|
|
235
|
-
|
|
236
|
-
- `OPENAI_API_KEY`
|
|
237
|
-
- `ANTHROPIC_API_KEY`
|
|
238
|
-
- `GEMINI_API_KEY`
|
|
239
|
-
- `KAKAPO_URL` (defaults to `ws://127.0.0.1:5502`)
|
|
240
|
-
- `PORT` (defaults to `8787`)
|
|
241
|
-
- `SPARK_STORAGE_PATH` (defaults to `.spark/spark.sqlite`)
|
|
242
|
-
- `SPARK_SCENE_IDENTITY` (optional stable identity for the scene behind `KAKAPO_URL`)
|
|
243
|
-
|
|
244
|
-
The CLI automatically loads `.env` from its working directory when the file exists. Copy
|
|
245
|
-
`.env.example` to `.env`, then add the provider key you intend to use. Existing process environment
|
|
246
|
-
variables take precedence over values loaded from the file.
|
|
247
|
-
|
|
248
|
-
For repository development, `yarn dev` starts the Express server and Vite demo together. Open
|
|
249
|
-
`http://127.0.0.1:5173`.
|
|
250
|
-
|
|
251
|
-
Standalone scene edits use the existing `scene_state_patch` command followed by `save_scene` after
|
|
252
|
-
the patch is acknowledged. The durable operation journal records the separate mutation and save
|
|
253
|
-
outcomes. A failed or unknown save stops that operation with an explicit error, retaining any
|
|
254
|
-
applied work without replay. The two commands are not an atomic transaction, and a renderer save
|
|
255
|
-
acknowledgement proves only renderer-local history, not durable backend storage.
|
|
256
|
-
|
|
257
|
-
The standalone server persists conversations, run streams, and screenshot bytes in SQLite. Only
|
|
258
|
-
one local process can execute against a database. After a terminated process is replaced, startup
|
|
259
|
-
preserves recorded output and marks abandoned queued/running runs incomplete without replaying
|
|
260
|
-
scene edits. Pending approvals remain pending. Full operation reconciliation and run continuation
|
|
261
|
-
are still being integrated. Node 22 reports an experimental warning for its built-in SQLite module.
|
|
262
|
-
|
|
263
|
-
Set `SPARK_SCENE_IDENTITY` only when the host can verify that the value names the same durable
|
|
264
|
-
scene across renderer reconnects. Without it, Spark uses a process-scoped identity and does not
|
|
265
|
-
authorize interrupted scene mutations to continue after a server restart.
|
|
266
|
-
|
|
267
|
-
`createStandaloneSparkAgentApp(options)` is asynchronous. Await it before listening, and call
|
|
268
|
-
`await app.dispose()` on shutdown to settle active runs and release storage ownership.
|
|
269
|
-
|
|
270
|
-
This development branch currently installs `kakapo-sdk` from the sibling checkout. Run `yarn build`
|
|
271
|
-
in `../kakapo-sdk` before installing Spark dependencies. A registry version must replace this local
|
|
272
|
-
dependency before release; no new package has been published by this migration.
|
|
273
|
-
|
|
274
|
-
For the explicit fictional account profile, use `yarn dev:demo-account` or
|
|
275
|
-
`spark-agent --profile demo-account`. It adds the `account` skill and `womp.account.read`
|
|
276
|
-
beside the real local Kakapo scene API. Profile reads identify themselves as fictional fixtures;
|
|
277
|
-
credits, billing, orders, and other account services report unavailable. The default composition
|
|
278
|
-
omits account entirely. Neither profile supplies fake generation jobs, imports, or billing permission.
|
|
279
|
-
Embedded development servers can select `developmentProfile: "demo-account"` explicitly.
|
|
280
|
-
|
|
281
|
-
The same profile supplies two actual local material resources: `Development: Matte Blue` and
|
|
282
|
-
`Development: Polished Copper`. They are bundled JSON files under `src/server/fixtures/materials`
|
|
283
|
-
and imported through the normal SDK draft, not simulated scene effects. In `scene.edit`, await
|
|
284
|
-
`api.materialImport(name)`, then assign its numeric ID. The default profile has no library service.
|
|
285
|
-
|
|
286
|
-
## Run cost estimates
|
|
287
|
-
|
|
288
|
-
Prompt caching is enabled for every listed model: Claude Opus 5 and Fable 5 use
|
|
289
|
-
automatic 5-minute caching, and GPT-6 Astra uses implicit caching with a 30-minute TTL.
|
|
290
|
-
The providers manage the cache as message history grows across tool steps; no local
|
|
291
|
-
response cache is used. Cache hits require matching prefixes, sufficient prompt length,
|
|
292
|
-
and an unexpired entry. Cache writes can cost more than uncached input, while subsequent
|
|
293
|
-
cache reads are cheaper. The UI displays actual reported reads/writes, not assumed savings.
|
|
294
|
-
See [Claude caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
|
|
295
|
-
and [OpenAI caching](https://developers.openai.com/api/docs/guides/prompt-caching).
|
|
296
|
-
Restart the server after changing these settings; existing runs keep their original configuration.
|
|
297
|
-
|
|
298
|
-
The chat displays estimated API token cost in USD, updated after every completed model step.
|
|
299
|
-
Hover or focus the amount to inspect uncached input, cache reads/writes, and output costs. Output usage
|
|
300
|
-
already includes reasoning tokens. Image input is included in the provider's input token count.
|
|
301
|
-
|
|
302
|
-
`src/pricing.ts` contains standard direct API rates verified on September 9, 2026:
|
|
303
|
-
|
|
304
|
-
| Model | Input / 1M | Cache read / 1M | Cache write / 1M | Output / 1M |
|
|
305
|
-
| -------------- | ---------- | --------------- | --------------------- | ----------- |
|
|
306
|
-
| GPT-6 Astra | $10 | $1 | $12.50 | $50 |
|
|
307
|
-
| Claude Opus 5 | $5 | $0.50 | $6.25 (5m), $10 (1h) | $25 |
|
|
308
|
-
| Claude Fable 5 | $10 | $1 | $12.50 (5m), $20 (1h) | $50 |
|
|
309
|
-
|
|
310
|
-
Astra requests above 272,000 input tokens use 2x input/cache rates and 1.5x output rates.
|
|
311
|
-
This threshold is applied to each request before summing run costs. Claude's full context uses
|
|
312
|
-
standard rates. Sources: [Astra pricing](https://developers.openai.com/api/docs/models/gpt-6-astra),
|
|
313
|
-
[Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing).
|
|
314
|
-
|
|
315
|
-
Each `data-step-metrics` part stores a `cost` snapshot with the model, rate date, source, token
|
|
316
|
-
categories, rates, and amounts. The UI sums those stored amounts, so reconnection does not double
|
|
317
|
-
count steps and future price changes do not reprice recorded runs. Update the rate table and its
|
|
318
|
-
verification date when provider pricing changes.
|
|
319
|
-
|
|
320
|
-
The unfinished step is excluded until its usage arrives. Cancelled/failed runs show a partial
|
|
321
|
-
estimate. Missing usage, unknown models, missing cache TTLs, or provider sub-inferences without
|
|
322
|
-
separate attribution are marked unavailable, never silently priced as zero. Historical runs
|
|
323
|
-
without step pricing remain unavailable. Estimates exclude taxes, negotiated discounts, and scene
|
|
324
|
-
hosting; the provider bill is authoritative. Spark requests OpenAI's standard service tier.
|
|
325
|
-
|
|
326
|
-
## Execution security
|
|
327
|
-
|
|
328
|
-
The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js
|
|
329
|
-
or browser globals and receives only the composed capability APIs. Imports, filesystem access,
|
|
330
|
-
network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
|
|
331
|
-
execution time, source size, host calls, and data crossing the host bridge. Browser hosts can instead
|
|
332
|
-
choose native page execution, with the page-global access and interruption limitations described above.
|
|
333
|
-
|
|
334
|
-
QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
|
|
335
|
-
boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
|
|
336
|
-
session and tool call, enforce quotas, and avoid passing privileged functions through the runtime
|
|
337
|
-
bridge.
|
|
338
|
-
|
|
339
|
-
## Validation
|
|
340
|
-
|
|
341
|
-
```bash
|
|
342
|
-
yarn build
|
|
343
|
-
yarn typecheck
|
|
344
|
-
yarn test
|
|
345
|
-
yarn web:build
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## Release procedure
|
|
349
|
-
|
|
350
|
-
Releases use a manual package publication; no separate release service is required.
|
|
351
|
-
|
|
352
|
-
### Migration to 0.3
|
|
353
|
-
|
|
354
|
-
- The package root is browser-safe. Import `KakapoRuntime`, `createWompSceneApi`, and its enums
|
|
355
|
-
from `/kakapo`, and `QuickJSJavaScriptExecutor` from `/quickjs`.
|
|
356
|
-
- `runSparkAgent` takes the host's selected model, executor, capability composition, artifact
|
|
357
|
-
resolver, and persistence hooks. The host owns connection setup and disposal. The model sees
|
|
358
|
-
only `read_skill` and `execute_javascript`; domain methods live under the supplied `womp` APIs.
|
|
359
|
-
- Replace fixed scene/screenshot tool history with the host's history adapter. Use
|
|
360
|
-
`convertSparkMessages` when rebuilding model input from current Spark UI messages so artifact
|
|
361
|
-
references become model-visible content. Preserve old records; do not replay historical tools.
|
|
362
|
-
- Custom server executors receive `uiMessages` and convert them using their actual capability
|
|
363
|
-
tools. `SparkRunExecutionContext.modelMessages` has been removed.
|
|
364
|
-
- Custom JavaScript executors must honor cancellation and host-owned user waits. Callback-based
|
|
365
|
-
scene edits and artifact references work with both the native and QuickJS executors.
|
|
366
|
-
|
|
367
|
-
### 4.1.0
|
|
368
|
-
|
|
369
|
-
- `emitImage(artifact, { inspect: false })` displays an image without adding its bytes to model input, including restored history. Inspection remains the default.
|
|
370
|
-
- Generation instructions use completed outputs directly and avoid redundant listing or emission; explicit inspection and follow-up imports remain supported.
|
|
371
|
-
- Streamed tool labels are emitted only after their label field is complete. Hosts can consume `data-tool-input-label` while the rest of the input streams.
|
|
372
|
-
- Server cancellation waits for active execution to settle before reporting its terminal state.
|
|
373
|
-
- Includes the latest geometry material, scalar-port, and primitive improvements from main.
|
|
374
|
-
|
|
375
|
-
### 4.2.2
|
|
376
|
-
|
|
377
|
-
The published 4.2.1 was packed from the 4.2.0 sources and differs from 4.2.0 only in its version
|
|
378
|
-
number, so none of the changes below are in it. Use 4.2.2.
|
|
379
|
-
|
|
380
|
-
Compatible: both additions are optional, and a host that sets neither behaves as before except that a
|
|
381
|
-
run whose progress cannot be stored now ends instead of staying `running`.
|
|
382
|
-
|
|
383
|
-
- A run whose stream chunk, `running` status or ending cannot be stored ends `failed` with a sentence
|
|
384
|
-
instead of staying `running` until a restart. The final status and the conversation release are
|
|
385
|
-
retried in the background for about five minutes; a message or a Stop for that conversation stores
|
|
386
|
-
the ending at once.
|
|
387
|
-
- `SparkAgentServiceOptions.endOrphanedRuns` (default `false`): a message or a Stop ends an unfinished
|
|
388
|
-
active run that no execution in this process holds, with "This run stopped unexpectedly and could
|
|
389
|
-
not be finished." Turn it on only when one process owns the store; on a shared store it would end
|
|
390
|
-
another process's live runs.
|
|
391
|
-
- `SparkAgentStore.releaseActiveRun(conversationId, runId, updatedAt)` (optional): clears the active
|
|
392
|
-
run only while it still names that run. The memory and SQLite stores implement it. A store without
|
|
393
|
-
it has the read-and-save release serialised against the service's own submits, so a late retry
|
|
394
|
-
cannot overwrite a newer message.
|
|
395
|
-
|
|
396
|
-
### 4.2.0
|
|
397
|
-
|
|
398
|
-
Incompatible: a color that can only be a 0-1 one is now refused where it used to be stored as
|
|
399
|
-
near-black, and the `scene-editing` skill payload changed. Hosts that pin the skill surface in a
|
|
400
|
-
fixture must regenerate it.
|
|
401
|
-
|
|
402
|
-
- Model-facing colors state their domain in the declared parameter name: `nodeSetColor`,
|
|
403
|
-
`materialSetColor`, `materialSetSecondaryColor`, `materialSetSubsurfaceColor` and
|
|
404
|
-
`setBackgroundColor` take `rgb255`, and the internal type is `Color255`. The `scene-editing` skill
|
|
405
|
-
version is 30.
|
|
406
|
-
- A color whose every component is strictly between 0 and 1 and none an integer throws, naming the
|
|
407
|
-
0-255 conversion (`{r:0.2, g:0.4, b:0.6}` → "use `{r:51, g:102, b:153}` … or `{r:0, g:0, b:0}` for
|
|
408
|
-
black"). Nothing is auto-scaled: `{r:1, g:0, b:0}`, `{r:0, g:0, b:0}`, `{r:0.5, g:0, b:0}` and
|
|
409
|
-
every 0-255 color write exactly as before.
|
|
410
|
-
- `scene.health` accepts the object ids `scene.read()` publishes, including `0`; a value that is not
|
|
411
|
-
an id is refused with a sentence naming it instead of a serialized schema error. Ids belonging to
|
|
412
|
-
no Area (the root, a root-level Group, a Light) are dropped and reported rather than making the
|
|
413
|
-
whole reading `unverified`.
|
|
414
|
-
- A tool result can no longer cost a turn. Geometry diagnostics quote a source line once per line
|
|
415
|
-
and cap at 5 diagnostics and 4 KiB; the `execute_javascript` envelope bounds the error to 8 KiB and
|
|
416
|
-
the receipts to a 1 MiB model input, always as valid JSON. Only model input is bounded —
|
|
417
|
-
`persistence.afterToolCall`, the UI stream and the host's own receipts keep the original object.
|
|
418
|
-
- A scene-health receipt keeps its totals, limits and overflow flags whole and bounds the per-Area
|
|
419
|
-
rows to 8, affected and overflowing Areas first, naming the omission in `reason`. Health read for a
|
|
420
|
-
decision (`hasGeometryOverflow`, `hasSceneResourcePressure`, `options.onHealth`) still sees every
|
|
421
|
-
row, so no repair decision changes. A consumer fixture that recorded a receipt with more than 8
|
|
422
|
-
Areas will differ.
|
|
423
|
-
- `operations.generate` keeps a host's own cancellation message on a stopped generation instead of
|
|
424
|
-
replacing it with the bare abort reason.
|
|
425
|
-
|
|
426
|
-
### Publishing
|
|
427
|
-
|
|
428
|
-
1. Choose an unused package version after checking `npm view @womp/spark-agent versions --json`.
|
|
429
|
-
Update `package.json` and record public API changes and migration notes. Use a new minor version
|
|
430
|
-
for incompatible changes while the package is below 1.0.
|
|
431
|
-
2. On a supported Node version, run `yarn install --frozen-lockfile`, `yarn typecheck`, `yarn test`,
|
|
432
|
-
and `yarn web:build`. Do not bypass dependency engine checks. Commit the release changes and
|
|
433
|
-
record the commit used to build the package.
|
|
434
|
-
3. Run `yarn pack --filename spark-agent-release.tgz`. Inspect the archive contents and install
|
|
435
|
-
that archive in the consuming application. Verify packaged declarations and skills, browser
|
|
436
|
-
import boundaries, the production build, and the applicable integration cases without source
|
|
437
|
-
aliases. Keep the archive outside Git and do not rebuild or change it after verification.
|
|
438
|
-
4. Publish the verified archive with `npm publish ./spark-agent-release.tgz --access public`.
|
|
439
|
-
If the response is uncertain, inspect the registry's version and integrity before retrying.
|
|
440
|
-
Verify the published version and archive integrity, then tag the recorded commit as `v<version>`
|
|
441
|
-
and push the release commit and tag. An already published version cannot be replaced; corrections
|
|
442
|
-
need a new version.
|
|
443
|
-
5. In the consuming application, use `yarn add @womp/spark-agent@<version> --exact`. Commit its
|
|
444
|
-
manifest and lockfile together, and verify a clean frozen-lockfile installation and production
|
|
445
|
-
build. Remove the temporary archive dependency. Application deployment and merge requirements
|
|
446
|
-
remain separate from package publication.
|
|
447
|
-
|
|
448
|
-
## Layout
|
|
449
|
-
|
|
450
|
-
- `src/agent.ts` — AI SDK stream and Spark tool orchestration
|
|
451
|
-
- `src/ui.ts` — browser-safe protocol types and validation schemas
|
|
452
|
-
- `src/client.ts` — durable `ChatTransport` implementation
|
|
453
|
-
- `src/server/` — service, storage contracts, Express adapter, artifacts, and standalone composition
|
|
454
|
-
- `src/runtime.ts` — Kakapo scene connections and edit transactions
|
|
455
|
-
- `src/quickjs-executor.ts` — isolated JavaScript execution and host bridge
|
|
456
|
-
- `src/womp-scene-api.ts` — Womp-compatible Kakapo scene API
|
|
457
|
-
- `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
|
+
## Run a prompt from the command line
|
|
20
|
+
|
|
21
|
+
With the Spark server already running:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
yarn agent-run --prompt "Make a cube" --model gpt-5.6-luna --reasoning max
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `--conversation <id>` to continue a thread and `--output <new-directory>` to save
|
|
28
|
+
`result.json`, `transcript.json` and original run artifacts under `artifacts/`.
|
|
29
|
+
For local runs, use the git-ignored `.spark/` directory, with a fresh subdirectory per run:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
yarn agent-run --prompt "Make rainbow-colored boxes" --output .spark/agent-runs/rainbow-boxes
|
|
33
|
+
yarn agent-run --prompt "Model this object using both views" --image front.png --image side.jpg --output .spark/agent-runs/reference-model
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Repeat `--image <path>` for local PNG, JPEG, WebP or GIF references, in argument order.
|
|
37
|
+
The required prompt and all images are sent in one user message. Files, format signatures,
|
|
38
|
+
and the full JSON request size (including base64 overhead) are checked before contacting the server.
|
|
39
|
+
The CLI uses the server's default 25 MiB request limit; a custom server or provider may impose
|
|
40
|
+
stricter limits. Images are never resized, and remote URLs are not accepted.
|
|
41
|
+
With `--output`, original bytes are saved under `inputs/`; `input.json` and the result's `input`
|
|
42
|
+
field list ordered original filenames, MIME types and relative paths. The submitted message in
|
|
43
|
+
`transcript.json` uses those paths instead of inline data. Generated artifacts stay in `artifacts/`.
|
|
44
|
+
|
|
45
|
+
Without output, artifacts remain server references. The CLI never clears the scene or
|
|
46
|
+
takes extra screenshots. Defaults are `gpt-6-astra`, `high`, and
|
|
47
|
+
`--api http://127.0.0.1:8787/agent`; unsupported settings are rejected.
|
|
48
|
+
|
|
49
|
+
Progress goes to stderr; one JSON result goes to stdout. Use `yarn --silent agent-run`
|
|
50
|
+
or the installed `agent-run` executable when parsing stdout (Yarn otherwise adds banners).
|
|
51
|
+
Usage comes from the provider, with per-step USD estimates; missing usage is not estimated.
|
|
52
|
+
Exit codes: 0 completed, 1 run/transport/export failure, 2 invalid arguments,
|
|
53
|
+
3 requires user action (continue in the UI), 130 interrupted. Interrupts attempt to cancel
|
|
54
|
+
only this invocation's run; a failed cancellation is reported and the run may still be active.
|
|
55
|
+
Output directories must not already exist. Artifact manifests include IDs, media types,
|
|
56
|
+
server URLs and relative paths; failed downloads retain their references and report errors.
|
|
57
|
+
Legacy inline base64 uploads in transcripts are replaced with `[inline data omitted]`.
|
|
58
|
+
|
|
59
|
+
## Package entry points
|
|
60
|
+
|
|
61
|
+
- `@womp/spark-agent` — browser-safe model loop, two tools, persistence hooks, and shared contracts
|
|
62
|
+
- `@womp/spark-agent/browser` — native JavaScript executor and local chat transport
|
|
63
|
+
- `@womp/spark-agent/capabilities` — capability composition and skill contracts
|
|
64
|
+
- `@womp/spark-agent/kakapo` — direct Kakapo runtime and scene capability
|
|
65
|
+
- `@womp/spark-agent/quickjs` — optional isolated JavaScript executor
|
|
66
|
+
- `@womp/spark-agent/ui` — browser-safe UI message types, schemas, and model catalog
|
|
67
|
+
- `@womp/spark-agent/client` — `SparkChatTransport` and HTTP client types
|
|
68
|
+
- `@womp/spark-agent/server` — run service, store interfaces, executor, and development stores
|
|
69
|
+
- `@womp/spark-agent/express` — mountable Express 5 router
|
|
70
|
+
|
|
71
|
+
The root, `/browser`, `/ui`, `/capabilities`, and `/client` entry points do not import QuickJS,
|
|
72
|
+
Kakapo, Express, or React at runtime.
|
|
73
|
+
|
|
74
|
+
## Running in a host application
|
|
75
|
+
|
|
76
|
+
`runSparkAgent` accepts a configured model, model messages, an executor, a capability composition,
|
|
77
|
+
and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
|
|
78
|
+
Skills carry instructions, declarations, and JavaScript examples. The native executor runs in the
|
|
79
|
+
page realm; it is not a sandbox and cannot interrupt a synchronous infinite loop.
|
|
80
|
+
|
|
81
|
+
Compose only supported capabilities with `composeCapabilities({ scene, account, ... })`. Each
|
|
82
|
+
capability supplies its skills and `createApi(context)`; the returned API is bound under `womp`.
|
|
83
|
+
Every invocation receives a cancellation signal and an awaited `recordReceipt` hook. Capabilities
|
|
84
|
+
own their transactions and must check cancellation at their commit boundary.
|
|
85
|
+
|
|
86
|
+
For a real user interaction, call `context.waitForUser(label, async signal => ...)` outside mutation
|
|
87
|
+
locks. The run stays active with `requires_action` progress and resumes the same invocation after
|
|
88
|
+
input. Native and QuickJS executors exclude this wait from their execution budget; service work
|
|
89
|
+
continues to consume time, and QuickJS guest computation still counts even if an interaction was
|
|
90
|
+
not awaited. Abort settles the wait and late answers cannot revive it. The UI host must also reject
|
|
91
|
+
competing requests for a shared dialog and match callbacks to the original interaction identity.
|
|
92
|
+
Custom executors must honor the optional host-owned `userWait` argument to `execute`.
|
|
93
|
+
|
|
94
|
+
Persist the user message before starting a run. Supply `persistence.beforeToolCall`,
|
|
95
|
+
`persistence.recordReceipt`, and `persistence.afterToolCall` for awaited storage barriers.
|
|
96
|
+
Supply `persistence.saveMessage` to save the canonical UI message at each model-step boundary and
|
|
97
|
+
at termination, including reasoning and provider metadata. Spark awaits each checkpoint before the
|
|
98
|
+
next model request. Consume the run's stream while awaiting its result; the result settles after
|
|
99
|
+
the terminal checkpoint. A host run owner must keep consuming when a view detaches.
|
|
100
|
+
A failed barrier aborts the loop. The default model-step limit is 100; a limit returns
|
|
101
|
+
`status: "incomplete"`. A host must not automatically restart to bypass that limit.
|
|
102
|
+
|
|
103
|
+
Spark does not estimate or limit input tokens or automatically shorten conversation history.
|
|
104
|
+
The provider enforces its context limit; rejections follow the normal provider-error path.
|
|
105
|
+
Provider-reported usage and cost tracking are unchanged.
|
|
106
|
+
|
|
107
|
+
Screenshot and file APIs return serializable artifact references. `emitImage` and `emitFile` resolve
|
|
108
|
+
them through the host's artifact resolver. Keep references resolvable when reloading history, and
|
|
109
|
+
pass `createSparkTools(...)` to AI SDK's `convertToModelMessages` to reconstruct multimodal results.
|
|
110
|
+
Kakapo skill declarations are generated from its actual typed API during the build and packaged as
|
|
111
|
+
JavaScript strings; they do not need filesystem or Markdown loading in the browser.
|
|
112
|
+
|
|
113
|
+
## Local frontend
|
|
114
|
+
|
|
115
|
+
`LocalSparkChatTransport` accepts a host callback returning a `SparkAgentRun` from `runSparkAgent`.
|
|
116
|
+
The callback receives the standard AI SDK chat request, including its messages and abort signal.
|
|
117
|
+
It persists the initiating message, resolves the model and capabilities, and starts the shared loop.
|
|
118
|
+
Keep ownership of the active run outside the React renderer. Local browser runs end when the page
|
|
119
|
+
closes; `reconnectToStream` returns `null`.
|
|
120
|
+
|
|
121
|
+
## Remote frontend
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { useChat } from "@ai-sdk/react";
|
|
125
|
+
import { SparkChatTransport } from "@womp/spark-agent/client";
|
|
126
|
+
import {
|
|
127
|
+
sparkDataPartSchemas,
|
|
128
|
+
sparkMessageMetadataSchema,
|
|
129
|
+
type SparkUIMessage,
|
|
130
|
+
} from "@womp/spark-agent/ui";
|
|
131
|
+
|
|
132
|
+
const transport = new SparkChatTransport({ api: "/agent" });
|
|
133
|
+
|
|
134
|
+
const chat = useChat<SparkUIMessage>({
|
|
135
|
+
id: conversationId,
|
|
136
|
+
messages: initialMessages,
|
|
137
|
+
transport,
|
|
138
|
+
resume: true,
|
|
139
|
+
messageMetadataSchema: sparkMessageMetadataSchema,
|
|
140
|
+
dataPartSchemas: sparkDataPartSchemas,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Render `message.parts`. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool
|
|
145
|
+
outputs, approvals, and step boundaries. Spark adds typed `data-console`, `data-artifact`,
|
|
146
|
+
`data-run-status`, `data-step-metrics`, and `data-execution-receipt` parts. Receipts are emitted before
|
|
147
|
+
their storage barrier, keeping known effects visible even when saving the tool result fails.
|
|
148
|
+
|
|
149
|
+
An explicit Stop action must cancel the durable run before stopping local observation:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
await transport.cancelRun(conversationId);
|
|
153
|
+
await chat.stop();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Closing or reloading the page only detaches the SSE subscriber. It does not cancel the run.
|
|
157
|
+
|
|
158
|
+
## HTTP and SSE
|
|
159
|
+
|
|
160
|
+
`SparkChatTransport` hides the durable run lifecycle from `useChat`:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
POST /agent/conversations/:conversationId/messages
|
|
164
|
+
PATCH /agent/conversations/:conversationId/settings
|
|
165
|
+
GET /agent/runs/:runId/stream?after=0
|
|
166
|
+
POST /agent/runs/:runId/cancel
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Conversation settings contain `modelId` and `reasoningEffort`. The standalone UI saves selector
|
|
170
|
+
changes immediately through `transport.saveConversationSettings(id, settings)` and restores them
|
|
171
|
+
per thread. Older threads fall back to their last recorded model (or the default) and medium effort;
|
|
172
|
+
unsupported choices fall back to supported settings. Assistant footers show the model recorded on
|
|
173
|
+
that turn, independently of the selector.
|
|
174
|
+
|
|
175
|
+
Custom stores must implement `saveConversationSettings` as a settings-only update, and preserve
|
|
176
|
+
stored settings during `saveConversation` so streaming history saves cannot overwrite preferences.
|
|
177
|
+
The built-in memory and SQLite stores do this without a database migration.
|
|
178
|
+
|
|
179
|
+
The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
type StoredSparkChunk = {
|
|
183
|
+
sequence: number;
|
|
184
|
+
conversationId: string;
|
|
185
|
+
runId: string;
|
|
186
|
+
createdAt: string;
|
|
187
|
+
chunk: SparkUIMessageChunk;
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The transport unwraps each envelope and returns the contained `UIMessageChunk` to `useChat`.
|
|
192
|
+
Active assistant messages are replayed from sequence zero when reconnecting because `useChat`
|
|
193
|
+
reconstructs the in-progress message from the resumed stream.
|
|
194
|
+
|
|
195
|
+
## Embedding in an Express backend
|
|
196
|
+
|
|
197
|
+
Spark does not call `listen()` when imported. The host owns its HTTP server and mounts the router:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { createSparkAgentRouter } from "@womp/spark-agent/express";
|
|
201
|
+
import { SparkAgentService, createSparkRunExecutor } from "@womp/spark-agent/server";
|
|
202
|
+
|
|
203
|
+
const service = new SparkAgentService({
|
|
204
|
+
store,
|
|
205
|
+
executeRun: createSparkRunExecutor({
|
|
206
|
+
resolveModel,
|
|
207
|
+
createHost,
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
app.use(
|
|
212
|
+
"/agent",
|
|
213
|
+
userAccess,
|
|
214
|
+
createSparkAgentRouter({
|
|
215
|
+
service,
|
|
216
|
+
artifactStore,
|
|
217
|
+
resolveOwnerId: (request) => request.user.id,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`MemorySparkAgentStore` and `MemorySparkArtifactStore` are development implementations. Production
|
|
223
|
+
hosts must supply durable implementations backed by their database/object storage and must preserve
|
|
224
|
+
the ordering contract of `appendRunChunk()`.
|
|
225
|
+
|
|
226
|
+
## Standalone development server
|
|
227
|
+
|
|
228
|
+
The package includes a local server bound to `127.0.0.1:8787`:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
spark-agent
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Configure the process with:
|
|
235
|
+
|
|
236
|
+
- `OPENAI_API_KEY`
|
|
237
|
+
- `ANTHROPIC_API_KEY`
|
|
238
|
+
- `GEMINI_API_KEY`
|
|
239
|
+
- `KAKAPO_URL` (defaults to `ws://127.0.0.1:5502`)
|
|
240
|
+
- `PORT` (defaults to `8787`)
|
|
241
|
+
- `SPARK_STORAGE_PATH` (defaults to `.spark/spark.sqlite`)
|
|
242
|
+
- `SPARK_SCENE_IDENTITY` (optional stable identity for the scene behind `KAKAPO_URL`)
|
|
243
|
+
|
|
244
|
+
The CLI automatically loads `.env` from its working directory when the file exists. Copy
|
|
245
|
+
`.env.example` to `.env`, then add the provider key you intend to use. Existing process environment
|
|
246
|
+
variables take precedence over values loaded from the file.
|
|
247
|
+
|
|
248
|
+
For repository development, `yarn dev` starts the Express server and Vite demo together. Open
|
|
249
|
+
`http://127.0.0.1:5173`.
|
|
250
|
+
|
|
251
|
+
Standalone scene edits use the existing `scene_state_patch` command followed by `save_scene` after
|
|
252
|
+
the patch is acknowledged. The durable operation journal records the separate mutation and save
|
|
253
|
+
outcomes. A failed or unknown save stops that operation with an explicit error, retaining any
|
|
254
|
+
applied work without replay. The two commands are not an atomic transaction, and a renderer save
|
|
255
|
+
acknowledgement proves only renderer-local history, not durable backend storage.
|
|
256
|
+
|
|
257
|
+
The standalone server persists conversations, run streams, and screenshot bytes in SQLite. Only
|
|
258
|
+
one local process can execute against a database. After a terminated process is replaced, startup
|
|
259
|
+
preserves recorded output and marks abandoned queued/running runs incomplete without replaying
|
|
260
|
+
scene edits. Pending approvals remain pending. Full operation reconciliation and run continuation
|
|
261
|
+
are still being integrated. Node 22 reports an experimental warning for its built-in SQLite module.
|
|
262
|
+
|
|
263
|
+
Set `SPARK_SCENE_IDENTITY` only when the host can verify that the value names the same durable
|
|
264
|
+
scene across renderer reconnects. Without it, Spark uses a process-scoped identity and does not
|
|
265
|
+
authorize interrupted scene mutations to continue after a server restart.
|
|
266
|
+
|
|
267
|
+
`createStandaloneSparkAgentApp(options)` is asynchronous. Await it before listening, and call
|
|
268
|
+
`await app.dispose()` on shutdown to settle active runs and release storage ownership.
|
|
269
|
+
|
|
270
|
+
This development branch currently installs `kakapo-sdk` from the sibling checkout. Run `yarn build`
|
|
271
|
+
in `../kakapo-sdk` before installing Spark dependencies. A registry version must replace this local
|
|
272
|
+
dependency before release; no new package has been published by this migration.
|
|
273
|
+
|
|
274
|
+
For the explicit fictional account profile, use `yarn dev:demo-account` or
|
|
275
|
+
`spark-agent --profile demo-account`. It adds the `account` skill and `womp.account.read`
|
|
276
|
+
beside the real local Kakapo scene API. Profile reads identify themselves as fictional fixtures;
|
|
277
|
+
credits, billing, orders, and other account services report unavailable. The default composition
|
|
278
|
+
omits account entirely. Neither profile supplies fake generation jobs, imports, or billing permission.
|
|
279
|
+
Embedded development servers can select `developmentProfile: "demo-account"` explicitly.
|
|
280
|
+
|
|
281
|
+
The same profile supplies two actual local material resources: `Development: Matte Blue` and
|
|
282
|
+
`Development: Polished Copper`. They are bundled JSON files under `src/server/fixtures/materials`
|
|
283
|
+
and imported through the normal SDK draft, not simulated scene effects. In `scene.edit`, await
|
|
284
|
+
`api.materialImport(name)`, then assign its numeric ID. The default profile has no library service.
|
|
285
|
+
|
|
286
|
+
## Run cost estimates
|
|
287
|
+
|
|
288
|
+
Prompt caching is enabled for every listed model: Claude Opus 5 and Fable 5 use
|
|
289
|
+
automatic 5-minute caching, and GPT-6 Astra uses implicit caching with a 30-minute TTL.
|
|
290
|
+
The providers manage the cache as message history grows across tool steps; no local
|
|
291
|
+
response cache is used. Cache hits require matching prefixes, sufficient prompt length,
|
|
292
|
+
and an unexpired entry. Cache writes can cost more than uncached input, while subsequent
|
|
293
|
+
cache reads are cheaper. The UI displays actual reported reads/writes, not assumed savings.
|
|
294
|
+
See [Claude caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
|
|
295
|
+
and [OpenAI caching](https://developers.openai.com/api/docs/guides/prompt-caching).
|
|
296
|
+
Restart the server after changing these settings; existing runs keep their original configuration.
|
|
297
|
+
|
|
298
|
+
The chat displays estimated API token cost in USD, updated after every completed model step.
|
|
299
|
+
Hover or focus the amount to inspect uncached input, cache reads/writes, and output costs. Output usage
|
|
300
|
+
already includes reasoning tokens. Image input is included in the provider's input token count.
|
|
301
|
+
|
|
302
|
+
`src/pricing.ts` contains standard direct API rates verified on September 9, 2026:
|
|
303
|
+
|
|
304
|
+
| Model | Input / 1M | Cache read / 1M | Cache write / 1M | Output / 1M |
|
|
305
|
+
| -------------- | ---------- | --------------- | --------------------- | ----------- |
|
|
306
|
+
| GPT-6 Astra | $10 | $1 | $12.50 | $50 |
|
|
307
|
+
| Claude Opus 5 | $5 | $0.50 | $6.25 (5m), $10 (1h) | $25 |
|
|
308
|
+
| Claude Fable 5 | $10 | $1 | $12.50 (5m), $20 (1h) | $50 |
|
|
309
|
+
|
|
310
|
+
Astra requests above 272,000 input tokens use 2x input/cache rates and 1.5x output rates.
|
|
311
|
+
This threshold is applied to each request before summing run costs. Claude's full context uses
|
|
312
|
+
standard rates. Sources: [Astra pricing](https://developers.openai.com/api/docs/models/gpt-6-astra),
|
|
313
|
+
[Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing).
|
|
314
|
+
|
|
315
|
+
Each `data-step-metrics` part stores a `cost` snapshot with the model, rate date, source, token
|
|
316
|
+
categories, rates, and amounts. The UI sums those stored amounts, so reconnection does not double
|
|
317
|
+
count steps and future price changes do not reprice recorded runs. Update the rate table and its
|
|
318
|
+
verification date when provider pricing changes.
|
|
319
|
+
|
|
320
|
+
The unfinished step is excluded until its usage arrives. Cancelled/failed runs show a partial
|
|
321
|
+
estimate. Missing usage, unknown models, missing cache TTLs, or provider sub-inferences without
|
|
322
|
+
separate attribution are marked unavailable, never silently priced as zero. Historical runs
|
|
323
|
+
without step pricing remain unavailable. Estimates exclude taxes, negotiated discounts, and scene
|
|
324
|
+
hosting; the provider bill is authoritative. Spark requests OpenAI's standard service tier.
|
|
325
|
+
|
|
326
|
+
## Execution security
|
|
327
|
+
|
|
328
|
+
The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js
|
|
329
|
+
or browser globals and receives only the composed capability APIs. Imports, filesystem access,
|
|
330
|
+
network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
|
|
331
|
+
execution time, source size, host calls, and data crossing the host bridge. Browser hosts can instead
|
|
332
|
+
choose native page execution, with the page-global access and interruption limitations described above.
|
|
333
|
+
|
|
334
|
+
QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
|
|
335
|
+
boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
|
|
336
|
+
session and tool call, enforce quotas, and avoid passing privileged functions through the runtime
|
|
337
|
+
bridge.
|
|
338
|
+
|
|
339
|
+
## Validation
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
yarn build
|
|
343
|
+
yarn typecheck
|
|
344
|
+
yarn test
|
|
345
|
+
yarn web:build
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Release procedure
|
|
349
|
+
|
|
350
|
+
Releases use a manual package publication; no separate release service is required.
|
|
351
|
+
|
|
352
|
+
### Migration to 0.3
|
|
353
|
+
|
|
354
|
+
- The package root is browser-safe. Import `KakapoRuntime`, `createWompSceneApi`, and its enums
|
|
355
|
+
from `/kakapo`, and `QuickJSJavaScriptExecutor` from `/quickjs`.
|
|
356
|
+
- `runSparkAgent` takes the host's selected model, executor, capability composition, artifact
|
|
357
|
+
resolver, and persistence hooks. The host owns connection setup and disposal. The model sees
|
|
358
|
+
only `read_skill` and `execute_javascript`; domain methods live under the supplied `womp` APIs.
|
|
359
|
+
- Replace fixed scene/screenshot tool history with the host's history adapter. Use
|
|
360
|
+
`convertSparkMessages` when rebuilding model input from current Spark UI messages so artifact
|
|
361
|
+
references become model-visible content. Preserve old records; do not replay historical tools.
|
|
362
|
+
- Custom server executors receive `uiMessages` and convert them using their actual capability
|
|
363
|
+
tools. `SparkRunExecutionContext.modelMessages` has been removed.
|
|
364
|
+
- Custom JavaScript executors must honor cancellation and host-owned user waits. Callback-based
|
|
365
|
+
scene edits and artifact references work with both the native and QuickJS executors.
|
|
366
|
+
|
|
367
|
+
### 4.1.0
|
|
368
|
+
|
|
369
|
+
- `emitImage(artifact, { inspect: false })` displays an image without adding its bytes to model input, including restored history. Inspection remains the default.
|
|
370
|
+
- Generation instructions use completed outputs directly and avoid redundant listing or emission; explicit inspection and follow-up imports remain supported.
|
|
371
|
+
- Streamed tool labels are emitted only after their label field is complete. Hosts can consume `data-tool-input-label` while the rest of the input streams.
|
|
372
|
+
- Server cancellation waits for active execution to settle before reporting its terminal state.
|
|
373
|
+
- Includes the latest geometry material, scalar-port, and primitive improvements from main.
|
|
374
|
+
|
|
375
|
+
### 4.2.2
|
|
376
|
+
|
|
377
|
+
The published 4.2.1 was packed from the 4.2.0 sources and differs from 4.2.0 only in its version
|
|
378
|
+
number, so none of the changes below are in it. Use 4.2.2.
|
|
379
|
+
|
|
380
|
+
Compatible: both additions are optional, and a host that sets neither behaves as before except that a
|
|
381
|
+
run whose progress cannot be stored now ends instead of staying `running`.
|
|
382
|
+
|
|
383
|
+
- A run whose stream chunk, `running` status or ending cannot be stored ends `failed` with a sentence
|
|
384
|
+
instead of staying `running` until a restart. The final status and the conversation release are
|
|
385
|
+
retried in the background for about five minutes; a message or a Stop for that conversation stores
|
|
386
|
+
the ending at once.
|
|
387
|
+
- `SparkAgentServiceOptions.endOrphanedRuns` (default `false`): a message or a Stop ends an unfinished
|
|
388
|
+
active run that no execution in this process holds, with "This run stopped unexpectedly and could
|
|
389
|
+
not be finished." Turn it on only when one process owns the store; on a shared store it would end
|
|
390
|
+
another process's live runs.
|
|
391
|
+
- `SparkAgentStore.releaseActiveRun(conversationId, runId, updatedAt)` (optional): clears the active
|
|
392
|
+
run only while it still names that run. The memory and SQLite stores implement it. A store without
|
|
393
|
+
it has the read-and-save release serialised against the service's own submits, so a late retry
|
|
394
|
+
cannot overwrite a newer message.
|
|
395
|
+
|
|
396
|
+
### 4.2.0
|
|
397
|
+
|
|
398
|
+
Incompatible: a color that can only be a 0-1 one is now refused where it used to be stored as
|
|
399
|
+
near-black, and the `scene-editing` skill payload changed. Hosts that pin the skill surface in a
|
|
400
|
+
fixture must regenerate it.
|
|
401
|
+
|
|
402
|
+
- Model-facing colors state their domain in the declared parameter name: `nodeSetColor`,
|
|
403
|
+
`materialSetColor`, `materialSetSecondaryColor`, `materialSetSubsurfaceColor` and
|
|
404
|
+
`setBackgroundColor` take `rgb255`, and the internal type is `Color255`. The `scene-editing` skill
|
|
405
|
+
version is 30.
|
|
406
|
+
- A color whose every component is strictly between 0 and 1 and none an integer throws, naming the
|
|
407
|
+
0-255 conversion (`{r:0.2, g:0.4, b:0.6}` → "use `{r:51, g:102, b:153}` … or `{r:0, g:0, b:0}` for
|
|
408
|
+
black"). Nothing is auto-scaled: `{r:1, g:0, b:0}`, `{r:0, g:0, b:0}`, `{r:0.5, g:0, b:0}` and
|
|
409
|
+
every 0-255 color write exactly as before.
|
|
410
|
+
- `scene.health` accepts the object ids `scene.read()` publishes, including `0`; a value that is not
|
|
411
|
+
an id is refused with a sentence naming it instead of a serialized schema error. Ids belonging to
|
|
412
|
+
no Area (the root, a root-level Group, a Light) are dropped and reported rather than making the
|
|
413
|
+
whole reading `unverified`.
|
|
414
|
+
- A tool result can no longer cost a turn. Geometry diagnostics quote a source line once per line
|
|
415
|
+
and cap at 5 diagnostics and 4 KiB; the `execute_javascript` envelope bounds the error to 8 KiB and
|
|
416
|
+
the receipts to a 1 MiB model input, always as valid JSON. Only model input is bounded —
|
|
417
|
+
`persistence.afterToolCall`, the UI stream and the host's own receipts keep the original object.
|
|
418
|
+
- A scene-health receipt keeps its totals, limits and overflow flags whole and bounds the per-Area
|
|
419
|
+
rows to 8, affected and overflowing Areas first, naming the omission in `reason`. Health read for a
|
|
420
|
+
decision (`hasGeometryOverflow`, `hasSceneResourcePressure`, `options.onHealth`) still sees every
|
|
421
|
+
row, so no repair decision changes. A consumer fixture that recorded a receipt with more than 8
|
|
422
|
+
Areas will differ.
|
|
423
|
+
- `operations.generate` keeps a host's own cancellation message on a stopped generation instead of
|
|
424
|
+
replacing it with the bare abort reason.
|
|
425
|
+
|
|
426
|
+
### Publishing
|
|
427
|
+
|
|
428
|
+
1. Choose an unused package version after checking `npm view @womp/spark-agent versions --json`.
|
|
429
|
+
Update `package.json` and record public API changes and migration notes. Use a new minor version
|
|
430
|
+
for incompatible changes while the package is below 1.0.
|
|
431
|
+
2. On a supported Node version, run `yarn install --frozen-lockfile`, `yarn typecheck`, `yarn test`,
|
|
432
|
+
and `yarn web:build`. Do not bypass dependency engine checks. Commit the release changes and
|
|
433
|
+
record the commit used to build the package.
|
|
434
|
+
3. Run `yarn pack --filename spark-agent-release.tgz`. Inspect the archive contents and install
|
|
435
|
+
that archive in the consuming application. Verify packaged declarations and skills, browser
|
|
436
|
+
import boundaries, the production build, and the applicable integration cases without source
|
|
437
|
+
aliases. Keep the archive outside Git and do not rebuild or change it after verification.
|
|
438
|
+
4. Publish the verified archive with `npm publish ./spark-agent-release.tgz --access public`.
|
|
439
|
+
If the response is uncertain, inspect the registry's version and integrity before retrying.
|
|
440
|
+
Verify the published version and archive integrity, then tag the recorded commit as `v<version>`
|
|
441
|
+
and push the release commit and tag. An already published version cannot be replaced; corrections
|
|
442
|
+
need a new version.
|
|
443
|
+
5. In the consuming application, use `yarn add @womp/spark-agent@<version> --exact`. Commit its
|
|
444
|
+
manifest and lockfile together, and verify a clean frozen-lockfile installation and production
|
|
445
|
+
build. Remove the temporary archive dependency. Application deployment and merge requirements
|
|
446
|
+
remain separate from package publication.
|
|
447
|
+
|
|
448
|
+
## Layout
|
|
449
|
+
|
|
450
|
+
- `src/agent.ts` — AI SDK stream and Spark tool orchestration
|
|
451
|
+
- `src/ui.ts` — browser-safe protocol types and validation schemas
|
|
452
|
+
- `src/client.ts` — durable `ChatTransport` implementation
|
|
453
|
+
- `src/server/` — service, storage contracts, Express adapter, artifacts, and standalone composition
|
|
454
|
+
- `src/runtime.ts` — Kakapo scene connections and edit transactions
|
|
455
|
+
- `src/quickjs-executor.ts` — isolated JavaScript execution and host bridge
|
|
456
|
+
- `src/womp-scene-api.ts` — Womp-compatible Kakapo scene API
|
|
457
|
+
- `web/src/App.tsx` — reference `useChat<SparkUIMessage>` application
|