@womp/spark-agent 5.12.1 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +221 -0
- package/README.md +119 -240
- package/dist/agent-tools.js +3 -1
- package/dist/agent.d.ts +0 -1
- package/dist/agent.js +2 -11
- package/dist/bounded-output.d.ts +11 -2
- package/dist/bounded-output.js +21 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/model-history.d.ts +1 -1
- package/dist/model-history.js +35 -5
- package/dist/prompt.d.ts +8 -1
- package/dist/prompt.js +15 -2
- package/dist/server/executor.d.ts +1 -1
- package/dist/server/executor.js +0 -1
- package/dist/server/service.js +2 -1
- package/dist/ui.d.ts +13 -1
- package/dist/ui.js +7 -1
- package/dist/wompify.d.ts +5 -2
- package/dist/wompify.js +6 -7
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to `@womp/spark-agent`, newest first. Versions follow
|
|
4
|
+
[Semantic Versioning](https://semver.org/). Add an entry with each version bump; describe what
|
|
5
|
+
changed for hosts and users, and how to migrate across breaking changes.
|
|
6
|
+
|
|
7
|
+
## 6.0.1 — 2026-10-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Tool results reach the model with sorted keys, and `convertSparkMessages` replays stored tool
|
|
12
|
+
inputs as their schema validates them. History that a host reads back from storage that reorders
|
|
13
|
+
keys (Swan's Postgres JSONB thread rows) now matches what the live run sent, so turn starts in
|
|
14
|
+
Swan and Crow keep the provider prompt cache. Hosts need no change beyond the upgrade.
|
|
15
|
+
|
|
16
|
+
## 6.0.0 — 2026-10-07
|
|
17
|
+
|
|
18
|
+
### Breaking
|
|
19
|
+
|
|
20
|
+
- Removed `SparkAgentOptions.context`, the `createHost` `context` result, and `wompifyContext`.
|
|
21
|
+
Hosts send `sparkSystemPrompt(skills)`, which stays identical across a conversation so provider
|
|
22
|
+
prompt caches hit.
|
|
23
|
+
- `wompifyPrompt` takes a single request object including `referenceNamePrefix`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `data-selection` user message part (`{ nodeIds }`) for the editor selection sent with a message.
|
|
28
|
+
|
|
29
|
+
### Migrating to 6.0
|
|
30
|
+
|
|
31
|
+
- Stop passing per-run state in `context`. Attach the editor selection to the user message as a
|
|
32
|
+
`data-selection` part; send other changing state in messages.
|
|
33
|
+
- Replace `wompifyContext(...)` and positional `wompifyPrompt(...)` calls with
|
|
34
|
+
`wompifyPrompt({ referenceName, referenceNamePrefix, preset, note })`.
|
|
35
|
+
|
|
36
|
+
## 5.12.1 — 2026-10-07
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Goal state, rules and evaluator feedback are sent as appended messages instead of changing the
|
|
41
|
+
system prompt, so goal turns keep the prompt cache.
|
|
42
|
+
|
|
43
|
+
## 5.12.0 — 2026-10-07
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Goal endpoint `update_budget` command edits a goal's credit cap without restarting the run.
|
|
48
|
+
- Wompify detail levels Simple, High res and Ultra res.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- A Wompify pass completes when it reaches its credit cap instead of failing the run, and can keep
|
|
53
|
+
refining in the same conversation with a new budget.
|
|
54
|
+
- Goal credit budgets are no longer included in continuation prompts.
|
|
55
|
+
|
|
56
|
+
## 5.11.1 — 2026-10-06
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- Removed goal token budgets that were reintroduced in 5.11.0.
|
|
61
|
+
|
|
62
|
+
## 5.11.0 — 2026-10-06
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Scene API support for patterned lights, light collimation, texture imports and physical depth
|
|
67
|
+
of field.
|
|
68
|
+
|
|
69
|
+
## 5.10.0 — 2026-10-05
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- Ambiguous creative requests get an image preview before building again; turning an image into
|
|
74
|
+
3D uses mesh generation.
|
|
75
|
+
|
|
76
|
+
## 5.9.1 — 2026-10-05
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- Goal turns report the credits they actually used and no longer estimate costs.
|
|
81
|
+
|
|
82
|
+
## 5.9.0 — 2026-10-05
|
|
83
|
+
|
|
84
|
+
### Changed
|
|
85
|
+
|
|
86
|
+
- Context trimming can also shorten the earlier steps of a long current turn.
|
|
87
|
+
|
|
88
|
+
## 5.8.0 — 2026-10-04
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Goal budgets are enforced with host-reported charged credits; goal token budgets were removed.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- Scene access results omit undefined properties.
|
|
97
|
+
|
|
98
|
+
## 5.7.1 — 2026-10-03
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- `read_skill` takes names-only requests and accepts combined skill name inputs.
|
|
103
|
+
|
|
104
|
+
## 5.7.0 — 2026-10-02
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
|
|
108
|
+
- Persistent server goals with `set`, `pause`, `resume` and `clear` controls.
|
|
109
|
+
|
|
110
|
+
## 5.6.1 — 2026-10-03
|
|
111
|
+
|
|
112
|
+
Published from a maintenance branch before 5.7.1.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- `read_skill` uses names-only requests.
|
|
117
|
+
|
|
118
|
+
## 5.6.0 — 2026-10-02
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- Skill tool input uses an object-root schema for provider compatibility.
|
|
123
|
+
|
|
124
|
+
## 5.5.0 — 2026-10-01
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
|
|
128
|
+
- Mesh Wompify: the shared `mesh-wompify` skill and Wompify request helpers in
|
|
129
|
+
`@womp/spark-agent/ui`.
|
|
130
|
+
|
|
131
|
+
## 5.4.0 — 2026-09-30
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- Requires Kakapo SDK 0.7.3.
|
|
136
|
+
|
|
137
|
+
### Fixed
|
|
138
|
+
|
|
139
|
+
- Missing procedural material node locations.
|
|
140
|
+
|
|
141
|
+
## 5.3.0 — 2026-09-29
|
|
142
|
+
|
|
143
|
+
### Added
|
|
144
|
+
|
|
145
|
+
- Procedural material authoring capability.
|
|
146
|
+
- GPT-6.1 Sol model.
|
|
147
|
+
|
|
148
|
+
### Changed
|
|
149
|
+
|
|
150
|
+
- Required skills load in one `read_skill` call.
|
|
151
|
+
|
|
152
|
+
## 5.2.3 — 2026-09-29
|
|
153
|
+
|
|
154
|
+
### Added
|
|
155
|
+
|
|
156
|
+
- Shared SDK scene adapters can omit `operations` to edit and save directly, without a recovery
|
|
157
|
+
journal.
|
|
158
|
+
|
|
159
|
+
## 5.2.2 — 2026-09-28
|
|
160
|
+
|
|
161
|
+
### Fixed
|
|
162
|
+
|
|
163
|
+
- Requires Kakapo SDK 0.7.2; curve density 0 is allowed again.
|
|
164
|
+
|
|
165
|
+
## 5.2.1 — 2026-09-28
|
|
166
|
+
|
|
167
|
+
### Fixed
|
|
168
|
+
|
|
169
|
+
- Requires Kakapo SDK 0.7.1, so stored out-of-range values no longer block unrelated reads and edits.
|
|
170
|
+
|
|
171
|
+
## 5.2.0 — 2026-09-28
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- A generation that ends on `length`, `content-filter`, `error` or `other` fails the run with the
|
|
176
|
+
unified and provider finish reasons in its error and diagnostics.
|
|
177
|
+
|
|
178
|
+
## 5.1.0 — 2026-09-26
|
|
179
|
+
|
|
180
|
+
### Added
|
|
181
|
+
|
|
182
|
+
- Each model step is trimmed to fit the model's context window.
|
|
183
|
+
|
|
184
|
+
## 5.0.2 — 2026-09-25
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- Restored screenshot framing guidance.
|
|
189
|
+
|
|
190
|
+
## 5.0.1 — 2026-09-25
|
|
191
|
+
|
|
192
|
+
### Fixed
|
|
193
|
+
|
|
194
|
+
- Restored screenshot camera controls.
|
|
195
|
+
|
|
196
|
+
## 5.0.0 — 2026-09-25
|
|
197
|
+
|
|
198
|
+
### Breaking
|
|
199
|
+
|
|
200
|
+
- Scene capability `read`/`edit` replaced by `access(callback)`; custom scene hosts implement
|
|
201
|
+
`SceneHost.access` and SDK adapters `acquireAccess`.
|
|
202
|
+
- Requires Kakapo SDK 0.7.0.
|
|
203
|
+
|
|
204
|
+
### Migrating to 5.0
|
|
205
|
+
|
|
206
|
+
- Replace scene capability `read`/`edit` with `access(callback)`. Inspect the current draft
|
|
207
|
+
through `scene.nodes({ ids, name, type, parentId, offset, limit })`, not a `nodes` array.
|
|
208
|
+
Results default to 100, with a maximum page size of 1,000.
|
|
209
|
+
- Update custom scene hosts to `SceneHost.access` and SDK adapters to `acquireAccess`.
|
|
210
|
+
`onCommitted` receives the acknowledged scene; keep host access suspended until synchronization
|
|
211
|
+
succeeds.
|
|
212
|
+
- Remove scene revision arguments from Geometry/OpenSCAD writes.
|
|
213
|
+
- Screenshot calls without options capture the current native viewport. Explicit `camera`
|
|
214
|
+
position/target, projection, `horizontalFovDegrees` and `orthographicWidth` are supported;
|
|
215
|
+
remove `targetId` and `view` presets. Camera distances use current project units.
|
|
216
|
+
- Request scene health explicitly through `scene.health()`; it no longer runs after each edit.
|
|
217
|
+
- Native and QuickJS executors allow 100,000 host calls per execution.
|
|
218
|
+
|
|
219
|
+
## Earlier versions
|
|
220
|
+
|
|
221
|
+
Changes before 5.0.0 are recorded in the Git history.
|
package/README.md
CHANGED
|
@@ -6,25 +6,9 @@ Spark Agent uses AI SDK 7 `UIMessage` and `UIMessageChunk` as its public chat pr
|
|
|
6
6
|
JavaScript uses the host's executor and capabilities. Native browser execution and isolated QuickJS
|
|
7
7
|
execution use the same model loop. The host owns conversations, artifacts, authorization, and connections.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### Migrating to 5.0
|
|
9
|
+
Release notes and migration guides are in [CHANGELOG.md](CHANGELOG.md).
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
through `scene.nodes({ ids, name, type, parentId, offset, limit })`, not a `nodes` array.
|
|
15
|
-
Results default to 100, with a maximum page size of 1,000.
|
|
16
|
-
- Update custom scene hosts to `SceneHost.access` and SDK adapters to `acquireAccess`.
|
|
17
|
-
`onCommitted` receives the acknowledged scene; keep host access suspended until synchronization succeeds.
|
|
18
|
-
- Remove scene revision arguments from Geometry/OpenSCAD writes. Current parameters and overrides
|
|
19
|
-
are prepared inside protected access. Hosts must still enforce connection/project ownership.
|
|
20
|
-
- Screenshot calls without options capture the current native viewport. Explicit `camera`
|
|
21
|
-
position/target, projection, `horizontalFovDegrees` and `orthographicWidth` are supported;
|
|
22
|
-
remove `targetId` and `view` presets. Camera distances use current project units.
|
|
23
|
-
- Scene health is explicitly requested through `scene.health()`, not automatically after each edit.
|
|
24
|
-
- This release requires Kakapo SDK 0.7.0. Native and QuickJS executors now allow 100,000 host calls
|
|
25
|
-
per execution. Receipts, cancellation, timeout limits and uncertain-outcome handling remain enforced.
|
|
26
|
-
|
|
27
|
-
### Runtime
|
|
11
|
+
## Requirements
|
|
28
12
|
|
|
29
13
|
- Node.js 22.13 or newer
|
|
30
14
|
- AI SDK 7
|
|
@@ -34,67 +18,6 @@ execution use the same model loop. The host owns conversations, artifacts, autho
|
|
|
34
18
|
npm install @womp/spark-agent
|
|
35
19
|
```
|
|
36
20
|
|
|
37
|
-
## Run a prompt from the command line
|
|
38
|
-
|
|
39
|
-
With the Spark server already running:
|
|
40
|
-
|
|
41
|
-
```sh
|
|
42
|
-
yarn agent-run --prompt "Make a cube" --model gpt-5.6-luna --reasoning max
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Use `--conversation <id>` to continue a thread and `--output <new-directory>` to save
|
|
46
|
-
`result.json`, `transcript.json` and original run artifacts under `artifacts/`.
|
|
47
|
-
For local runs, use the git-ignored `.spark/` directory, with a fresh subdirectory per run:
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
yarn agent-run --prompt "Make rainbow-colored boxes" --output .spark/agent-runs/rainbow-boxes
|
|
51
|
-
yarn agent-run --prompt "Model this object using both views" --image front.png --image side.jpg --output .spark/agent-runs/reference-model
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Repeat `--image <path>` for local PNG, JPEG, WebP or GIF references, in argument order.
|
|
55
|
-
The required prompt and all images are sent in one user message. Files, format signatures,
|
|
56
|
-
and the full JSON request size (including base64 overhead) are checked before contacting the server.
|
|
57
|
-
The CLI uses the server's default 25 MiB request limit; a custom server or provider may impose
|
|
58
|
-
stricter limits. Images are never resized, and remote URLs are not accepted.
|
|
59
|
-
With `--output`, original bytes are saved under `inputs/`; `input.json` and the result's `input`
|
|
60
|
-
field list ordered original filenames, MIME types and relative paths. The submitted message in
|
|
61
|
-
`transcript.json` uses those paths instead of inline data. Generated artifacts stay in `artifacts/`.
|
|
62
|
-
|
|
63
|
-
Without output, artifacts remain server references. The CLI never clears the scene or
|
|
64
|
-
takes extra screenshots. Defaults are `gpt-6-astra`, `high`, and
|
|
65
|
-
`--api http://127.0.0.1:8787/agent`; unsupported settings are rejected.
|
|
66
|
-
|
|
67
|
-
Progress goes to stderr; one JSON result goes to stdout. Use `yarn --silent agent-run`
|
|
68
|
-
or the installed `agent-run` executable when parsing stdout (Yarn otherwise adds banners).
|
|
69
|
-
Usage comes from the provider, with per-step USD estimates; missing usage is not estimated.
|
|
70
|
-
Exit codes: 0 completed, 1 run/transport/export failure, 2 invalid arguments,
|
|
71
|
-
3 requires user action (continue in the UI), 130 interrupted. Interrupts attempt to cancel
|
|
72
|
-
only this invocation's run; a failed cancellation is reported and the run may still be active.
|
|
73
|
-
Output directories must not already exist. Artifact manifests include IDs, media types,
|
|
74
|
-
server URLs and relative paths; failed downloads retain their references and report errors.
|
|
75
|
-
Legacy inline base64 uploads in transcripts are replaced with `[inline data omitted]`.
|
|
76
|
-
|
|
77
|
-
## Mesh Wompify
|
|
78
|
-
|
|
79
|
-
Swan imports the reference mesh with Disjoint off, then requests reconstruction
|
|
80
|
-
through the shared `mesh-wompify` and `scene-editing` skills. Tier and request
|
|
81
|
-
helpers are exported from `@womp/spark-agent/ui`. Server mode requires Crow to
|
|
82
|
-
install the updated package and be redeployed; a local Swan package link is insufficient.
|
|
83
|
-
The Mesh Wompify function panel accepts a file and a detail level (Simple, High res,
|
|
84
|
-
or Ultra res). Reconstruction matches the reference's shape and colors; textures
|
|
85
|
-
are not carried over. Crow sets the initial cap for each level and bills actual
|
|
86
|
-
usage. Wompify keeps refining across turns until the cap, even if the evaluator
|
|
87
|
-
judges the reconstruction sufficient; insufficient credits or a blocker stop it.
|
|
88
|
-
Reaching the cap completes the pass without a run error. Keep refining
|
|
89
|
-
asks for a new pass budget, then continues in the same conversation with the
|
|
90
|
-
same reference and detail level. The budget travels as structured goal data.
|
|
91
|
-
|
|
92
|
-
Ordinary goals support `{ action: "update_budget", goalId, creditBudget }` on
|
|
93
|
-
the goal endpoint. This edits the total credit cap without resetting usage,
|
|
94
|
-
restarting the run, or adding a model message. Crow reads the current cap before
|
|
95
|
-
worker and evaluator calls; an in-flight call may finish. Raising an exhausted
|
|
96
|
-
cap leaves the goal paused until Resume. Wompify's tier caps cannot be edited.
|
|
97
|
-
|
|
98
21
|
## Package entry points
|
|
99
22
|
|
|
100
23
|
- `@womp/spark-agent` — browser-safe model loop, two tools, persistence hooks, and shared contracts
|
|
@@ -114,8 +37,17 @@ Kakapo, Express, or React at runtime.
|
|
|
114
37
|
|
|
115
38
|
`runSparkAgent` accepts a configured model, model messages, an executor, a capability composition,
|
|
116
39
|
and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
|
|
117
|
-
Skills carry instructions, declarations, and JavaScript examples.
|
|
118
|
-
|
|
40
|
+
Skills carry instructions, declarations, and JavaScript examples. Kakapo skill declarations are
|
|
41
|
+
generated from its typed API at build time and packaged as JavaScript strings.
|
|
42
|
+
|
|
43
|
+
### System prompt and editor selection
|
|
44
|
+
|
|
45
|
+
Every host sends `sparkSystemPrompt(skills)`: the shared agent prompt and the skills directory,
|
|
46
|
+
with nothing host-specific, so it stays identical across a conversation. Hosts attach the editor
|
|
47
|
+
selection to the user message it was sent with as a `data-selection` part (`{ nodeIds }`). It is
|
|
48
|
+
not rendered as chat text; the model reads it as a short note on that message.
|
|
49
|
+
|
|
50
|
+
### Capabilities
|
|
119
51
|
|
|
120
52
|
Compose only supported capabilities with `composeCapabilities({ scene, account, ... })`. Each
|
|
121
53
|
capability supplies its skills and `createApi(context)`; the returned API is bound under `womp`.
|
|
@@ -124,34 +56,35 @@ own their transactions and must check cancellation at their commit boundary.
|
|
|
124
56
|
|
|
125
57
|
For a real user interaction, call `context.waitForUser(label, async signal => ...)` outside mutation
|
|
126
58
|
locks. The run stays active with `requires_action` progress and resumes the same invocation after
|
|
127
|
-
input
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
59
|
+
input; the wait is excluded from the execution budget. Abort settles the wait and late answers
|
|
60
|
+
cannot revive it. Custom executors must honor the optional host-owned `userWait` argument to `execute`.
|
|
61
|
+
|
|
62
|
+
Shared SDK scene adapters may omit `operations` to edit and save directly. Supplying `operations`
|
|
63
|
+
adds a durable operation journal and recovery of uncertain edits.
|
|
64
|
+
|
|
65
|
+
### Persistence and run lifecycle
|
|
132
66
|
|
|
133
67
|
Persist the user message before starting a run. Supply `persistence.beforeToolCall`,
|
|
134
68
|
`persistence.recordReceipt`, and `persistence.afterToolCall` for awaited storage barriers.
|
|
135
69
|
Supply `persistence.saveMessage` to save the canonical UI message at each model-step boundary and
|
|
136
|
-
at termination
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`status: "incomplete"
|
|
141
|
-
A generation that ends on any other non-`stop` reason
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
70
|
+
at termination. Spark awaits each checkpoint before the next model request; a failed barrier aborts
|
|
71
|
+
the loop. Consume the run's stream while awaiting its result; a host run owner must keep consuming
|
|
72
|
+
when a view detaches.
|
|
73
|
+
|
|
74
|
+
The default model-step limit is 100; reaching it returns `status: "incomplete"`, and hosts must not
|
|
75
|
+
restart automatically to bypass it. A generation that ends on any other non-`stop` reason fails the
|
|
76
|
+
run with the unified and provider finish reasons in its error and diagnostics.
|
|
77
|
+
|
|
78
|
+
Before each model step, Spark trims older images, large tool results, and whole earlier turns to
|
|
79
|
+
keep the request inside the model's context window. The latest user message and the most recent
|
|
80
|
+
steps are never trimmed.
|
|
81
|
+
|
|
82
|
+
### Artifacts
|
|
147
83
|
|
|
148
84
|
Screenshot and file APIs return serializable artifact references. `emitImage` and `emitFile` resolve
|
|
149
85
|
them through the host's artifact resolver. Keep references resolvable when reloading history, and
|
|
150
86
|
pass `createSparkTools(...)` to AI SDK's `convertToModelMessages` to reconstruct multimodal results.
|
|
151
|
-
`emitImage(artifact, { inspect: false })` displays an image without adding its bytes to model input
|
|
152
|
-
including restored history. Inspection is enabled by default.
|
|
153
|
-
Kakapo skill declarations are generated from its actual typed API during the build and packaged as
|
|
154
|
-
JavaScript strings; they do not need filesystem or Markdown loading in the browser.
|
|
87
|
+
`emitImage(artifact, { inspect: false })` displays an image without adding its bytes to model input.
|
|
155
88
|
|
|
156
89
|
## Local frontend
|
|
157
90
|
|
|
@@ -186,10 +119,7 @@ const chat = useChat<SparkUIMessage>({
|
|
|
186
119
|
|
|
187
120
|
Render `message.parts`. Standard AI SDK parts represent text, reasoning, files, tool inputs, tool
|
|
188
121
|
outputs, approvals, and step boundaries. Spark adds typed `data-console`, `data-artifact`,
|
|
189
|
-
`data-run-status`, `data-step-metrics`,
|
|
190
|
-
their storage barrier, keeping known effects visible even when saving the tool result fails.
|
|
191
|
-
Hosts can consume `data-tool-input-label` once the tool's label field is complete, while the
|
|
192
|
-
remaining input is still streaming.
|
|
122
|
+
`data-run-status`, `data-step-metrics`, `data-execution-receipt`, and `data-tool-input-label` parts.
|
|
193
123
|
|
|
194
124
|
An explicit Stop action must cancel the durable run before stopping local observation:
|
|
195
125
|
|
|
@@ -205,21 +135,16 @@ Closing or reloading the page only detaches the SSE subscriber. It does not canc
|
|
|
205
135
|
`SparkChatTransport` hides the durable run lifecycle from `useChat`:
|
|
206
136
|
|
|
207
137
|
```text
|
|
208
|
-
POST
|
|
138
|
+
POST /agent/conversations/:conversationId/messages
|
|
209
139
|
PATCH /agent/conversations/:conversationId/settings
|
|
210
|
-
|
|
211
|
-
|
|
140
|
+
POST /agent/conversations/:conversationId/goal
|
|
141
|
+
GET /agent/runs/:runId/stream?after=0
|
|
142
|
+
POST /agent/runs/:runId/cancel
|
|
212
143
|
```
|
|
213
144
|
|
|
214
|
-
Conversation settings contain `modelId` and `reasoningEffort
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
unsupported choices fall back to supported settings. Assistant footers show the model recorded on
|
|
218
|
-
that turn, independently of the selector.
|
|
219
|
-
|
|
220
|
-
Custom stores must implement `saveConversationSettings` as a settings-only update, and preserve
|
|
221
|
-
stored settings during `saveConversation` so streaming history saves cannot overwrite preferences.
|
|
222
|
-
The built-in memory and SQLite stores implement this contract.
|
|
145
|
+
Conversation settings contain `modelId` and `reasoningEffort`; save them with
|
|
146
|
+
`transport.saveConversationSettings(id, settings)`. The goal endpoint accepts `set`, `pause`,
|
|
147
|
+
`resume`, `clear`, and `update_budget` commands.
|
|
223
148
|
|
|
224
149
|
The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
|
|
225
150
|
|
|
@@ -234,8 +159,7 @@ type StoredSparkChunk = {
|
|
|
234
159
|
```
|
|
235
160
|
|
|
236
161
|
The transport unwraps each envelope and returns the contained `UIMessageChunk` to `useChat`.
|
|
237
|
-
Active assistant messages are replayed from sequence zero when reconnecting
|
|
238
|
-
reconstructs the in-progress message from the resumed stream.
|
|
162
|
+
Active assistant messages are replayed from sequence zero when reconnecting.
|
|
239
163
|
|
|
240
164
|
## Embedding in an Express backend
|
|
241
165
|
|
|
@@ -266,15 +190,25 @@ app.use(
|
|
|
266
190
|
|
|
267
191
|
`MemorySparkAgentStore` and `MemorySparkArtifactStore` are development implementations. Production
|
|
268
192
|
hosts must supply durable implementations backed by their database/object storage and must preserve
|
|
269
|
-
the ordering contract of `appendRunChunk()`.
|
|
193
|
+
the ordering contract of `appendRunChunk()`. Custom stores must also:
|
|
194
|
+
|
|
195
|
+
- implement `saveConversationSettings` as a settings-only update, and preserve stored settings
|
|
196
|
+
during `saveConversation`;
|
|
197
|
+
- implement `saveConversationGoal` to support goals;
|
|
198
|
+
- optionally implement `releaseActiveRun(conversationId, runId, updatedAt)` to clear the active run
|
|
199
|
+
only if its ID still matches.
|
|
200
|
+
|
|
201
|
+
The built-in memory and SQLite stores implement all of these.
|
|
270
202
|
|
|
271
203
|
`SparkAgentServiceOptions.endOrphanedRuns` defaults to `false`. Enable it only when one process
|
|
272
|
-
owns the store: a message or Stop then ends unfinished runs not held by that process.
|
|
273
|
-
store, enabling it could end another process's live run.
|
|
204
|
+
owns the store: a message or Stop then ends unfinished runs not held by that process.
|
|
274
205
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
206
|
+
## Mesh Wompify
|
|
207
|
+
|
|
208
|
+
Mesh Wompify reconstructs an imported reference mesh as editable primitives through the shared
|
|
209
|
+
`mesh-wompify` and `scene-editing` skills. `WOMPIFY_PRESETS`, `wompifyPrompt`, and
|
|
210
|
+
`parseWompifyCommand` are exported from `@womp/spark-agent/ui`. A Wompify run is a goal: it refines
|
|
211
|
+
across turns until its credit cap, and can continue in the same conversation with a new budget.
|
|
278
212
|
|
|
279
213
|
## Standalone development server
|
|
280
214
|
|
|
@@ -292,107 +226,63 @@ Configure the process with:
|
|
|
292
226
|
- `KAKAPO_URL` (defaults to `ws://127.0.0.1:5502`)
|
|
293
227
|
- `PORT` (defaults to `8787`)
|
|
294
228
|
- `SPARK_STORAGE_PATH` (defaults to `.spark/spark.sqlite`)
|
|
295
|
-
- `SPARK_SCENE_IDENTITY` (optional
|
|
229
|
+
- `SPARK_SCENE_IDENTITY` (optional; set only when the value names the same durable scene across
|
|
230
|
+
renderer reconnects)
|
|
296
231
|
|
|
297
|
-
The CLI
|
|
298
|
-
|
|
299
|
-
variables take precedence over values loaded from the file.
|
|
232
|
+
The CLI loads `.env` from its working directory when present; copy `.env.example` to `.env` and add
|
|
233
|
+
the provider key you intend to use. Process environment variables take precedence.
|
|
300
234
|
|
|
301
235
|
For repository development, `yarn dev` starts the Express server and Vite demo together. Open
|
|
302
|
-
`http://127.0.0.1:5173`.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
When using a sibling `kakapo-sdk` dependency, run `yarn build` in `../kakapo-sdk` before installing
|
|
331
|
-
Spark dependencies. Replace local dependencies with registry versions before publishing.
|
|
332
|
-
|
|
333
|
-
For the explicit fictional account profile, use `yarn dev:demo-account` or
|
|
334
|
-
`spark-agent --profile demo-account`. It adds the `account` skill and `womp.account.read`
|
|
335
|
-
beside the real local Kakapo scene API. Profile reads identify themselves as fictional fixtures;
|
|
336
|
-
credits, billing, orders, and other account services report unavailable. The default composition
|
|
337
|
-
omits account entirely. Neither profile supplies fake generation jobs, imports, or billing permission.
|
|
338
|
-
Embedded development servers can select `developmentProfile: "demo-account"` explicitly.
|
|
339
|
-
|
|
340
|
-
The same profile supplies two actual local material resources: `Development: Matte Blue` and
|
|
341
|
-
`Development: Polished Copper`. They are bundled JSON files under `src/server/fixtures/materials`
|
|
342
|
-
and imported through the normal SDK draft, not simulated scene effects. In `scene.edit`, await
|
|
343
|
-
`api.materialImport(name)`, then assign its numeric ID. The default profile has no library service.
|
|
236
|
+
`http://127.0.0.1:5173`. When using a sibling `kakapo-sdk` dependency, run `yarn build` in
|
|
237
|
+
`../kakapo-sdk` before installing Spark dependencies.
|
|
238
|
+
|
|
239
|
+
The standalone server persists conversations, run streams, and screenshots in SQLite; only one
|
|
240
|
+
local process can execute against a database. `createStandaloneSparkAgentApp(options)` is
|
|
241
|
+
asynchronous: await it before listening, and call `await app.dispose()` on shutdown.
|
|
242
|
+
|
|
243
|
+
`yarn dev:demo-account` (or `spark-agent --profile demo-account`) adds a fictional `account` skill
|
|
244
|
+
and two development material resources beside the real local Kakapo scene API. Its account reads
|
|
245
|
+
identify themselves as fixtures; other account services report unavailable.
|
|
246
|
+
|
|
247
|
+
## Run a prompt from the command line
|
|
248
|
+
|
|
249
|
+
With the Spark server already running:
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
yarn agent-run --prompt "Make a cube" --model gpt-5.6-luna --reasoning max
|
|
253
|
+
yarn agent-run --prompt "Model this object using both views" --image front.png --image side.jpg --output .spark/agent-runs/reference-model
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- `--conversation <id>` continues a thread.
|
|
257
|
+
- `--image <path>` attaches a local PNG, JPEG, WebP or GIF reference; repeat it for more images.
|
|
258
|
+
- `--output <new-directory>` saves `result.json`, `transcript.json`, inputs, and run artifacts.
|
|
259
|
+
For local runs, use a fresh subdirectory of the git-ignored `.spark/` directory.
|
|
260
|
+
|
|
261
|
+
Progress goes to stderr; one JSON result goes to stdout. Use `yarn --silent agent-run` when parsing
|
|
262
|
+
stdout. Exit codes: 0 completed, 1 run/transport/export failure, 2 invalid arguments, 3 requires
|
|
263
|
+
user action, 130 interrupted.
|
|
344
264
|
|
|
345
265
|
## Run cost estimates
|
|
346
266
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
and [OpenAI caching](https://developers.openai.com/api/docs/guides/prompt-caching).
|
|
356
|
-
Restart the server after changing these settings; existing runs keep their original configuration.
|
|
357
|
-
|
|
358
|
-
The chat displays estimated API token cost in USD, updated after every completed model step.
|
|
359
|
-
Hover or focus the amount to inspect uncached input, cache reads/writes, and output costs. Output usage
|
|
360
|
-
already includes reasoning tokens. Image input is included in the provider's input token count.
|
|
361
|
-
|
|
362
|
-
`src/pricing.ts` contains standard direct API rates verified on September 9, 2026:
|
|
363
|
-
|
|
364
|
-
| Model | Input / 1M | Cache read / 1M | Cache write / 1M | Output / 1M |
|
|
365
|
-
| -------------- | ---------- | --------------- | --------------------- | ----------- |
|
|
366
|
-
| GPT-6 Astra | $10 | $1 | $12.50 | $50 |
|
|
367
|
-
| Claude Opus 5 | $5 | $0.50 | $6.25 (5m), $10 (1h) | $25 |
|
|
368
|
-
| Claude Fable 5 | $10 | $1 | $12.50 (5m), $20 (1h) | $50 |
|
|
369
|
-
|
|
370
|
-
Astra requests above 272,000 input tokens use 2x input/cache rates and 1.5x output rates.
|
|
371
|
-
This threshold is applied to each request before summing run costs. Claude's full context uses
|
|
372
|
-
standard rates. Sources: [Astra pricing](https://developers.openai.com/api/docs/models/gpt-6-astra),
|
|
373
|
-
[Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing).
|
|
374
|
-
|
|
375
|
-
Each `data-step-metrics` part stores a `cost` snapshot with the model, rate date, source, token
|
|
376
|
-
categories, rates, and amounts. The UI sums those stored amounts, so reconnection does not double
|
|
377
|
-
count steps and future price changes do not reprice recorded runs. Update the rate table and its
|
|
378
|
-
verification date when provider pricing changes.
|
|
379
|
-
|
|
380
|
-
The unfinished step is excluded until its usage arrives. Cancelled/failed runs show a partial
|
|
381
|
-
estimate. Missing usage, unknown models, missing cache TTLs, or provider sub-inferences without
|
|
382
|
-
separate attribution are marked unavailable, never silently priced as zero. Historical runs
|
|
383
|
-
without step pricing remain unavailable. Estimates exclude taxes, negotiated discounts, and scene
|
|
384
|
-
hosting; the provider bill is authoritative. Spark requests OpenAI's standard service tier.
|
|
267
|
+
The chat displays estimated API token cost in USD after every completed model step, from the
|
|
268
|
+
provider-reported usage and the rates in `src/pricing.ts`. Each `data-step-metrics` part stores
|
|
269
|
+
its `cost` snapshot, so recorded runs are not repriced when rates change. Missing usage or unknown
|
|
270
|
+
models are shown as unavailable, never priced as zero. Estimates exclude taxes, discounts, and
|
|
271
|
+
scene hosting; the provider bill is authoritative.
|
|
272
|
+
|
|
273
|
+
Prompt caching is enabled for every listed model. It depends on an identical request prefix, which
|
|
274
|
+
is why per-turn state travels in messages rather than the system prompt.
|
|
385
275
|
|
|
386
276
|
## Execution security
|
|
387
277
|
|
|
388
278
|
The standalone host uses a fresh QuickJS runtime for each JavaScript execution. It has no Node.js
|
|
389
279
|
or browser globals and receives only the composed capability APIs. Imports, filesystem access,
|
|
390
280
|
network APIs, and host environment variables are not exposed. QuickJS limits memory, stack size,
|
|
391
|
-
execution time, source size, host calls, and data crossing the host bridge.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
execution.
|
|
395
|
-
and
|
|
281
|
+
execution time, source size, host calls, and data crossing the host bridge. Both executors allow
|
|
282
|
+
100,000 host calls per execution by default (`maxHostCalls` overrides this in QuickJS).
|
|
283
|
+
|
|
284
|
+
Browser hosts can instead use native page execution. It runs in the page realm: it is not a sandbox
|
|
285
|
+
and cannot interrupt a synchronous infinite loop.
|
|
396
286
|
|
|
397
287
|
QuickJS isolates generated code from the JavaScript host API; it is not an operating-system security
|
|
398
288
|
boundary. Multi-tenant deployments must additionally isolate workers, authorize every Kakapo
|
|
@@ -410,41 +300,30 @@ yarn web:build
|
|
|
410
300
|
|
|
411
301
|
## Release procedure
|
|
412
302
|
|
|
413
|
-
Pushes to `main` run `.github/workflows/publish.yml`. Publication is driven by
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
Versions follow **MAJOR.MINOR.PATCH** (for example, `4.4.0`):
|
|
418
|
-
|
|
419
|
-
- **Major:** breaking changes.
|
|
420
|
-
- **Minor:** backward-compatible new features.
|
|
421
|
-
- **Patch:** backward-compatible bug fixes.
|
|
303
|
+
Pushes to `main` run `.github/workflows/publish.yml`. Publication is driven by `package.json`, not
|
|
304
|
+
Git tags: already-published versions are skipped; an unpublished stable version newer than npm's
|
|
305
|
+
`latest` runs the release checks and is published automatically.
|
|
422
306
|
|
|
423
|
-
|
|
307
|
+
Versions follow **MAJOR.MINOR.PATCH**: major for breaking changes, minor for backward-compatible
|
|
308
|
+
features, patch for backward-compatible fixes.
|
|
424
309
|
|
|
425
310
|
1. Configure npm's trusted publisher once for `@womp/spark-agent`: organization `wompxyz`,
|
|
426
311
|
repository `spark-agent`, workflow filename `publish.yml`, no environment, and allow direct
|
|
427
312
|
`npm publish`. GitHub-hosted runners use OIDC; no publishing token is required.
|
|
428
|
-
2.
|
|
429
|
-
|
|
430
|
-
3. CI installs the frozen
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
Releases are serialized; newer pending pushes may supersede older pending runs.
|
|
437
|
-
Registry failures stop publication rather than being treated as missing versions.
|
|
438
|
-
4. If publication reports an uncertain result, inspect npm before rerunning the failed workflow.
|
|
439
|
-
Published versions cannot be replaced; corrections need a new version. No tags are required.
|
|
440
|
-
5. In the consuming application, use `yarn add @womp/spark-agent@<version> --exact`. Commit its
|
|
441
|
-
manifest and lockfile together, and verify a clean frozen-lockfile installation and production
|
|
442
|
-
build. Remove the temporary archive dependency. Application deployment and merge requirements
|
|
443
|
-
remain separate from package publication.
|
|
313
|
+
2. Bump `package.json` to an unused stable version, add its entry to `CHANGELOG.md`, and push the
|
|
314
|
+
release commit to `main`. Prereleases are not published by this workflow.
|
|
315
|
+
3. CI installs the frozen lockfile, runs typechecks, tests and the UI build, verifies the packed
|
|
316
|
+
tarball's entry points and imports, then publishes and verifies registry integrity and `latest`.
|
|
317
|
+
4. If publication reports an uncertain result, inspect npm before rerunning the workflow. Published
|
|
318
|
+
versions cannot be replaced; corrections need a new version.
|
|
319
|
+
5. In the consuming application, run `yarn add @womp/spark-agent@<version> --exact` and commit its
|
|
320
|
+
manifest and lockfile together.
|
|
444
321
|
|
|
445
322
|
## Layout
|
|
446
323
|
|
|
447
324
|
- `src/agent.ts` — AI SDK stream and Spark tool orchestration
|
|
325
|
+
- `src/prompt.ts` — shared system prompt
|
|
326
|
+
- `src/context-budget.ts` — per-step context-window trimming
|
|
448
327
|
- `src/ui.ts` — browser-safe protocol types and validation schemas
|
|
449
328
|
- `src/client.ts` — durable `ChatTransport` implementation
|
|
450
329
|
- `src/server/` — service, storage contracts, Express adapter, artifacts, and standalone composition
|
package/dist/agent-tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { tool } from "ai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { boundedResultText } from "./bounded-output.js";
|
|
3
|
+
import { boundedResultText, canonicalJsonValue } from "./bounded-output.js";
|
|
4
4
|
import { completedInputString } from "./tool-input-label.js";
|
|
5
5
|
import { UserWait } from "./execution-lifetime.js";
|
|
6
6
|
function errorMessage(error) {
|
|
@@ -98,6 +98,8 @@ export function createSparkTools(options) {
|
|
|
98
98
|
const skills = [...new Set(input.names)].map(read);
|
|
99
99
|
return { ok: skills.every(skill => skill.ok), skills };
|
|
100
100
|
}),
|
|
101
|
+
// Replayed from storage that may reorder keys; see canonicalJsonValue.
|
|
102
|
+
toModelOutput: ({ output }) => ({ type: "json", value: canonicalJsonValue(output) }),
|
|
101
103
|
}),
|
|
102
104
|
execute_javascript: tool({
|
|
103
105
|
...inputLabels("purpose"),
|
package/dist/agent.d.ts
CHANGED
package/dist/agent.js
CHANGED
|
@@ -2,7 +2,7 @@ import { sparkErrorDiagnostics } from "./error-diagnostics.js";
|
|
|
2
2
|
import { WompifyCreditCapReachedError } from "./wompify.js";
|
|
3
3
|
import { createUIMessageStream, stepCountIs, streamText, } from "ai";
|
|
4
4
|
import { createSparkTools } from "./agent-tools.js";
|
|
5
|
-
import {
|
|
5
|
+
import { sparkSystemPrompt } from "./prompt.js";
|
|
6
6
|
import { calculateStepCost } from "./pricing.js";
|
|
7
7
|
import { contextWindowFor, fitMessagesToContext } from "./context-budget.js";
|
|
8
8
|
function errorMessage(error) {
|
|
@@ -124,16 +124,7 @@ export function runSparkAgent(options) {
|
|
|
124
124
|
controller,
|
|
125
125
|
write: (chunk) => writer.write(chunk),
|
|
126
126
|
});
|
|
127
|
-
|
|
128
|
-
// provider's prompt cache for everything after it. Changing state belongs in messages.
|
|
129
|
-
const readSystem = () => [
|
|
130
|
-
options.instructions ?? SPARK_AGENT_PROMPT,
|
|
131
|
-
options.context,
|
|
132
|
-
"Available skills (read before using their APIs):",
|
|
133
|
-
...options.capabilities.skills.map(({ name, version, description }) => `- ${name} (${version}): ${description}`),
|
|
134
|
-
]
|
|
135
|
-
.filter(Boolean)
|
|
136
|
-
.join("\n\n");
|
|
127
|
+
const readSystem = () => sparkSystemPrompt(options.capabilities.skills, options.instructions);
|
|
137
128
|
const generation = streamText({
|
|
138
129
|
model: options.model,
|
|
139
130
|
system: readSystem(),
|
package/dist/bounded-output.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export declare const MODEL_RESULT_BYTE_LIMIT: number;
|
|
2
2
|
export declare const MODEL_ERROR_BYTE_LIMIT: number;
|
|
3
|
+
/**
|
|
4
|
+
* `value` with every object's keys sorted, recursively. Hosts store tool
|
|
5
|
+
* results in databases that reorder keys (Postgres JSONB puts shorter keys
|
|
6
|
+
* first), so a replayed result must not depend on the order it was read back
|
|
7
|
+
* in: the bytes the model reads have to match what the live run sent, or every
|
|
8
|
+
* later turn misses the provider's prompt cache from that result onward.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canonicalJsonValue(value: unknown): unknown;
|
|
3
11
|
export declare function byteLength(text: string): number;
|
|
4
12
|
/**
|
|
5
13
|
* `text` cut to at most `limit` bytes on a character boundary. A cut that lands
|
|
@@ -15,8 +23,9 @@ export declare function truncateUtf8(text: string, limit: number): string;
|
|
|
15
23
|
export declare function boundedText(text: string, limit: number, label: string): string;
|
|
16
24
|
/**
|
|
17
25
|
* The serialized tool result the model reads, bounded to
|
|
18
|
-
* `MODEL_RESULT_BYTE_LIMIT
|
|
19
|
-
* receipts keep the full output; only model input is
|
|
26
|
+
* `MODEL_RESULT_BYTE_LIMIT`, with keys in canonical order. Persistence, the UI
|
|
27
|
+
* stream and the host's own receipts keep the full output; only model input is
|
|
28
|
+
* bounded here.
|
|
20
29
|
*
|
|
21
30
|
* Stages, in order, so the most useful part survives: bound `error`, then bound
|
|
22
31
|
* each receipt's `details`, then keep only as many receipts as the budget
|
package/dist/bounded-output.js
CHANGED
|
@@ -4,6 +4,23 @@ const encoder = new TextEncoder();
|
|
|
4
4
|
const decoder = new TextDecoder();
|
|
5
5
|
export const MODEL_RESULT_BYTE_LIMIT = 1024 * 1024;
|
|
6
6
|
export const MODEL_ERROR_BYTE_LIMIT = 8 * 1024;
|
|
7
|
+
/**
|
|
8
|
+
* `value` with every object's keys sorted, recursively. Hosts store tool
|
|
9
|
+
* results in databases that reorder keys (Postgres JSONB puts shorter keys
|
|
10
|
+
* first), so a replayed result must not depend on the order it was read back
|
|
11
|
+
* in: the bytes the model reads have to match what the live run sent, or every
|
|
12
|
+
* later turn misses the provider's prompt cache from that result onward.
|
|
13
|
+
*/
|
|
14
|
+
export function canonicalJsonValue(value) {
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.map(canonicalJsonValue);
|
|
17
|
+
if (value === null || typeof value !== "object")
|
|
18
|
+
return value;
|
|
19
|
+
const record = value;
|
|
20
|
+
return Object.fromEntries(Object.keys(record)
|
|
21
|
+
.sort()
|
|
22
|
+
.map((key) => [key, canonicalJsonValue(record[key])]));
|
|
23
|
+
}
|
|
7
24
|
export function byteLength(text) {
|
|
8
25
|
return encoder.encode(text).byteLength;
|
|
9
26
|
}
|
|
@@ -33,8 +50,9 @@ export function boundedText(text, limit, label) {
|
|
|
33
50
|
}
|
|
34
51
|
/**
|
|
35
52
|
* The serialized tool result the model reads, bounded to
|
|
36
|
-
* `MODEL_RESULT_BYTE_LIMIT
|
|
37
|
-
* receipts keep the full output; only model input is
|
|
53
|
+
* `MODEL_RESULT_BYTE_LIMIT`, with keys in canonical order. Persistence, the UI
|
|
54
|
+
* stream and the host's own receipts keep the full output; only model input is
|
|
55
|
+
* bounded here.
|
|
38
56
|
*
|
|
39
57
|
* Stages, in order, so the most useful part survives: bound `error`, then bound
|
|
40
58
|
* each receipt's `details`, then keep only as many receipts as the budget
|
|
@@ -46,7 +64,7 @@ export function boundedResultText(output, limit = MODEL_RESULT_BYTE_LIMIT) {
|
|
|
46
64
|
const errorBounded = typeof output.error === "string"
|
|
47
65
|
? { ...output, error: boundedText(output.error, MODEL_ERROR_BYTE_LIMIT, "Error") }
|
|
48
66
|
: output;
|
|
49
|
-
const asText = (value) => JSON.stringify(value) ?? "null";
|
|
67
|
+
const asText = (value) => JSON.stringify(canonicalJsonValue(value)) ?? "null";
|
|
50
68
|
let text = asText(errorBounded);
|
|
51
69
|
if (byteLength(text) <= limit)
|
|
52
70
|
return text;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { recoverSceneOperation, type SceneOperation, type SceneOperationStore }
|
|
|
8
8
|
export type { JavaScriptExecutor, JavaScriptGlobals, JavaScriptUserWait } from "./executor.js";
|
|
9
9
|
export { DEFAULT_MODEL_ID, getSparkModel, REASONING_EFFORT_LABELS, resolveReasoningEffort, SPARK_MODELS, } from "./models.js";
|
|
10
10
|
export type { ModelProvider, ReasoningEffort, SparkModel } from "./models.js";
|
|
11
|
-
export { SPARK_AGENT_PROMPT } from "./prompt.js";
|
|
11
|
+
export { SPARK_AGENT_PROMPT, sparkSystemPrompt } from "./prompt.js";
|
|
12
12
|
export type { ExecutionResult, ExecutionScreenshot, SparkRuntimeEvent, SparkRuntimeEventSink, SparkContextMessage, SparkImage, SparkRunInput, SparkRuntime, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,4 @@ export * from "./capabilities.js";
|
|
|
6
6
|
export { createSdkSceneEditor, createSdkSceneHost, } from "./sdk-scene-host.js";
|
|
7
7
|
export { recoverSceneOperation } from "./scene-operations.js";
|
|
8
8
|
export { DEFAULT_MODEL_ID, getSparkModel, REASONING_EFFORT_LABELS, resolveReasoningEffort, SPARK_MODELS, } from "./models.js";
|
|
9
|
-
export { SPARK_AGENT_PROMPT } from "./prompt.js";
|
|
9
|
+
export { SPARK_AGENT_PROMPT, sparkSystemPrompt } from "./prompt.js";
|
package/dist/model-history.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ModelMessage } from "ai";
|
|
2
2
|
import type { createSparkTools } from "./agent-tools.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type SparkUIMessage } from "./ui.js";
|
|
4
4
|
/** UI artifact previews already represented by a tool result are not model output. */
|
|
5
5
|
export declare function convertSparkMessages(messages: SparkUIMessage[], tools: ReturnType<typeof createSparkTools>): Promise<ModelMessage[]>;
|
package/dist/model-history.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
import { convertToModelMessages } from "ai";
|
|
1
|
+
import { asSchema, convertToModelMessages } from "ai";
|
|
2
|
+
import { sparkDataPartSchemas } from "./ui.js";
|
|
2
3
|
function record(value) {
|
|
3
4
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
4
5
|
? value
|
|
5
6
|
: {};
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* A stored call's input as the live run handed it to the model: the AI SDK
|
|
10
|
+
* passes on the input its schema validated, in the schema's key order, while
|
|
11
|
+
* storage may return the keys in another order (Postgres JSONB sorts them).
|
|
12
|
+
* An input the schema refuses is replayed as stored.
|
|
13
|
+
*/
|
|
14
|
+
async function liveInput(tools, part) {
|
|
15
|
+
if (part.type !== "dynamic-tool" && !part.type.startsWith("tool-"))
|
|
16
|
+
return part;
|
|
17
|
+
const name = part.type === "dynamic-tool" ? part.toolName : part.type.slice(5);
|
|
18
|
+
const tool = tools[name];
|
|
19
|
+
const input = part.input;
|
|
20
|
+
if (tool === undefined || input === null || typeof input !== "object" || Array.isArray(input))
|
|
21
|
+
return part;
|
|
22
|
+
const validated = await asSchema(tool.inputSchema).validate?.(input);
|
|
23
|
+
return validated?.success ? { ...part, input: validated.value } : part;
|
|
24
|
+
}
|
|
7
25
|
/** UI artifact previews already represented by a tool result are not model output. */
|
|
8
|
-
export function convertSparkMessages(messages, tools) {
|
|
9
|
-
const history = messages.map((message) => {
|
|
26
|
+
export async function convertSparkMessages(messages, tools) {
|
|
27
|
+
const history = await Promise.all(messages.map(async (message) => {
|
|
10
28
|
if (message.role !== "assistant")
|
|
11
29
|
return message;
|
|
12
30
|
const previews = new Set();
|
|
@@ -27,8 +45,20 @@ export function convertSparkMessages(messages, tools) {
|
|
|
27
45
|
}
|
|
28
46
|
return {
|
|
29
47
|
...message,
|
|
30
|
-
parts:
|
|
48
|
+
parts: await Promise.all(message.parts
|
|
49
|
+
.filter((part) => part.type !== "file" || (!previews.has(part.url) && part.providerMetadata?.spark?.inspect !== false))
|
|
50
|
+
.map((part) => liveInput(tools, part))),
|
|
31
51
|
};
|
|
52
|
+
}));
|
|
53
|
+
return convertToModelMessages(history, {
|
|
54
|
+
tools,
|
|
55
|
+
convertDataPart: (part) => {
|
|
56
|
+
if (part.type !== "data-selection")
|
|
57
|
+
return undefined;
|
|
58
|
+
const selection = sparkDataPartSchemas.selection.safeParse(part.data);
|
|
59
|
+
return selection.success
|
|
60
|
+
? { type: "text", text: `[Scene objects selected when this message was sent (node IDs): ${selection.data.nodeIds.join(", ")}]` }
|
|
61
|
+
: undefined;
|
|
62
|
+
},
|
|
32
63
|
});
|
|
33
|
-
return convertToModelMessages(history, { tools });
|
|
34
64
|
}
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type { SparkSkill } from "./capabilities.js";
|
|
2
|
+
export declare const SPARK_AGENT_PROMPT = "You are Spark, Womp's unified assistant.\nUse read_skill({names:[...]}) to load the required APIs together. Skills provide TypeScript\ndeclarations for documentation; execute_javascript runs JavaScript, with top-level await.\nOnly the listed capabilities are available. Reading a skill does not grant additional permissions.\nUse only the supplied womp APIs and output helpers to access application services and state.\nIf a capability is unavailable, explain what is missing and ask the user to enable it when applicable.\nDo not work around missing capabilities by inspecting page globals, DOM or framework internals,\nor making direct network requests. Native execution is not a security sandbox; it does not expand\nthe supported API or the user's granted capabilities.\nGive each execute_javascript call a concise present-progressive purpose, such as \"Generating a car image\", \"Inspecting the scene\", or \"Importing the model\". Write purpose before code.\nEach execution has fresh local variables. Keep stable object, job, and artifact references.\nUse print for concise, filtered observations; await emitImage or emitFile with a host-issued artifact\nreference to show actual output. emitImage inspects by default; use { inspect: false } to display without adding image bytes to model context.\nCompleted generations are shown automatically to the user; image bytes are intentionally omitted from your context. Acknowledge completion without listing, re-emitting, or inspecting outputs unless the task requires it. Use returned artifact references for follow-up work.\nDo not claim visual verification without inspecting the image. Missing artifact references must be recovered or reported; omitted image bytes alone are not a missing result.\nInspect the scene before editing and verify the committed result.\nPreserve existing content unless the user asks to replace or delete it. Use the latest target revision\nwhen a write requires one. Capabilities own transactions: an entire script is not atomic.\nRecover ordinary tool errors yourself: use confirmed receipts and fresh reads, retry only unresolved work,\npreserve successful changes, and never replay an interrupted script. Do not narrate internal failures\nunless recovery cannot continue. Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.\nComplete related work in this conversation without delegating to another agent. Report limits,\nunsupported actions, cancellation, and incomplete work truthfully. You cannot see what your work\ncosts: model use can be billed, not only generations. Never estimate credits used or call work free;\nreport only credit figures a host message or tool provides. Finish with a concise explanation\nof the result in the user's language; keep implementation details out of that explanation.\n\nCreation intent\n\nResolve the requested outcome from the current message and preceding dialogue, not keyword matches.\nExplicit method choices take precedence. Continue edits in the existing representation unless the user\nasks to change it. Creation defaults do not apply to help, discussion, imports, or account questions.\n\n- Build directly with editable scene shapes for ordinary 3D requests, including \"3D X\" and\n \"make X in 3D\", and when the user wants individually editable shapes or geometry that blends\n with existing objects. Organic appearance alone does not require Geometry Nodes.\n\n- Use OpenSCAD for parametric, measured, mechanical, printable, or real-world-fit objects,\n except when another method is explicit (including primitives with dimensions), or when\n parametric organic/procedural geometry, animation, or Geometry Nodes is requested:\n use Geometry Nodes for those, not Workbench Flow. Do not claim print readiness without validation.\n\n- For mesh wompification, read mesh-wompify and scene-editing together and reconstruct only the specified reference using primitives and curves.\n\n- Use mesh generation when the user explicitly requests a mesh, asks to turn an existing or\n attached image into 3D, or chooses a sculpted result rather than individually editable shapes.\n\n- Use image generation for pictures, logos, concept art, wallpapers, and other clearly visual output.\n\n- For an ambiguous creative request without explicit 3D or printing intent, generate one image\n preview first. Then offer one short, outcome-based choice: refine the image, turn it into a\n sculpted 3D piece, or build it from individually editable shapes. Offer adjustable CAD instead\n of editable shapes when the subject is measured or must fit something real.\n\n- Also preview first when a 3D request without a named method depends on likeness or surface\n detail that shapes cannot carry (characters, creatures, people, food, sculptures, specific\n vehicles or branded items). Plain functional objects still build directly.\n\n- Generating a result does not authorize importing, converting, or building it in the scene.\n\n- Use Workbench Flow for image/mesh generation explicitly requested through Flow/nodes or\n continued edits to that Flow workflow, unless the user asks for an inline result. Flow must be\n open; explain that prerequisite if closed instead of silently generating inline. Merely opening\n Flow does not redirect ordinary scene requests or inline generation to it.\n\n- After a preview, offer the choice above. After other completed generation, you may briefly\n suggest a relevant next step using available capabilities. Describe its outcome, not internal tools. Suggestions are optional,\n not a mandatory menu, and never authorize actions. Wait for a clear choice and follow existing\n confirmations. Reuse the existing artifact/reference for the chosen follow-up, including as the\n image-to-mesh input, instead of regenerating the source. Preserve its defining features when building it.\n\n- Choose construction methods by the user's requested workflow and the\n available capabilities, not by assuming editable shapes require lower\n fidelity. Outside the preview defaults above, a sculpted appearance does not by itself\n require mesh generation.\n Respect an explicit method choice and pursue the reference's visible shape\n and detail with that method. Offer alternatives only for a specific\n demonstrated limitation; ask before switching or simplifying the requested\n result. No method guarantees exact reconstruction from a single image.\n\n- Ask at most one focused question at a time, only when a sensible default\n would likely produce the wrong outcome. Do not repeat resolved questions.\n Interpret replies using the preceding offer; if several options were\n offered and \u201Cyes\u201D does not identify one, clarify briefly.\n\n- Treat a clear answer to your question as an instruction to proceed.\n Continue from existing results rather than restarting the workflow.\n\nOffer only workflows available in this host. Follow their authorization\nand confirmation requirements before generation or scene changes. If a requested workflow is unavailable,\nexplain the limitation rather than silently substituting another.";
|
|
3
|
+
/**
|
|
4
|
+
* The system prompt every host sends: the agent prompt and the skills directory.
|
|
5
|
+
* It must stay identical across the turns and steps of a conversation, or the
|
|
6
|
+
* provider's prompt cache misses; per-turn state travels in messages instead.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sparkSystemPrompt(skills: readonly Pick<SparkSkill, "name" | "version" | "description">[], instructions?: string): string;
|
package/dist/prompt.js
CHANGED
|
@@ -16,8 +16,9 @@ Do not claim visual verification without inspecting the image. Missing artifact
|
|
|
16
16
|
Inspect the scene before editing and verify the committed result.
|
|
17
17
|
Preserve existing content unless the user asks to replace or delete it. Use the latest target revision
|
|
18
18
|
when a write requires one. Capabilities own transactions: an entire script is not atomic.
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Recover ordinary tool errors yourself: use confirmed receipts and fresh reads, retry only unresolved work,
|
|
20
|
+
preserve successful changes, and never replay an interrupted script. Do not narrate internal failures
|
|
21
|
+
unless recovery cannot continue. Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.
|
|
21
22
|
Complete related work in this conversation without delegating to another agent. Report limits,
|
|
22
23
|
unsupported actions, cancellation, and incomplete work truthfully. You cannot see what your work
|
|
23
24
|
costs: model use can be billed, not only generations. Never estimate credits used or call work free;
|
|
@@ -88,3 +89,15 @@ asks to change it. Creation defaults do not apply to help, discussion, imports,
|
|
|
88
89
|
Offer only workflows available in this host. Follow their authorization
|
|
89
90
|
and confirmation requirements before generation or scene changes. If a requested workflow is unavailable,
|
|
90
91
|
explain the limitation rather than silently substituting another.`;
|
|
92
|
+
/**
|
|
93
|
+
* The system prompt every host sends: the agent prompt and the skills directory.
|
|
94
|
+
* It must stay identical across the turns and steps of a conversation, or the
|
|
95
|
+
* provider's prompt cache misses; per-turn state travels in messages instead.
|
|
96
|
+
*/
|
|
97
|
+
export function sparkSystemPrompt(skills, instructions = SPARK_AGENT_PROMPT) {
|
|
98
|
+
return [
|
|
99
|
+
instructions,
|
|
100
|
+
"Available skills (read before using their APIs):",
|
|
101
|
+
...skills.map(({ name, version, description }) => `- ${name} (${version}): ${description}`),
|
|
102
|
+
].join("\n\n");
|
|
103
|
+
}
|
|
@@ -9,7 +9,7 @@ export type ResolvedSparkModel = {
|
|
|
9
9
|
};
|
|
10
10
|
export type SparkRunExecutorOptions = {
|
|
11
11
|
resolveModel(settings: SparkRunSettings): ResolvedSparkModel | Promise<ResolvedSparkModel>;
|
|
12
|
-
createHost(context: SparkRunExecutionContext): Promise<Pick<SparkAgentOptions, "capabilities" | "executor" | "artifacts" | "persistence"
|
|
12
|
+
createHost(context: SparkRunExecutionContext): Promise<Pick<SparkAgentOptions, "capabilities" | "executor" | "artifacts" | "persistence"> & {
|
|
13
13
|
dispose(): void;
|
|
14
14
|
}>;
|
|
15
15
|
};
|
package/dist/server/executor.js
CHANGED
|
@@ -23,7 +23,6 @@ export function createSparkRunExecutor(options) {
|
|
|
23
23
|
runId: context.runId,
|
|
24
24
|
messageId: context.assistantMessageId,
|
|
25
25
|
signal: context.signal,
|
|
26
|
-
context: host.context,
|
|
27
26
|
});
|
|
28
27
|
return { stream: run.stream, result: run.result.finally(() => host.dispose()) };
|
|
29
28
|
}
|
package/dist/server/service.js
CHANGED
|
@@ -57,7 +57,8 @@ function normalizeUserMessage(message) {
|
|
|
57
57
|
throw new SparkServiceError(400, "A new chat message must have the user role.");
|
|
58
58
|
}
|
|
59
59
|
for (const part of message.parts) {
|
|
60
|
-
|
|
60
|
+
// The data schemas are checked with the rest of the message (validateUIMessages).
|
|
61
|
+
if (part.type === "text" || part.type === "data-selection")
|
|
61
62
|
continue;
|
|
62
63
|
if (part.type === "file" &&
|
|
63
64
|
part.mediaType.startsWith("image/") &&
|
package/dist/ui.d.ts
CHANGED
|
@@ -89,6 +89,11 @@ export type SparkDataParts = {
|
|
|
89
89
|
toolCallId: string;
|
|
90
90
|
receipt: ExecutionReceipt;
|
|
91
91
|
};
|
|
92
|
+
selection: SparkSelectionData;
|
|
93
|
+
};
|
|
94
|
+
/** Editor node IDs selected when a user message was sent; never rendered as chat text. */
|
|
95
|
+
export type SparkSelectionData = {
|
|
96
|
+
nodeIds: string[];
|
|
92
97
|
};
|
|
93
98
|
export type ExecuteJavaScriptInput = {
|
|
94
99
|
purpose: string;
|
|
@@ -321,6 +326,13 @@ export declare const sparkDataPartSchemas: {
|
|
|
321
326
|
}, z.core.$strip>>;
|
|
322
327
|
usage: z.ZodOptional<z.ZodUnknown>;
|
|
323
328
|
}, z.core.$strip>;
|
|
329
|
+
/**
|
|
330
|
+
* The editor selection when a user message was sent. Hosts attach it to that
|
|
331
|
+
* message and do not render it; the model reads it as text (model-history.ts).
|
|
332
|
+
*/
|
|
333
|
+
readonly selection: z.ZodObject<{
|
|
334
|
+
nodeIds: z.ZodArray<z.ZodString>;
|
|
335
|
+
}, z.core.$strip>;
|
|
324
336
|
};
|
|
325
|
-
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt,
|
|
337
|
+
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt, wompifyContextSchema, type WompifyRequest, type WompifyContext } from "./wompify.js";
|
|
326
338
|
export { parseSparkGoalCommand, DEFAULT_GOAL_CREDITS } from "./goals.js";
|
package/dist/ui.js
CHANGED
|
@@ -79,6 +79,12 @@ export const sparkDataPartSchemas = {
|
|
|
79
79
|
cost: sparkStepCostSchema.optional(),
|
|
80
80
|
usage: z.unknown().optional(),
|
|
81
81
|
}),
|
|
82
|
+
/**
|
|
83
|
+
* The editor selection when a user message was sent. Hosts attach it to that
|
|
84
|
+
* message and do not render it; the model reads it as text (model-history.ts).
|
|
85
|
+
*/
|
|
86
|
+
// Editor ids are the engine's decimal integers; 8192 covers a select-all at the engine's budgets.
|
|
87
|
+
selection: z.object({ nodeIds: z.array(z.string().min(1).max(64)).min(1).max(8192) }),
|
|
82
88
|
};
|
|
83
|
-
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt,
|
|
89
|
+
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt, wompifyContextSchema } from "./wompify.js";
|
|
84
90
|
export { parseSparkGoalCommand, DEFAULT_GOAL_CREDITS } from "./goals.js";
|
package/dist/wompify.d.ts
CHANGED
|
@@ -29,8 +29,11 @@ export declare const wompifyContextSchema: z.ZodObject<{
|
|
|
29
29
|
}>;
|
|
30
30
|
}, z.core.$strip>;
|
|
31
31
|
export type WompifyContext = z.infer<typeof wompifyContextSchema>;
|
|
32
|
-
|
|
33
|
-
export declare function wompifyPrompt(
|
|
32
|
+
/** The Wompify request message, which is also the goal objective every continuation repeats. */
|
|
33
|
+
export declare function wompifyPrompt(request: WompifyContext & {
|
|
34
|
+
referenceName: string;
|
|
35
|
+
note?: string;
|
|
36
|
+
}): string;
|
|
34
37
|
export declare function parseWompifyCommand(text: string): {
|
|
35
38
|
preset: WompifyRequest["preset"];
|
|
36
39
|
note: string;
|
package/dist/wompify.js
CHANGED
|
@@ -15,13 +15,12 @@ export const wompifyContextSchema = z.object({
|
|
|
15
15
|
referenceNamePrefix: z.string().trim().min(1).max(200),
|
|
16
16
|
preset: z.enum(["simple", "high", "ultra"]),
|
|
17
17
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return `
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return `Wompify ${JSON.stringify(referenceName)} at tier ${WOMPIFY_PRESETS[preset].label}.${note.trim() ? `\n${note.trim()}` : ""}`;
|
|
18
|
+
/** The Wompify request message, which is also the goal objective every continuation repeats. */
|
|
19
|
+
export function wompifyPrompt(request) {
|
|
20
|
+
const note = request.note?.trim();
|
|
21
|
+
return `Wompify ${JSON.stringify(request.referenceName)} at tier ${WOMPIFY_PRESETS[request.preset].label}.`
|
|
22
|
+
+ `\nUse the mesh-wompify skill for reference name prefix ${JSON.stringify(request.referenceNamePrefix)}. Stop if that skill is unavailable.`
|
|
23
|
+
+ (note ? `\n${note}` : "");
|
|
25
24
|
}
|
|
26
25
|
export function parseWompifyCommand(text) {
|
|
27
26
|
const match = /^\/wompify (simple|high|ultra)(?:\n([\s\S]*))?$/u.exec(text.trim());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@womp/spark-agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Portable skills-based AI agent with local and durable chat transports for Womp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
"dist/**/*.d.ts",
|
|
74
74
|
"dist/server/fixtures/**/*.json",
|
|
75
75
|
"dist/geometry/schemas/**/*.json",
|
|
76
|
-
"README.md"
|
|
76
|
+
"README.md",
|
|
77
|
+
"CHANGELOG.md"
|
|
77
78
|
],
|
|
78
79
|
"sideEffects": false,
|
|
79
80
|
"engines": {
|