@womp/spark-agent 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +11 -2
- package/dist/capabilities/scene.js +14 -7
- package/dist/error-diagnostics.d.ts +32 -0
- package/dist/error-diagnostics.js +80 -0
- package/dist/prompt.d.ts +1 -1
- package/dist/prompt.js +7 -4
- package/dist/server/service.js +35 -10
- package/dist/server/types.d.ts +2 -0
- package/dist/ui.d.ts +46 -0
- package/dist/ui.js +9 -1
- package/package.json +1 -1
- package/.env.example +0 -6
package/dist/agent.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { sparkErrorDiagnostics } from "./error-diagnostics.js";
|
|
1
2
|
import { createUIMessageStream, stepCountIs, streamText, } from "ai";
|
|
2
3
|
import { createSparkTools } from "./agent-tools.js";
|
|
3
4
|
import { SPARK_AGENT_PROMPT } from "./prompt.js";
|
|
4
5
|
import { calculateStepCost } from "./pricing.js";
|
|
5
6
|
function errorMessage(error) {
|
|
6
|
-
return error
|
|
7
|
+
return sparkErrorDiagnostics(error).message;
|
|
7
8
|
}
|
|
8
9
|
export function runSparkAgent(options) {
|
|
9
10
|
const maxSteps = options.maxSteps ?? 100;
|
|
@@ -187,7 +188,15 @@ export function runSparkAgent(options) {
|
|
|
187
188
|
const status = options.signal?.aborted ? "cancelled" : "failed";
|
|
188
189
|
writer.write({
|
|
189
190
|
type: "message-metadata",
|
|
190
|
-
messageMetadata: {
|
|
191
|
+
messageMetadata: {
|
|
192
|
+
...metadata(status),
|
|
193
|
+
error: errorMessage(failure),
|
|
194
|
+
...(status === "cancelled"
|
|
195
|
+
? {}
|
|
196
|
+
: {
|
|
197
|
+
errorDiagnostics: sparkErrorDiagnostics(failure),
|
|
198
|
+
}),
|
|
199
|
+
},
|
|
191
200
|
});
|
|
192
201
|
rejectResult(failure);
|
|
193
202
|
throw failure;
|
|
@@ -16,14 +16,15 @@ export function createSceneCapability(host) {
|
|
|
16
16
|
skills: [
|
|
17
17
|
{
|
|
18
18
|
name: "scene-editing",
|
|
19
|
-
version: "
|
|
19
|
+
version: "29",
|
|
20
20
|
description: "Inspect the open scene and edit its objects, materials, curves, and environment.",
|
|
21
21
|
instructions: `Read the scene first and keep its revision and object IDs. read() includes health.
|
|
22
22
|
Use womp.scene.health() to refresh whole-scene health or womp.scene.health(objectIds) to check specific objects (mapped to their owning Areas).
|
|
23
23
|
Health is a separate snapshot, not proof of consistency with read().revision or that the latest edit finished evaluating. Inspect post-edit health in receipts before claiming success; refresh only when needed.
|
|
24
24
|
Unavailable, stale, or unverified feedback must not trigger blind regeneration or a claim that the model passed. Hosts without health services return unverified health with a reason.
|
|
25
25
|
For confirmed primitive or depth overflow, repair the same target while preserving design, dimensions, active parameter overrides, and unrelated objects. At most two geometry repairs are allowed per target in one logical run.
|
|
26
|
-
Empty Areas may be pending, intentionally empty, mesh-only, or failed. Overflow flags take precedence over possibly stale counts.
|
|
26
|
+
Empty Areas may be pending, intentionally empty, mesh-only, or failed. Overflow flags take precedence over possibly stale counts.
|
|
27
|
+
max_total_depth reports the engine's tier-dependent nesting allowance, not measured depth. Compiled Boolean depth includes generated grouping; organizational Groups do not add a level. Use flag_depth_overflow to detect evaluator rejection.
|
|
27
28
|
Call scene.edit(callback, expectedRevision)
|
|
28
29
|
for one coherent undoable change. The callback receives a draft; all node/material/curve functions and enums
|
|
29
30
|
are properties of that draft. The draft expires when the callback ends. Await asynchronous methods.
|
|
@@ -32,6 +33,7 @@ Preserve the existing scene unless asked to remove or replace content. Use seman
|
|
|
32
33
|
known IDs when editing rather than creating duplicates. Distances follow the current project's units
|
|
33
34
|
(read().unitMultiplier is 1 for millimetres, 0.1 for centimetres, approximately 0.0393701 for inches).
|
|
34
35
|
Convert a dimension in millimetres to project units by multiplying it by read().unitMultiplier.
|
|
36
|
+
The SDK-backed draft clamps dimension writes to 0.01–1000 mm per component before converting to native units; this is an adapter limit, not a scene-wide size limit. Read back dimensions when physical size matters.
|
|
35
37
|
Area size is a dimensionless scale; Group size stays fixed at one. Lights must be created at root (parentId 0); their size has x/y dimensions.
|
|
36
38
|
Light power must be a finite number in [0, 100].
|
|
37
39
|
nodeCreate(Curve, parentId) creates an empty curve with zero blend. curvePointAdd(curveId, point, index?) returns the new index; omit index to append or supply an integer from 0 through point count to insert before that position.
|
|
@@ -62,9 +64,12 @@ Groups are organizational only; move and rotate their child Areas individually,
|
|
|
62
64
|
Sphere uses size.x as uniform diameter (y/z do not stretch it), including new or resized spherical curve points. Cylinder/Cone/Pyramid use x as base diameter/width and y as height;
|
|
63
65
|
Torus uses x as outer diameter and y as tube thickness. For these four primitives, nodeSetSize accepts {x, y}; z follows x even when supplied. Cube uses all three dimensions. Node and curve-point round are dimensionless fractions clamped to [0, 1], not radii in project units; rounding does not set size. Cube rounding reaches half its shortest dimension at 1, so a shallow Cube retains broad flat faces rather than becoming an ellipsoid.
|
|
64
66
|
Build and verify coarse geometry and unfamiliar operations before replicating details. Check one representative result, then repeat it.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
Reference fidelity:
|
|
68
|
+
When recreating an image, treat its visible geometry and level of detail as the target—not merely its subject, colors, or list of parts. Preserve the user's chosen workflow and editability requirements.
|
|
69
|
+
Establish a comparison view matching the reference's angle, projection, and subject framing as closely as practical. Match silhouette, proportions, pose, and negative spaces first; then local contours, thickness, curvature, surface transitions, and defining details. Infer unseen geometry conservatively.
|
|
70
|
+
Compare each rendered pass directly with the source. Identify the largest remaining visible shape mismatch, edit it, and verify that it improved before moving to smaller details. Use close-ups when the comparison view cannot resolve a feature; return to the comparison view afterward. Adjust sampling or resolution when visible faceting or segmentation requires it—not as a substitute for reshaping.
|
|
71
|
+
Do not stop at semantic resemblance or after a fixed number of screenshots. Continue while substantial visible mismatches remain and supported edits are making progress, within the available budget. If progress stalls or the chosen representation cannot achieve the requested fidelity, explain the specific limitation and ask before changing workflows. Before finishing, compare the latest render with the reference and address the largest remaining visible mismatch; if substantial differences remain, identify the concrete stopping reason rather than treating an approximation disclaimer as completion.
|
|
72
|
+
|
|
68
73
|
nodeClone is shallow (no children). await cloneSubtree(id, { parentId?, name?, index? }) copies the complete hierarchy with fresh IDs,
|
|
69
74
|
preserving local transforms, order and metadata, remapping internal references and sharing external materials/assets.
|
|
70
75
|
Cloning requires a host transport with atomic scene patches; an unsupported host rejects it without changing the draft.
|
|
@@ -79,8 +84,10 @@ screenshot({ camera: { position: {x,y,z}, target: {x,y,z}, projection: 'perspect
|
|
|
79
84
|
sets a world-space camera in the same Womp coordinates and project units as scene positions (Y-up).
|
|
80
85
|
For orthographic framing use projection: 'orthographic' and orthographicWidth in project units instead of FOV.
|
|
81
86
|
Direction is derived from position/target; do not use coincident points or an exactly vertical viewing direction.
|
|
82
|
-
Omitted projection/lens settings
|
|
83
|
-
|
|
87
|
+
Omitted projection/lens settings come from the active camera. Pass the camera again for repeatable captures.
|
|
88
|
+
For object-focused screenshots, identify the requested object and frame it with an explicit camera position and target using its bounds; reuse established framing when appropriate. Choose a view that reveals the requested features without clipping the subject.
|
|
89
|
+
Omitting camera captures the active viewport without auto-fit. Use it when the user asks about the current viewport or clearly refers to what is visible on screen, not on the assumption that it shows the requested object.
|
|
90
|
+
Respect the host's camera-lock restrictions. Verify silhouette,
|
|
84
91
|
connections, openings, proportions, and materials before claiming completion. Use the owning top-level Area for scene-space bounds;
|
|
85
92
|
OpenSCAD part bounds are local to the part; individual primitive and curve bounds are unsupported. Renderer bounds and resize calls
|
|
86
93
|
must finish before subsequent writes. Inside scene.edit, complete renderer-backed bounds reads before the first
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Allowlisted diagnostics only; never serialize an SDK error or request wholesale. */
|
|
3
|
+
export type SparkErrorDiagnostics = {
|
|
4
|
+
message: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
code?: string;
|
|
7
|
+
status?: number;
|
|
8
|
+
requestId?: string;
|
|
9
|
+
cause?: SparkErrorDiagnostics;
|
|
10
|
+
};
|
|
11
|
+
export declare const sparkErrorDiagnosticsSchema: z.ZodObject<{
|
|
12
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
13
|
+
message: z.ZodString;
|
|
14
|
+
name: z.ZodOptional<z.ZodString>;
|
|
15
|
+
code: z.ZodOptional<z.ZodString>;
|
|
16
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
18
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
19
|
+
message: z.ZodString;
|
|
20
|
+
name: z.ZodOptional<z.ZodString>;
|
|
21
|
+
code: z.ZodOptional<z.ZodString>;
|
|
22
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
26
|
+
message: z.ZodString;
|
|
27
|
+
name: z.ZodOptional<z.ZodString>;
|
|
28
|
+
code: z.ZodOptional<z.ZodString>;
|
|
29
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export declare function sparkErrorDiagnostics(error: unknown): SparkErrorDiagnostics;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const fields = {
|
|
3
|
+
message: z.string().max(1024),
|
|
4
|
+
name: z.string().max(128).optional(),
|
|
5
|
+
code: z.string().max(128).optional(),
|
|
6
|
+
status: z.number().int().min(100).max(599).optional(),
|
|
7
|
+
requestId: z.string().max(128).optional(),
|
|
8
|
+
};
|
|
9
|
+
const causeSchema = z.object(fields);
|
|
10
|
+
export const sparkErrorDiagnosticsSchema = z.object({
|
|
11
|
+
...fields,
|
|
12
|
+
cause: causeSchema.extend({ cause: causeSchema.optional() }).optional(),
|
|
13
|
+
});
|
|
14
|
+
function record(value) {
|
|
15
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
16
|
+
}
|
|
17
|
+
function responseData(value) {
|
|
18
|
+
if (typeof value !== "string" || value.length > 16384)
|
|
19
|
+
return {};
|
|
20
|
+
try {
|
|
21
|
+
return record(JSON.parse(value));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function text(value, limit = 1024) {
|
|
28
|
+
if (typeof value !== "string" && typeof value !== "number")
|
|
29
|
+
return undefined;
|
|
30
|
+
// Bound work as well as storage. Embedded JSON/request dumps are not diagnostics.
|
|
31
|
+
return String(value)
|
|
32
|
+
.slice(0, 8192)
|
|
33
|
+
.replace(/\{[\s\S]*|\[(?=\s*["{0-9])[\s\S]*/u, "[payload omitted]")
|
|
34
|
+
.replace(/\b(?:request(?:\s*body)?|response\s*body|payload|headers|image|prompt)\s*[:=][\s\S]*/giu, "[payload omitted]")
|
|
35
|
+
.replace(/data:[^\s]+/giu, "[data omitted]")
|
|
36
|
+
.replace(/https?:\/\/[^\s]+/giu, "[url omitted]")
|
|
37
|
+
.replace(/\b(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|password|(?:client[_ -]?)?secret)\b\s*[:=]\s*[^\r\n,;]+/giu, "[credential omitted]")
|
|
38
|
+
.replace(/\b(?:Bearer|Basic)\s+[^\s,;]+/giu, "[credential omitted]")
|
|
39
|
+
.replace(/\b(?:sk-|sk_|AIza)[A-Za-z0-9_-]+/gu, "[credential omitted]")
|
|
40
|
+
.replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/gu, "[credential omitted]")
|
|
41
|
+
.replace(/[A-Za-z0-9+/=_-]{200,}/gu, "[data omitted]")
|
|
42
|
+
.slice(0, limit);
|
|
43
|
+
}
|
|
44
|
+
export function sparkErrorDiagnostics(error) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const visit = (value, depth) => {
|
|
47
|
+
seen.add(value);
|
|
48
|
+
const source = record(value);
|
|
49
|
+
const data = record(source.data);
|
|
50
|
+
const body = responseData(source.responseBody);
|
|
51
|
+
const provider = record(data.error ?? source.error ?? body.error ?? body);
|
|
52
|
+
const headers = record(source.responseHeaders);
|
|
53
|
+
const status = source.statusCode ?? source.status ?? provider.status;
|
|
54
|
+
const result = {
|
|
55
|
+
message: text(source.message ?? provider.message ?? value) || "An error occurred.",
|
|
56
|
+
};
|
|
57
|
+
const name = text(source.name, 128);
|
|
58
|
+
const code = text(source.code ?? provider.code ?? data.code, 128);
|
|
59
|
+
const requestId = text(source.requestId ??
|
|
60
|
+
source.request_id ??
|
|
61
|
+
provider.request_id ??
|
|
62
|
+
body.request_id ??
|
|
63
|
+
headers["x-request-id"] ??
|
|
64
|
+
headers["request-id"] ??
|
|
65
|
+
headers["x-amzn-requestid"], 128);
|
|
66
|
+
if (name)
|
|
67
|
+
result.name = name;
|
|
68
|
+
if (code)
|
|
69
|
+
result.code = code;
|
|
70
|
+
if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599)
|
|
71
|
+
result.status = status;
|
|
72
|
+
if (requestId)
|
|
73
|
+
result.requestId = requestId;
|
|
74
|
+
const cause = source.cause ?? source.lastError;
|
|
75
|
+
if (depth < 2 && cause !== undefined && !seen.has(cause))
|
|
76
|
+
result.cause = visit(cause, depth + 1);
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
return visit(error, 0);
|
|
80
|
+
}
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SPARK_AGENT_PROMPT = "You are Spark, Womp's unified assistant.\nUse read_skill to discover the APIs needed for the user's request. 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.\nIf an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.\nNever 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. Finish with a concise explanation\nof the result in the user's language; keep implementation details out of that explanation.\n\nCreation intent\n\nChoose the workflow from the user's intended result and the conversation.\nDescribe choices by their outcome, not internal tools.\n\n- Use editable CAD for printable, measured, mechanical, or real-world-fit\n objects. Ask about missing dimensions only when a sensible default would\n likely make the result wrong. CAD is the preferred printing workflow;\n do not claim print readiness without supporting validation.\n\n- Build directly in the scene for ordinary 3D requests, including \u201C3D X\u201D\n and \u201Cmake X in 3D,\u201D and when the user wants individually editable shapes\n or geometry that blends with existing objects. Printable, measured,\n or mechanical intent takes precedence and routes to CAD.\n\n- Use mesh generation when the user explicitly requests a mesh, asks to\n turn an existing image into 3D, or chooses a sculpted result rather than\n individually editable shapes.\n\n- Use image generation for pictures, logos, concept art, wallpapers,\n and other clearly visual output.\n\n- For an ambiguous creative request without explicit 3D or printing intent,\n generate one image preview first. Then offer one short, outcome-based\n choice: refine the image, turn it into a sculpted 3D piece, or build it\n from individually editable shapes. Offer adjustable CAD instead of\n editable shapes when the subject is measured or must fit something real.\n\n-
|
|
1
|
+
export declare const SPARK_AGENT_PROMPT = "You are Spark, Womp's unified assistant.\nUse read_skill to discover the APIs needed for the user's request. 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.\nIf an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.\nNever 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. Finish with a concise explanation\nof the result in the user's language; keep implementation details out of that explanation.\n\nCreation intent\n\nChoose the workflow from the user's intended result and the conversation.\nDescribe choices by their outcome, not internal tools.\n\n- Use editable CAD for printable, measured, mechanical, or real-world-fit\n objects. Ask about missing dimensions only when a sensible default would\n likely make the result wrong. CAD is the preferred printing workflow;\n do not claim print readiness without supporting validation.\n\n- Build directly in the scene for ordinary 3D requests, including \u201C3D X\u201D\n and \u201Cmake X in 3D,\u201D and when the user wants individually editable shapes\n or geometry that blends with existing objects. Printable, measured,\n or mechanical intent takes precedence and routes to CAD.\n\n- Use mesh generation when the user explicitly requests a mesh, asks to\n turn an existing image into 3D, or chooses a sculpted result rather than\n individually editable shapes.\n\n- Use image generation for pictures, logos, concept art, wallpapers,\n and other clearly visual output.\n\n- For an ambiguous creative request without explicit 3D or printing intent,\n generate one image preview first. Then offer one short, outcome-based\n choice: refine the image, turn it into a sculpted 3D piece, or build it\n from individually editable shapes. Offer adjustable CAD instead of\n editable shapes when the subject is measured or must fit something real.\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. A sculpted appearance does not by itself 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- When the user chooses editable shapes or CAD after a preview, preserve\n its palette, proportions, and defining features. When they choose a\n sculpted result, use that preview as the image-to-mesh input rather\n than starting again from a text prompt.\n\n- If the user wants repeated generation variations and Flow is available,\n offer a reusable Flow workflow.\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.";
|
package/dist/prompt.js
CHANGED
|
@@ -50,10 +50,13 @@ Describe choices by their outcome, not internal tools.
|
|
|
50
50
|
from individually editable shapes. Offer adjustable CAD instead of
|
|
51
51
|
editable shapes when the subject is measured or must fit something real.
|
|
52
52
|
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Respect an explicit choice
|
|
53
|
+
- Choose construction methods by the user's requested workflow and the
|
|
54
|
+
available capabilities, not by assuming editable shapes require lower
|
|
55
|
+
fidelity. A sculpted appearance does not by itself require mesh generation.
|
|
56
|
+
Respect an explicit method choice and pursue the reference's visible shape
|
|
57
|
+
and detail with that method. Offer alternatives only for a specific
|
|
58
|
+
demonstrated limitation; ask before switching or simplifying the requested
|
|
59
|
+
result. No method guarantees exact reconstruction from a single image.
|
|
57
60
|
|
|
58
61
|
- When the user chooses editable shapes or CAD after a preview, preserve
|
|
59
62
|
its palette, proportions, and defining features. When they choose a
|
package/dist/server/service.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sparkErrorDiagnostics } from "../error-diagnostics.js";
|
|
1
2
|
import { readUIMessageStream, validateUIMessages } from "ai";
|
|
2
3
|
import { sparkDataPartSchemas, sparkMessageMetadataSchema, } from "../ui.js";
|
|
3
4
|
const TERMINAL_STATUSES = new Set([
|
|
@@ -102,7 +103,7 @@ export class SparkAgentService {
|
|
|
102
103
|
}
|
|
103
104
|
async deleteConversation(ownerId, id) {
|
|
104
105
|
await this.getConversation(ownerId, id);
|
|
105
|
-
if (await this.store.deleteConversation(id) === false) {
|
|
106
|
+
if ((await this.store.deleteConversation(id)) === false) {
|
|
106
107
|
throw new SparkServiceError(409, "Stop the active run before deleting this conversation.");
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -209,7 +210,9 @@ export class SparkAgentService {
|
|
|
209
210
|
this.controllers.set(run.id, controller);
|
|
210
211
|
const execution = this.runInBackground(run, conversation, controller);
|
|
211
212
|
this.executions.set(run.id, execution);
|
|
212
|
-
void execution
|
|
213
|
+
void execution
|
|
214
|
+
.finally(() => this.executions.delete(run.id))
|
|
215
|
+
.catch((error) => {
|
|
213
216
|
console.error("Spark run persistence failed", error);
|
|
214
217
|
});
|
|
215
218
|
return run;
|
|
@@ -314,7 +317,7 @@ export class SparkAgentService {
|
|
|
314
317
|
continue;
|
|
315
318
|
await this.appendChunk(run, chunk);
|
|
316
319
|
}
|
|
317
|
-
})();
|
|
320
|
+
})().then(() => undefined, (error) => error);
|
|
318
321
|
let executionError;
|
|
319
322
|
let result;
|
|
320
323
|
try {
|
|
@@ -323,9 +326,11 @@ export class SparkAgentService {
|
|
|
323
326
|
catch (error) {
|
|
324
327
|
executionError = error;
|
|
325
328
|
}
|
|
326
|
-
await consume;
|
|
329
|
+
const streamError = await consume;
|
|
327
330
|
if (executionError !== undefined)
|
|
328
331
|
throw executionError;
|
|
332
|
+
if (streamError !== undefined)
|
|
333
|
+
throw streamError;
|
|
329
334
|
if (controller.signal.aborted) {
|
|
330
335
|
await this.appendStatus(run, "cancelled");
|
|
331
336
|
await this.finishRun(run, conversation, "cancelled");
|
|
@@ -341,25 +346,26 @@ export class SparkAgentService {
|
|
|
341
346
|
await this.finishRun(run, conversation, "cancelled");
|
|
342
347
|
return;
|
|
343
348
|
}
|
|
344
|
-
const
|
|
349
|
+
const diagnostics = sparkErrorDiagnostics(error);
|
|
350
|
+
const message = diagnostics.message;
|
|
345
351
|
const failedChunk = {
|
|
346
352
|
type: "data-run-status",
|
|
347
353
|
id: `run-status-${run.id}`,
|
|
348
|
-
data: { runId: run.id, status: "failed", error: message },
|
|
354
|
+
data: { runId: run.id, status: "failed", error: message, errorDiagnostics: diagnostics },
|
|
349
355
|
};
|
|
350
356
|
await this.appendChunk(run, { type: "error", errorText: message });
|
|
351
357
|
await this.appendChunk(run, failedChunk);
|
|
352
|
-
await this.finishRun(run, conversation, "failed", message);
|
|
358
|
+
await this.finishRun(run, conversation, "failed", message, diagnostics);
|
|
353
359
|
}
|
|
354
360
|
finally {
|
|
355
361
|
this.controllers.delete(run.id);
|
|
356
362
|
}
|
|
357
363
|
}
|
|
358
|
-
async finishRun(run, conversation, status, error) {
|
|
364
|
+
async finishRun(run, conversation, status, error, errorDiagnostics) {
|
|
359
365
|
const completedAt = now();
|
|
360
366
|
const storedChunks = await this.store.listRunChunks(run.id, 0);
|
|
361
367
|
const chunks = storedChunks.map(({ chunk }) => chunk);
|
|
362
|
-
const assistant = await this.materializeMessage(run, chunks, status, completedAt, error);
|
|
368
|
+
const assistant = await this.materializeMessage(run, chunks, status, completedAt, error, errorDiagnostics);
|
|
363
369
|
if (assistant !== undefined) {
|
|
364
370
|
await this.appendChunk(run, {
|
|
365
371
|
type: "message-metadata",
|
|
@@ -379,12 +385,13 @@ export class SparkAgentService {
|
|
|
379
385
|
...run,
|
|
380
386
|
status,
|
|
381
387
|
...(error === undefined ? {} : { error }),
|
|
388
|
+
...(errorDiagnostics === undefined ? {} : { errorDiagnostics }),
|
|
382
389
|
updatedAt: completedAt,
|
|
383
390
|
completedAt,
|
|
384
391
|
});
|
|
385
392
|
this.wakeRun(run.id);
|
|
386
393
|
}
|
|
387
|
-
async materializeMessage(run, chunks, status, completedAt, error) {
|
|
394
|
+
async materializeMessage(run, chunks, status, completedAt, error, errorDiagnostics) {
|
|
388
395
|
let message;
|
|
389
396
|
try {
|
|
390
397
|
for await (const current of readUIMessageStream({
|
|
@@ -412,6 +419,7 @@ export class SparkAgentService {
|
|
|
412
419
|
modelId: run.settings.modelId,
|
|
413
420
|
...(message.metadata?.usage === undefined ? {} : { usage: message.metadata.usage }),
|
|
414
421
|
...(error === undefined ? {} : { error }),
|
|
422
|
+
...(errorDiagnostics === undefined ? {} : { errorDiagnostics }),
|
|
415
423
|
},
|
|
416
424
|
};
|
|
417
425
|
}
|
|
@@ -423,6 +431,23 @@ export class SparkAgentService {
|
|
|
423
431
|
});
|
|
424
432
|
}
|
|
425
433
|
async appendChunk(run, chunk) {
|
|
434
|
+
if (chunk.type === "error")
|
|
435
|
+
chunk = { ...chunk, errorText: sparkErrorDiagnostics(chunk.errorText).message };
|
|
436
|
+
if (chunk.type === "message-metadata" || chunk.type === "data-run-status") {
|
|
437
|
+
const data = chunk.type === "message-metadata" ? chunk.messageMetadata : chunk.data;
|
|
438
|
+
const safe = {
|
|
439
|
+
...(data.error === undefined ? {} : { error: sparkErrorDiagnostics(data.error).message }),
|
|
440
|
+
...(data.errorDiagnostics === undefined
|
|
441
|
+
? {}
|
|
442
|
+
: {
|
|
443
|
+
errorDiagnostics: sparkErrorDiagnostics(data.errorDiagnostics),
|
|
444
|
+
}),
|
|
445
|
+
};
|
|
446
|
+
chunk =
|
|
447
|
+
chunk.type === "message-metadata"
|
|
448
|
+
? { ...chunk, messageMetadata: { ...chunk.messageMetadata, ...safe } }
|
|
449
|
+
: { ...chunk, data: { ...chunk.data, ...safe } };
|
|
450
|
+
}
|
|
426
451
|
const stored = await this.store.appendRunChunk(run.id, run.conversationId, chunk);
|
|
427
452
|
for (const listener of this.listeners.get(run.id) ?? [])
|
|
428
453
|
listener(stored);
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SparkErrorDiagnostics } from "../error-diagnostics.js";
|
|
1
2
|
import type { SparkAgentRun } from "../agent.js";
|
|
2
3
|
import type { ReasoningEffort } from "../models.js";
|
|
3
4
|
import type { SparkRunStatus, SparkUIMessage, StoredSparkChunk } from "../ui.js";
|
|
@@ -26,6 +27,7 @@ export type SparkRun = {
|
|
|
26
27
|
status: SparkRunStatus;
|
|
27
28
|
settings: SparkRunSettings;
|
|
28
29
|
error?: string;
|
|
30
|
+
errorDiagnostics?: SparkErrorDiagnostics;
|
|
29
31
|
createdAt: string;
|
|
30
32
|
updatedAt: string;
|
|
31
33
|
startedAt?: string;
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type SparkErrorDiagnostics } from "./error-diagnostics.js";
|
|
2
|
+
export { sparkErrorDiagnostics, sparkErrorDiagnosticsSchema, type SparkErrorDiagnostics, } from "./error-diagnostics.js";
|
|
1
3
|
import type { InferUIMessageChunk, UIMessage } from "ai";
|
|
2
4
|
import type { SceneScreenshotOptions } from "./capabilities/scene.js";
|
|
3
5
|
import { z } from "zod";
|
|
@@ -24,6 +26,7 @@ export type SparkMessageMetadata = {
|
|
|
24
26
|
modelId?: string;
|
|
25
27
|
usage?: unknown;
|
|
26
28
|
error?: string;
|
|
29
|
+
errorDiagnostics?: SparkErrorDiagnostics;
|
|
27
30
|
};
|
|
28
31
|
export type SparkConsoleData = {
|
|
29
32
|
stream: "stdout" | "stderr";
|
|
@@ -33,6 +36,7 @@ export type SparkRunStatusData = {
|
|
|
33
36
|
runId: string;
|
|
34
37
|
status: SparkRunStatus;
|
|
35
38
|
error?: string;
|
|
39
|
+
errorDiagnostics?: SparkErrorDiagnostics;
|
|
36
40
|
label?: string;
|
|
37
41
|
};
|
|
38
42
|
export declare const sparkStepCostSchema: z.ZodObject<{
|
|
@@ -157,6 +161,27 @@ export declare const sparkMessageMetadataSchema: z.ZodObject<{
|
|
|
157
161
|
modelId: z.ZodOptional<z.ZodString>;
|
|
158
162
|
usage: z.ZodOptional<z.ZodUnknown>;
|
|
159
163
|
error: z.ZodOptional<z.ZodString>;
|
|
164
|
+
errorDiagnostics: z.ZodOptional<z.ZodObject<{
|
|
165
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
166
|
+
message: z.ZodString;
|
|
167
|
+
name: z.ZodOptional<z.ZodString>;
|
|
168
|
+
code: z.ZodOptional<z.ZodString>;
|
|
169
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
170
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
171
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
172
|
+
message: z.ZodString;
|
|
173
|
+
name: z.ZodOptional<z.ZodString>;
|
|
174
|
+
code: z.ZodOptional<z.ZodString>;
|
|
175
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
176
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
177
|
+
}, z.core.$strip>>;
|
|
178
|
+
}, z.core.$strip>>;
|
|
179
|
+
message: z.ZodString;
|
|
180
|
+
name: z.ZodOptional<z.ZodString>;
|
|
181
|
+
code: z.ZodOptional<z.ZodString>;
|
|
182
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
183
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
184
|
+
}, z.core.$strip>>;
|
|
160
185
|
}, z.core.$strip>;
|
|
161
186
|
export declare const sparkDataPartSchemas: {
|
|
162
187
|
readonly "tool-input-label": z.ZodObject<{
|
|
@@ -211,6 +236,27 @@ export declare const sparkDataPartSchemas: {
|
|
|
211
236
|
incomplete: "incomplete";
|
|
212
237
|
}>;
|
|
213
238
|
error: z.ZodOptional<z.ZodString>;
|
|
239
|
+
errorDiagnostics: z.ZodOptional<z.ZodObject<{
|
|
240
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
241
|
+
message: z.ZodString;
|
|
242
|
+
name: z.ZodOptional<z.ZodString>;
|
|
243
|
+
code: z.ZodOptional<z.ZodString>;
|
|
244
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
245
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
246
|
+
cause: z.ZodOptional<z.ZodObject<{
|
|
247
|
+
message: z.ZodString;
|
|
248
|
+
name: z.ZodOptional<z.ZodString>;
|
|
249
|
+
code: z.ZodOptional<z.ZodString>;
|
|
250
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
251
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
252
|
+
}, z.core.$strip>>;
|
|
253
|
+
}, z.core.$strip>>;
|
|
254
|
+
message: z.ZodString;
|
|
255
|
+
name: z.ZodOptional<z.ZodString>;
|
|
256
|
+
code: z.ZodOptional<z.ZodString>;
|
|
257
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
258
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
259
|
+
}, z.core.$strip>>;
|
|
214
260
|
label: z.ZodOptional<z.ZodString>;
|
|
215
261
|
}, z.core.$strip>;
|
|
216
262
|
readonly "step-metrics": z.ZodObject<{
|
package/dist/ui.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sparkErrorDiagnosticsSchema } from "./error-diagnostics.js";
|
|
2
|
+
export { sparkErrorDiagnostics, sparkErrorDiagnosticsSchema, } from "./error-diagnostics.js";
|
|
1
3
|
import { z } from "zod";
|
|
2
4
|
export { DEFAULT_MODEL_ID, getSparkModel, REASONING_EFFORT_LABELS, resolveReasoningEffort, SPARK_MODELS, } from "./models.js";
|
|
3
5
|
export const SPARK_RUN_STATUSES = [
|
|
@@ -48,9 +50,14 @@ export const sparkMessageMetadataSchema = z.object({
|
|
|
48
50
|
modelId: z.string().optional(),
|
|
49
51
|
usage: z.unknown().optional(),
|
|
50
52
|
error: z.string().optional(),
|
|
53
|
+
errorDiagnostics: sparkErrorDiagnosticsSchema.optional(),
|
|
51
54
|
});
|
|
52
55
|
export const sparkDataPartSchemas = {
|
|
53
|
-
"tool-input-label": z.object({
|
|
56
|
+
"tool-input-label": z.object({
|
|
57
|
+
toolCallId: z.string(),
|
|
58
|
+
field: z.enum(["name", "purpose"]),
|
|
59
|
+
value: z.string(),
|
|
60
|
+
}),
|
|
54
61
|
"execution-receipt": z.object({ toolCallId: z.string(), receipt: sparkExecutionReceiptSchema }),
|
|
55
62
|
console: z.object({
|
|
56
63
|
stream: z.enum(["stdout", "stderr"]),
|
|
@@ -61,6 +68,7 @@ export const sparkDataPartSchemas = {
|
|
|
61
68
|
runId: z.string().min(1),
|
|
62
69
|
status: z.enum(SPARK_RUN_STATUSES),
|
|
63
70
|
error: z.string().optional(),
|
|
71
|
+
errorDiagnostics: sparkErrorDiagnosticsSchema.optional(),
|
|
64
72
|
label: z.string().optional(),
|
|
65
73
|
}),
|
|
66
74
|
"step-metrics": z.object({
|
package/package.json
CHANGED