@womp/spark-agent 3.0.3 → 4.0.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.
Files changed (76) hide show
  1. package/README.md +59 -33
  2. package/dist/agent-run-cli.d.ts +2 -0
  3. package/dist/agent-run-cli.js +30 -0
  4. package/dist/agent-run-input.d.ts +11 -0
  5. package/dist/agent-run-input.js +59 -0
  6. package/dist/agent-run.d.ts +81 -0
  7. package/dist/agent-run.js +234 -0
  8. package/dist/agent-tools.js +3 -2
  9. package/dist/agent.d.ts +0 -2
  10. package/dist/agent.js +4 -36
  11. package/dist/capabilities/account.js +15 -15
  12. package/dist/capabilities/asset-imports.js +50 -50
  13. package/dist/capabilities/assets.js +10 -10
  14. package/dist/capabilities/flow.js +15 -15
  15. package/dist/capabilities/geometry.js +12 -12
  16. package/dist/capabilities/knowledge.js +4 -4
  17. package/dist/capabilities/openscad-instructions.d.ts +1 -1
  18. package/dist/capabilities/openscad-instructions.js +83 -80
  19. package/dist/capabilities/scene.d.ts +11 -3
  20. package/dist/capabilities/scene.js +88 -27
  21. package/dist/capabilities.d.ts +1 -2
  22. package/dist/capabilities.js +0 -1
  23. package/dist/client.d.ts +5 -0
  24. package/dist/client.js +22 -0
  25. package/dist/coordinates.d.ts +0 -7
  26. package/dist/coordinates.js +8 -47
  27. package/dist/generated/scene-declarations.d.ts +1 -1
  28. package/dist/generated/scene-declarations.js +1 -1
  29. package/dist/geometry/schemas/flowir-dialect.json +2 -2
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +0 -1
  32. package/dist/models.d.ts +7 -0
  33. package/dist/models.js +10 -0
  34. package/dist/prompt.js +18 -18
  35. package/dist/request-limits.d.ts +1 -0
  36. package/dist/request-limits.js +1 -0
  37. package/dist/run-metrics.d.ts +15 -0
  38. package/dist/run-metrics.js +30 -0
  39. package/dist/runtime.d.ts +3 -5
  40. package/dist/scene-metadata.js +1 -1
  41. package/dist/sdk-scene-host.d.ts +11 -13
  42. package/dist/sdk-scene-host.js +1 -0
  43. package/dist/sdk-scene-screenshot.d.ts +4 -6
  44. package/dist/sdk-scene-screenshot.js +14 -15
  45. package/dist/server/artifacts.js +1 -5
  46. package/dist/server/executor.d.ts +0 -1
  47. package/dist/server/executor.js +0 -1
  48. package/dist/server/express.js +17 -5
  49. package/dist/server/scene-actions.d.ts +2 -0
  50. package/dist/server/scene-actions.js +20 -0
  51. package/dist/server/service.d.ts +2 -0
  52. package/dist/server/service.js +10 -0
  53. package/dist/server/sqlite-store.d.ts +3 -1
  54. package/dist/server/sqlite-store.js +26 -3
  55. package/dist/server/standalone.d.ts +2 -0
  56. package/dist/server/standalone.js +54 -3
  57. package/dist/server/store.d.ts +3 -1
  58. package/dist/server/store.js +21 -1
  59. package/dist/server/types.d.ts +5 -0
  60. package/dist/simple-bench-cli.d.ts +2 -0
  61. package/dist/simple-bench-cli.js +64 -0
  62. package/dist/simple-bench-prompts.d.ts +28 -0
  63. package/dist/simple-bench-prompts.js +11 -0
  64. package/dist/simple-bench.d.ts +15 -0
  65. package/dist/simple-bench.js +175 -0
  66. package/dist/types.d.ts +4 -13
  67. package/dist/ui.d.ts +3 -8
  68. package/dist/womp-scene-api.d.ts +14 -3
  69. package/dist/womp-scene-api.js +91 -72
  70. package/package.json +7 -3
  71. package/dist/capabilities/health.d.ts +0 -9
  72. package/dist/capabilities/health.js +0 -27
  73. package/dist/context.d.ts +0 -15
  74. package/dist/context.js +0 -266
  75. package/dist/generated/health-declarations.d.ts +0 -1
  76. package/dist/generated/health-declarations.js +0 -3
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export { runSparkAgent, } from "./agent.js";
2
2
  export { createSparkTools } from "./agent-tools.js";
3
3
  export { convertSparkMessages } from "./model-history.js";
4
- export { prepareSparkContext, estimateSparkContextTokens, SparkContextLimitError, } from "./context.js";
5
4
  export * from "./capabilities.js";
6
5
  export { createSdkSceneEditor, createSdkSceneHost, } from "./sdk-scene-host.js";
7
6
  export { recoverSceneOperation } from "./scene-operations.js";
package/dist/models.d.ts CHANGED
@@ -7,33 +7,40 @@ export type SparkModel = {
7
7
  note: string;
8
8
  provider: ModelProvider;
9
9
  reasoningEfforts: readonly ReasoningEffort[];
10
+ /** Published capacity for display only; providers enforce their own limits. */
11
+ contextWindow?: number;
10
12
  };
11
13
  export declare const SPARK_MODELS: readonly [{
12
14
  readonly id: "gpt-6-astra";
15
+ readonly contextWindow: 1050000;
13
16
  readonly label: "GPT-6 Astra";
14
17
  readonly note: "Best OpenAI quality";
15
18
  readonly provider: "openai";
16
19
  readonly reasoningEfforts: readonly ["low", "medium", "high", "xhigh", "max"];
17
20
  }, {
18
21
  readonly id: "gpt-5.6-luna";
22
+ readonly contextWindow: 1050000;
19
23
  readonly label: "GPT-5.6 Luna";
20
24
  readonly note: "Fast, cost-efficient OpenAI model";
21
25
  readonly provider: "openai";
22
26
  readonly reasoningEfforts: readonly ["low", "medium", "high", "xhigh", "max"];
23
27
  }, {
24
28
  readonly id: "gemini-3.8-flash";
29
+ readonly contextWindow: 1048576;
25
30
  readonly label: "Gemini 3.8 Flash";
26
31
  readonly note: "Fast Google model";
27
32
  readonly provider: "google";
28
33
  readonly reasoningEfforts: readonly ["low", "medium", "high"];
29
34
  }, {
30
35
  readonly id: "claude-opus-5";
36
+ readonly contextWindow: 1000000;
31
37
  readonly label: "Opus 5";
32
38
  readonly note: "Best Anthropic quality";
33
39
  readonly provider: "anthropic";
34
40
  readonly reasoningEfforts: readonly ["low", "medium", "high", "xhigh", "max"];
35
41
  }, {
36
42
  readonly id: "claude-fable-5";
43
+ readonly contextWindow: 1000000;
37
44
  readonly label: "Fable 5";
38
45
  readonly note: "Fast Anthropic model";
39
46
  readonly provider: "anthropic";
package/dist/models.js CHANGED
@@ -15,6 +15,8 @@ const EXTENDED_REASONING_EFFORTS = [
15
15
  export const SPARK_MODELS = [
16
16
  {
17
17
  id: "gpt-6-astra",
18
+ // https://developers.openai.com/api/docs/models/gpt-6-astra
19
+ contextWindow: 1_050_000,
18
20
  label: "GPT-6 Astra",
19
21
  note: "Best OpenAI quality",
20
22
  provider: "openai",
@@ -22,6 +24,8 @@ export const SPARK_MODELS = [
22
24
  },
23
25
  {
24
26
  id: "gpt-5.6-luna",
27
+ // https://developers.openai.com/api/docs/models/gpt-5.6-luna
28
+ contextWindow: 1_050_000,
25
29
  label: "GPT-5.6 Luna",
26
30
  note: "Fast, cost-efficient OpenAI model",
27
31
  provider: "openai",
@@ -29,6 +33,8 @@ export const SPARK_MODELS = [
29
33
  },
30
34
  {
31
35
  id: "gemini-3.8-flash",
36
+ // https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash
37
+ contextWindow: 1_048_576,
32
38
  label: "Gemini 3.8 Flash",
33
39
  note: "Fast Google model",
34
40
  provider: "google",
@@ -36,6 +42,8 @@ export const SPARK_MODELS = [
36
42
  },
37
43
  {
38
44
  id: "claude-opus-5",
45
+ // https://platform.claude.com/docs/en/models/opus-5/whats-new-opus-5
46
+ contextWindow: 1_000_000,
39
47
  label: "Opus 5",
40
48
  note: "Best Anthropic quality",
41
49
  provider: "anthropic",
@@ -43,6 +51,8 @@ export const SPARK_MODELS = [
43
51
  },
44
52
  {
45
53
  id: "claude-fable-5",
54
+ // https://platform.claude.com/docs/en/models/fable-5/introducing-claude-fable-5-and-claude-mythos-5
55
+ contextWindow: 1_000_000,
46
56
  label: "Fable 5",
47
57
  note: "Fast Anthropic model",
48
58
  provider: "anthropic",
package/dist/prompt.js CHANGED
@@ -1,19 +1,19 @@
1
- export const SPARK_AGENT_PROMPT = `You are Spark, Womp's unified assistant.
2
- Use read_skill to discover the APIs needed for the user's request. Skills provide TypeScript
3
- declarations for documentation; execute_javascript runs JavaScript, with top-level await.
4
- Only the listed capabilities are available. Reading a skill does not grant additional permissions.
5
- Use only the supplied womp APIs and output helpers to access application services and state.
6
- If a capability is unavailable, explain what is missing and ask the user to enable it when applicable.
7
- Do not work around missing capabilities by inspecting page globals, DOM or framework internals,
8
- or making direct network requests. Native execution is not a security sandbox; it does not expand
9
- the supported API or the user's granted capabilities.
10
- Each execution has fresh local variables. Keep stable object, job, and artifact references.
11
- Use print for concise, filtered observations; await emitImage or emitFile with a host-issued artifact
12
- reference to show actual output. Inspect the scene before editing and verify the committed result.
13
- Preserve existing content unless the user asks to replace or delete it. Use the latest target revision
14
- when a write requires one. Capabilities own transactions: an entire script is not atomic.
15
- If an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.
16
- Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.
17
- Complete related work in this conversation without delegating to another agent. Report limits,
18
- unsupported actions, cancellation, and incomplete work truthfully. Finish with a concise explanation
1
+ export const SPARK_AGENT_PROMPT = `You are Spark, Womp's unified assistant.
2
+ Use read_skill to discover the APIs needed for the user's request. Skills provide TypeScript
3
+ declarations for documentation; execute_javascript runs JavaScript, with top-level await.
4
+ Only the listed capabilities are available. Reading a skill does not grant additional permissions.
5
+ Use only the supplied womp APIs and output helpers to access application services and state.
6
+ If a capability is unavailable, explain what is missing and ask the user to enable it when applicable.
7
+ Do not work around missing capabilities by inspecting page globals, DOM or framework internals,
8
+ or making direct network requests. Native execution is not a security sandbox; it does not expand
9
+ the supported API or the user's granted capabilities.
10
+ Each execution has fresh local variables. Keep stable object, job, and artifact references.
11
+ Use print for concise, filtered observations; await emitImage or emitFile with a host-issued artifact
12
+ reference to show actual output. Inspect the scene before editing and verify the committed result.
13
+ Preserve existing content unless the user asks to replace or delete it. Use the latest target revision
14
+ when a write requires one. Capabilities own transactions: an entire script is not atomic.
15
+ If an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.
16
+ Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.
17
+ Complete related work in this conversation without delegating to another agent. Report limits,
18
+ unsupported actions, cancellation, and incomplete work truthfully. Finish with a concise explanation
19
19
  of the result in the user's language; keep implementation details out of that explanation.`;
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_JSON_LIMIT_BYTES: number;
@@ -0,0 +1 @@
1
+ export const DEFAULT_JSON_LIMIT_BYTES = 25 * 1024 * 1024;
@@ -0,0 +1,15 @@
1
+ import type { SparkStepMetricsData, SparkUIMessageChunk } from "./ui.js";
2
+ export declare function createBenchMetrics(): {
3
+ accept(chunk: SparkUIMessageChunk): void;
4
+ details(): {
5
+ toolCounts: Record<string, number>;
6
+ steps: SparkStepMetricsData[];
7
+ };
8
+ snapshot(): {
9
+ steps: number;
10
+ readSkill: number;
11
+ executeJavaScript: number;
12
+ costUsd: number | null;
13
+ costStatus: "unavailable" | "partial" | "estimated";
14
+ };
15
+ };
@@ -0,0 +1,30 @@
1
+ export function createBenchMetrics() {
2
+ const steps = new Map();
3
+ const calls = new Map();
4
+ return {
5
+ accept(chunk) {
6
+ if (chunk.type === "data-step-metrics")
7
+ steps.set(chunk.data.step, chunk.data);
8
+ if (chunk.type === "tool-input-available")
9
+ calls.set(chunk.toolCallId, chunk.toolName);
10
+ },
11
+ details() {
12
+ const toolCounts = {};
13
+ for (const name of calls.values())
14
+ toolCounts[name] = (toolCounts[name] ?? 0) + 1;
15
+ return { toolCounts, steps: Array.from(steps.values()) };
16
+ },
17
+ snapshot() {
18
+ const names = Array.from(calls.values());
19
+ const priced = Array.from(steps.values()).map((step) => step.cost?.totalUsd ?? null)
20
+ .filter((cost) => cost !== null);
21
+ return {
22
+ steps: steps.size,
23
+ readSkill: names.filter((name) => name === "read_skill").length,
24
+ executeJavaScript: names.filter((name) => name === "execute_javascript").length,
25
+ costUsd: priced.length ? priced.reduce((sum, cost) => sum + cost, 0) : null,
26
+ costStatus: (priced.length === 0 ? "unavailable" : priced.length < steps.size ? "partial" : "estimated"),
27
+ };
28
+ },
29
+ };
30
+ }
package/dist/runtime.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { KakapoAPI, type ScreenshotView } from "@womp/kakapo-sdk";
1
+ import { KakapoAPI } from "@womp/kakapo-sdk";
2
+ import type { SceneScreenshotOptions } from "./capabilities/scene.js";
2
3
  import type { JavaScriptExecutor } from "./executor.js";
3
4
  import type { ExecutionResult, ExecutionScreenshot, SparkRuntimeEventSink, SparkRuntime } from "./types.js";
4
5
  import { type WompSceneApi } from "./womp-scene-api.js";
@@ -31,9 +32,6 @@ export declare class KakapoRuntime implements SparkRuntime {
31
32
  scene: import("@womp/kakapo-sdk").Scene;
32
33
  }>;
33
34
  edit<T>(callback: (draft: WompSceneApi) => Promise<T> | T, expectedRevision: string, context: SparkInvocationContext): Promise<T>;
34
- screenshot(input: {
35
- targetId?: number;
36
- view?: ScreenshotView;
37
- }): Promise<ExecutionScreenshot>;
35
+ screenshot(input: SceneScreenshotOptions): Promise<ExecutionScreenshot>;
38
36
  execute(code: string, purpose: string): Promise<ExecutionResult>;
39
37
  }
@@ -41,7 +41,7 @@ export function shiftCurvePointMetadata(scene, curveId, index, direction) {
41
41
  if (direction === 1) {
42
42
  points[index] = {
43
43
  locked: false,
44
- libraryId: metadataObject(points[String(Math.max(0, index - 1))]).libraryId ?? -1,
44
+ libraryId: -1,
45
45
  name: `Point ${index + 1}`,
46
46
  };
47
47
  }
@@ -1,6 +1,6 @@
1
1
  import type { FontFamily, KakapoAPI, SceneEdit, MaterialResource } from "@womp/kakapo-sdk";
2
2
  import type { ArtifactRef, SparkInvocationContext } from "./capabilities.js";
3
- import type { SceneHost, SceneScreenshotOptions } from "./capabilities/scene.js";
3
+ import type { SceneHost, SceneHealthHost, SceneScreenshotOptions } from "./capabilities/scene.js";
4
4
  import { type SceneOperation, type SceneOperationStore } from "./scene-operations.js";
5
5
  import type { SceneHdr } from "./scene-environment.js";
6
6
  export type SdkSceneServices = {
@@ -82,6 +82,11 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
82
82
  nodeCreate: (type: number, parentId?: number) => number;
83
83
  nodeDelete: (id: number) => void;
84
84
  nodeClone: (id: number) => number;
85
+ cloneSubtree: (id: number, options?: {
86
+ parentId?: number;
87
+ name?: string;
88
+ index?: number;
89
+ }) => Promise<number>;
85
90
  nodeList: () => number[];
86
91
  nodeChildren: (parentId: number) => number[];
87
92
  nodeSetParent: (id: number, parentId: number, index?: number) => void;
@@ -118,7 +123,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
118
123
  nodeGetMaterialId: (id: number) => number | null;
119
124
  nodeSetMaterialId: (id: number, materialId: number) => void;
120
125
  nodeGetResolution: (id: number) => number;
121
- nodeSetResolution: (id: number, resolution: number) => void;
126
+ nodeSetUnionResolution: (unionId: number, resolution: number) => void;
122
127
  nodeGetLightType: (id: number) => number;
123
128
  nodeSetLightType: (id: number, lightType: number) => void;
124
129
  nodeGetPower: (id: number) => number;
@@ -164,16 +169,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
164
169
  curvePointSetHandleIn: (curveId: number, pointIndex: number, value: import("@womp/kakapo-sdk").Vec3) => void;
165
170
  curvePointSetHandleOut: (curveId: number, pointIndex: number, value: import("@womp/kakapo-sdk").Vec3) => void;
166
171
  curvePointCount: (curveId: number) => number;
167
- curvePointAdd: (curveId: number, point?: {
168
- name?: string;
169
- pos?: import("@womp/kakapo-sdk").Vec3;
170
- rot?: import("@womp/kakapo-sdk").Vec3;
171
- size?: import("@womp/kakapo-sdk").Vec3;
172
- fixed?: boolean;
173
- round?: number;
174
- materialId?: number;
175
- }) => number;
176
- curvePointInsert: (curveId: number, pointIndex: number, point?: {
172
+ curvePointAdd: (curveId: number, point: {
177
173
  name?: string;
178
174
  pos?: import("@womp/kakapo-sdk").Vec3;
179
175
  rot?: import("@womp/kakapo-sdk").Vec3;
@@ -181,7 +177,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
181
177
  fixed?: boolean;
182
178
  round?: number;
183
179
  materialId?: number;
184
- }) => number;
180
+ }, pointIndex?: number) => number;
185
181
  curvePointRemove: (curveId: number, pointIndex: number) => void;
186
182
  curvePointGetName: (curveId: number, pointIndex: number) => string;
187
183
  curvePointSetName: (curveId: number, pointIndex: number, name: string) => void;
@@ -285,6 +281,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
285
281
  max: import("@womp/kakapo-sdk").Vec3;
286
282
  }) => number;
287
283
  lookAt: (origin: import("@womp/kakapo-sdk").Vec3, target: import("@womp/kakapo-sdk").Vec3) => import("@womp/kakapo-sdk").Vec3;
284
+ alignAxis: (direction: import("@womp/kakapo-sdk").Vec3, axis: "x" | "y" | "z") => import("@womp/kakapo-sdk").Vec3;
288
285
  scenePropertiesRead: () => {
289
286
  hdr_name: string | undefined;
290
287
  hdr_multiplier: number | undefined;
@@ -306,6 +303,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
306
303
  export declare function createSdkSceneEditor(options: SdkSceneEditorOptions): SdkSceneEditor;
307
304
  export declare function createSdkSceneHost(options: SdkSceneEditorOptions & {
308
305
  editor?: SdkSceneEditor;
306
+ health?: SceneHealthHost;
309
307
  services(api: KakapoAPI, context: SparkInvocationContext): Promise<SdkSceneServices>;
310
308
  screenshot(options: SceneScreenshotOptions, context: SparkInvocationContext): Promise<ArtifactRef>;
311
309
  onSceneCommitted?(operation: SceneOperation, api: KakapoAPI): Promise<Record<string, unknown> | void>;
@@ -193,6 +193,7 @@ export function createSdkSceneEditor(options) {
193
193
  export function createSdkSceneHost(options) {
194
194
  const editor = options.editor ?? createSdkSceneEditor(options);
195
195
  return {
196
+ health: options.health?.read.bind(options.health),
196
197
  read: (context) => editor.read(context, async (api) => {
197
198
  const scene = api.getScene();
198
199
  const revision = await api.getSceneRevision();
@@ -1,7 +1,5 @@
1
- import type { KakapoAPI, ScreenshotView } from "@womp/kakapo-sdk";
1
+ import type { KakapoAPI } from "@womp/kakapo-sdk";
2
+ import type { SceneScreenshotOptions } from "./capabilities/scene.js";
2
3
  import type { ExecutionScreenshot } from "./types.js";
3
- /** Native capture policy shared by SDK-backed hosts; camera permissions belong to the host. */
4
- export declare function captureSdkSceneScreenshot(api: KakapoAPI, input: {
5
- targetId?: number;
6
- view?: ScreenshotView;
7
- }): Promise<ExecutionScreenshot>;
4
+ /** Convert Womp scene coordinates at the host boundary; never fit a camera implicitly. */
5
+ export declare function captureSdkSceneScreenshot(api: KakapoAPI, input: SceneScreenshotOptions): Promise<ExecutionScreenshot>;
@@ -1,17 +1,16 @@
1
- /** Native capture policy shared by SDK-backed hosts; camera permissions belong to the host. */
1
+ import { INTERNAL_UNITS_PER_MM, sceneUnitMultiplier, toEnginePosition } from "./coordinates.js";
2
+ /** Convert Womp scene coordinates at the host boundary; never fit a camera implicitly. */
2
3
  export async function captureSdkSceneScreenshot(api, input) {
3
- const view = input.view ?? (input.targetId === undefined ? "current" : "isometric");
4
- let targetId = input.targetId;
5
- if (view !== "current") {
6
- await api.refreshScene();
7
- if (targetId !== undefined) {
8
- let target = api.getNode(targetId);
9
- while (target.parentId !== null && target.parentId !== 0) {
10
- target = api.getNode(target.parentId);
11
- }
12
- if (target.kind === "union" || target.kind === "group")
13
- targetId = target.id;
14
- }
15
- }
16
- return api.captureScreenshot({ targetId, view, timeoutMs: 30_000 });
4
+ if (!input.camera)
5
+ return api.captureScreenshot({ timeoutMs: 30_000 });
6
+ const scene = await api.refreshScene();
7
+ const units = sceneUnitMultiplier(scene.properties.unit_multiplier);
8
+ const camera = {
9
+ ...input.camera,
10
+ position: toEnginePosition(input.camera.position, units),
11
+ target: toEnginePosition(input.camera.target, units),
12
+ };
13
+ if (camera.orthographicWidth !== undefined)
14
+ camera.orthographicWidth *= INTERNAL_UNITS_PER_MM / units;
15
+ return api.captureScreenshot({ camera, timeoutMs: 30_000 });
17
16
  }
@@ -11,13 +11,9 @@ export class MemorySparkArtifactStore {
11
11
  ownerId,
12
12
  url: `${this.publicBaseUrl}/${id}`,
13
13
  mediaType: screenshot.mediaType,
14
- name: `spark-${screenshot.view}.jpg`,
14
+ name: "spark-screenshot.jpg",
15
15
  width: screenshot.width,
16
16
  height: screenshot.height,
17
- metadata: {
18
- view: screenshot.view,
19
- targetId: screenshot.targetId,
20
- },
21
17
  data: screenshot.data.slice(),
22
18
  };
23
19
  this.artifacts.set(id, artifact);
@@ -6,7 +6,6 @@ export type ResolvedSparkModel = {
6
6
  modelId: string;
7
7
  providerOptions?: SparkAgentOptions["providerOptions"];
8
8
  maxOutputTokens?: number;
9
- maxInputTokens?: number;
10
9
  };
11
10
  export type SparkRunExecutorOptions = {
12
11
  resolveModel(settings: SparkRunSettings): ResolvedSparkModel | Promise<ResolvedSparkModel>;
@@ -20,7 +20,6 @@ export function createSparkRunExecutor(options) {
20
20
  modelId: resolved.modelId,
21
21
  providerOptions: resolved.providerOptions,
22
22
  maxOutputTokens: resolved.maxOutputTokens,
23
- maxInputTokens: resolved.maxInputTokens,
24
23
  runId: context.runId,
25
24
  messageId: context.assistantMessageId,
26
25
  signal: context.signal,
@@ -3,7 +3,12 @@ import express from "express";
3
3
  import { z } from "zod";
4
4
  import { SPARK_RUN_STATUSES } from "../ui.js";
5
5
  import { SparkServiceError } from "./service.js";
6
+ import { DEFAULT_JSON_LIMIT_BYTES } from "../request-limits.js";
6
7
  const reasoningEffortSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
8
+ const settingsSchema = z.object({
9
+ modelId: z.string().min(1),
10
+ reasoningEffort: reasoningEffortSchema,
11
+ });
7
12
  const messageSchema = z.object({
8
13
  id: z.string().min(1),
9
14
  role: z.enum(["system", "user", "assistant"]),
@@ -14,10 +19,7 @@ const submitSchema = z.object({
14
19
  trigger: z.enum(["submit-message", "regenerate-message"]),
15
20
  message: messageSchema,
16
21
  messageId: z.string().optional(),
17
- settings: z.object({
18
- modelId: z.string().min(1),
19
- reasoningEffort: reasoningEffortSchema,
20
- }),
22
+ settings: settingsSchema,
21
23
  });
22
24
  function parseSequence(value) {
23
25
  if (typeof value !== "string" || /^\d+$/u.test(value) === false)
@@ -37,7 +39,7 @@ export function createSparkAgentRouter(options) {
37
39
  const router = express.Router();
38
40
  const resolveOwnerId = options.resolveOwnerId ?? (() => "local");
39
41
  const heartbeatMs = options.heartbeatMs ?? 15_000;
40
- router.use(express.json({ limit: options.jsonLimit ?? "25mb" }));
42
+ router.use(express.json({ limit: options.jsonLimit ?? DEFAULT_JSON_LIMIT_BYTES }));
41
43
  router.get("/conversations", async (request, response) => {
42
44
  const ownerId = await resolveOwnerId(request);
43
45
  response.json({ conversations: await options.service.listConversations(ownerId) });
@@ -56,6 +58,16 @@ export function createSparkAgentRouter(options) {
56
58
  const conversation = await options.service.getConversation(ownerId, request.params.conversationId);
57
59
  response.json({ conversation });
58
60
  });
61
+ router.patch("/conversations/:conversationId/settings", async (request, response) => {
62
+ const ownerId = await resolveOwnerId(request);
63
+ await options.service.saveConversationSettings(ownerId, request.params.conversationId, settingsSchema.parse(request.body));
64
+ response.sendStatus(204);
65
+ });
66
+ router.delete("/conversations/:conversationId", async (request, response) => {
67
+ const ownerId = await resolveOwnerId(request);
68
+ await options.service.deleteConversation(ownerId, request.params.conversationId);
69
+ response.status(204).end();
70
+ });
59
71
  router.post("/conversations/:conversationId/messages", async (request, response) => {
60
72
  const ownerId = await resolveOwnerId(request);
61
73
  const body = submitSchema.parse(request.body);
@@ -0,0 +1,2 @@
1
+ import type { SceneEdit } from "@womp/kakapo-sdk";
2
+ export declare function clearSceneContents(scene: SceneEdit): number;
@@ -0,0 +1,20 @@
1
+ function nodeDepth(node, nodes) {
2
+ let depth = 0;
3
+ let parentId = node.parentId;
4
+ while (parentId !== null && parentId !== 0) {
5
+ depth += 1;
6
+ parentId = nodes.get(parentId)?.parentId ?? null;
7
+ }
8
+ return depth;
9
+ }
10
+ export function clearSceneContents(scene) {
11
+ const nodes = scene.listNodes().filter(({ id }) => id !== 0);
12
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
13
+ nodes.sort((a, b) => nodeDepth(b, nodesById) - nodeDepth(a, nodesById));
14
+ for (const node of nodes)
15
+ scene.deleteNode(node.id);
16
+ const materials = scene.listMaterials();
17
+ for (const material of materials)
18
+ scene.deleteMaterial(material.id);
19
+ return nodes.length + materials.length;
20
+ }
@@ -28,6 +28,8 @@ export declare class SparkAgentService {
28
28
  }): Promise<SparkConversation>;
29
29
  listConversations(ownerId: string): Promise<SparkConversationSummary[]>;
30
30
  getConversation(ownerId: string, id: string): Promise<SparkConversation>;
31
+ saveConversationSettings(ownerId: string, id: string, settings: SparkRunSettings): Promise<void>;
32
+ deleteConversation(ownerId: string, id: string): Promise<void>;
31
33
  getRun(ownerId: string, id: string): Promise<SparkRun>;
32
34
  getActiveRun(ownerId: string, conversationId: string): Promise<SparkRun | undefined>;
33
35
  /** The host must establish exclusive ownership before recovering abandoned runs. */
@@ -96,6 +96,16 @@ export class SparkAgentService {
96
96
  }
97
97
  return conversation;
98
98
  }
99
+ async saveConversationSettings(ownerId, id, settings) {
100
+ await this.getConversation(ownerId, id);
101
+ await this.store.saveConversationSettings(id, settings);
102
+ }
103
+ async deleteConversation(ownerId, id) {
104
+ await this.getConversation(ownerId, id);
105
+ if (await this.store.deleteConversation(id) === false) {
106
+ throw new SparkServiceError(409, "Stop the active run before deleting this conversation.");
107
+ }
108
+ }
99
109
  async getRun(ownerId, id) {
100
110
  const run = await this.store.getRun(id);
101
111
  if (run === undefined)
@@ -3,7 +3,7 @@ import type { SparkArtifact, StoredSparkChunk } from "../ui.js";
3
3
  import type { SparkArtifactStore, StoredSparkArtifact } from "./artifacts.js";
4
4
  import type { SceneOperation, SceneOperationStore } from "../scene-operations.js";
5
5
  import { type RepairStateStore } from "../repair-state.js";
6
- import type { SparkAgentStore, SparkConversation, SparkConversationSummary, SparkRun } from "./types.js";
6
+ import type { SparkAgentStore, SparkConversation, SparkConversationSummary, SparkRun, SparkRunSettings } from "./types.js";
7
7
  /** Local development storage. Execution ownership belongs to the host service. */
8
8
  export declare class SqliteSparkStore implements SparkAgentStore, SparkArtifactStore, SceneOperationStore {
9
9
  private readonly artifactBaseUrl;
@@ -21,6 +21,8 @@ export declare class SqliteSparkStore implements SparkAgentStore, SparkArtifactS
21
21
  getConversation(id: string): Promise<SparkConversation | undefined>;
22
22
  listConversations(ownerId: string): Promise<SparkConversationSummary[]>;
23
23
  saveConversation(value: SparkConversation): Promise<void>;
24
+ saveConversationSettings(id: string, settings: SparkRunSettings): Promise<void>;
25
+ deleteConversation(id: string): Promise<boolean>;
24
26
  createRun(value: SparkRun): Promise<void>;
25
27
  getRun(id: string): Promise<SparkRun | undefined>;
26
28
  saveRun(value: SparkRun): Promise<void>;
@@ -159,10 +159,34 @@ export class SqliteSparkStore {
159
159
  });
160
160
  }
161
161
  async saveConversation(value) {
162
- const result = this.database.prepare("UPDATE conversations SET owner_id = ?, updated_at = ?, data = ? WHERE id = ?").run(value.ownerId, value.updatedAt, JSON.stringify(value), value.id);
162
+ const result = this.database.prepare("UPDATE conversations SET owner_id = ?, updated_at = ?, data = json_patch(json_remove(?, '$.settings'), json_object('settings', json_extract(data, '$.settings'))) WHERE id = ?").run(value.ownerId, value.updatedAt, JSON.stringify(value), value.id);
163
163
  if (result.changes === 0)
164
164
  throw new Error(`Conversation ${value.id} does not exist.`);
165
165
  }
166
+ async saveConversationSettings(id, settings) {
167
+ const result = this.database.prepare("UPDATE conversations SET data = json_set(data, '$.settings', json(?)) WHERE id = ?").run(JSON.stringify(settings), id);
168
+ if (result.changes === 0)
169
+ throw new Error(`Conversation ${id} does not exist.`);
170
+ }
171
+ async deleteConversation(id) {
172
+ this.database.exec("BEGIN IMMEDIATE");
173
+ try {
174
+ const active = this.database.prepare("SELECT id FROM conversations WHERE id = ? AND json_extract(data, '$.activeRunId') IS NOT NULL").get(id);
175
+ if (active) {
176
+ this.database.exec("ROLLBACK");
177
+ return false;
178
+ }
179
+ this.database.prepare("DELETE FROM chunks WHERE run_id IN (SELECT id FROM runs WHERE conversation_id = ?)").run(id);
180
+ this.database.prepare("DELETE FROM runs WHERE conversation_id = ?").run(id);
181
+ this.database.prepare("DELETE FROM conversations WHERE id = ?").run(id);
182
+ this.database.exec("COMMIT");
183
+ return true;
184
+ }
185
+ catch (error) {
186
+ this.database.exec("ROLLBACK");
187
+ throw error;
188
+ }
189
+ }
166
190
  async createRun(value) {
167
191
  this.database.prepare("INSERT INTO runs VALUES (?, ?, ?)")
168
192
  .run(value.id, value.conversationId, JSON.stringify(value));
@@ -205,9 +229,8 @@ export class SqliteSparkStore {
205
229
  const id = crypto.randomUUID();
206
230
  const artifact = {
207
231
  id, url: `${this.artifactBaseUrl.replace(/\/$/u, "")}/${id}`,
208
- mediaType: image.mediaType, name: `spark-${image.view}.jpg`,
232
+ mediaType: image.mediaType, name: "spark-screenshot.jpg",
209
233
  width: image.width, height: image.height,
210
- metadata: { view: image.view, targetId: image.targetId },
211
234
  };
212
235
  this.database.prepare("INSERT INTO artifacts VALUES (?, ?, ?, ?)")
213
236
  .run(id, ownerId, JSON.stringify(artifact), image.data);
@@ -11,6 +11,8 @@ export type StandaloneSparkAgentOptions = {
11
11
  /** Stable host-verified identity for the scene behind kakapoUrl. */
12
12
  sceneIdentity?: string;
13
13
  logger?: Pick<Console, "info" | "warn">;
14
+ /** Observe successful renderer captures, including captures not attached to a reply. */
15
+ onScreenshot?: (runId: string) => void;
14
16
  };
15
17
  export type StandaloneSparkAgentApp = Express & {
16
18
  dispose(): Promise<void>;