@womp/spark-agent 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/README.md +12 -2
- package/dist/agent.d.ts +12 -2
- package/dist/agent.js +32 -1
- package/dist/client.d.ts +4 -2
- package/dist/client.js +13 -1
- package/dist/compaction.d.ts +64 -0
- package/dist/compaction.js +296 -0
- package/dist/context-budget.d.ts +2 -0
- package/dist/context-budget.js +14 -6
- package/dist/goals.d.ts +46 -3
- package/dist/goals.js +23 -18
- package/dist/index.d.ts +1 -0
- package/dist/model-history.d.ts +13 -3
- package/dist/model-history.js +24 -3
- package/dist/server/executor.d.ts +2 -0
- package/dist/server/executor.js +2 -10
- package/dist/server/express.js +9 -0
- package/dist/server/goal-evaluator.d.ts +13 -1
- package/dist/server/goal-evaluator.js +36 -4
- package/dist/server/goal-proposer.d.ts +15 -0
- package/dist/server/goal-proposer.js +64 -0
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.js +4 -3
- package/dist/server/service.d.ts +10 -1
- package/dist/server/service.js +63 -11
- package/dist/server/standalone.d.ts +8 -0
- package/dist/server/standalone.js +66 -35
- package/dist/ui.d.ts +43 -2
- package/dist/ui.js +16 -1
- package/dist/user-images.d.ts +10 -0
- package/dist/user-images.js +20 -0
- package/package.json +1 -1
package/dist/server/service.js
CHANGED
|
@@ -2,7 +2,7 @@ import { sparkErrorDiagnostics } from "../error-diagnostics.js";
|
|
|
2
2
|
import { validateUIMessages } from "ai";
|
|
3
3
|
import { foldAllChunks } from "./message-fold.js";
|
|
4
4
|
import { sparkDataPartSchemas, sparkMessageMetadataSchema, } from "../ui.js";
|
|
5
|
-
import { sparkGoalCommandSchema } from "../goals.js";
|
|
5
|
+
import { sparkGoalCommandSchema, sparkGoalProposalSchema } from "../goals.js";
|
|
6
6
|
import { goalVerdictSchema } from "./goal-evaluator.js";
|
|
7
7
|
import { wompifyContextSchema, WompifyCreditCapReachedError } from "../wompify.js";
|
|
8
8
|
const TERMINAL_STATUSES = new Set([
|
|
@@ -52,13 +52,14 @@ function titleFor(message) {
|
|
|
52
52
|
return "New build";
|
|
53
53
|
return text.length > 44 ? `${text.slice(0, 44)}…` : text;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
/** `confirmedGoal` admits the goal part, which only a `set` command attaches. */
|
|
56
|
+
function normalizeUserMessage(message, confirmedGoal = false) {
|
|
56
57
|
if (message.role !== "user") {
|
|
57
58
|
throw new SparkServiceError(400, "A new chat message must have the user role.");
|
|
58
59
|
}
|
|
59
60
|
for (const part of message.parts) {
|
|
60
61
|
// The data schemas are checked with the rest of the message (validateUIMessages).
|
|
61
|
-
if (part.type === "text" || part.type === "data-selection")
|
|
62
|
+
if (part.type === "text" || part.type === "data-selection" || (confirmedGoal && part.type === "data-goal"))
|
|
62
63
|
continue;
|
|
63
64
|
if (part.type === "file" &&
|
|
64
65
|
part.mediaType.startsWith("image/") &&
|
|
@@ -84,10 +85,21 @@ const GOAL_WORK_RULES = "Keep working toward this objective and verify it with c
|
|
|
84
85
|
* turn reuses the provider's cached prefix.
|
|
85
86
|
*/
|
|
86
87
|
function goalContinuationText(goal) {
|
|
88
|
+
const open = goal.conditions?.filter((condition) => condition.measurable && condition.met !== true) ?? [];
|
|
87
89
|
return `Continue working toward the goal: ${goal.objective}\n${GOAL_WORK_RULES}\nProgress check: ${goal.lastReason}`
|
|
90
|
+
+ (open.length ? `\nConditions not yet verified:${open.map(({ text, reason }) => `\n- ${text}${reason ? ` (${reason})` : ""}`).join("")}` : "")
|
|
88
91
|
+ (wompifyContextSchema.safeParse(goal.settings.wompify).success
|
|
89
92
|
? "\nKeep refining the existing reconstruction at the same detail level. Compare it with the reference and improve the remaining shape and color discrepancies." : "");
|
|
90
93
|
}
|
|
94
|
+
/** The goal's conditions with what this evaluation found for each measurable one. */
|
|
95
|
+
function judgedConditions(goal, evaluation) {
|
|
96
|
+
if (!goal.conditions || !evaluation?.conditions)
|
|
97
|
+
return goal.conditions;
|
|
98
|
+
return goal.conditions.map((condition, index) => {
|
|
99
|
+
const result = evaluation.conditions.find((entry) => entry.index === index + 1);
|
|
100
|
+
return result && condition.measurable ? { ...condition, met: result.met, reason: result.reason } : condition;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
91
103
|
function goalStartText(goal, action) {
|
|
92
104
|
return `${action === "set" ? "Goal" : "Resume the goal"}: ${goal.objective}\n${GOAL_WORK_RULES}`;
|
|
93
105
|
}
|
|
@@ -95,6 +107,7 @@ export class SparkAgentService {
|
|
|
95
107
|
store;
|
|
96
108
|
executeRun;
|
|
97
109
|
evaluateGoal;
|
|
110
|
+
proposer;
|
|
98
111
|
readGoalCredits;
|
|
99
112
|
continuationSettings;
|
|
100
113
|
closing = false;
|
|
@@ -113,6 +126,7 @@ export class SparkAgentService {
|
|
|
113
126
|
this.store = options.store;
|
|
114
127
|
this.executeRun = options.executeRun;
|
|
115
128
|
this.evaluateGoal = options.evaluateGoal;
|
|
129
|
+
this.proposer = options.proposeGoal;
|
|
116
130
|
this.readGoalCredits = options.readGoalCredits;
|
|
117
131
|
this.continuationSettings = options.continuationSettings;
|
|
118
132
|
this.endOrphanedRuns = options.endOrphanedRuns ?? false;
|
|
@@ -153,6 +167,21 @@ export class SparkAgentService {
|
|
|
153
167
|
}
|
|
154
168
|
});
|
|
155
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* The conditions a goal started from `message` would verify. Nothing is stored
|
|
172
|
+
* or started: the user confirms them, then sends the message with a `set` command.
|
|
173
|
+
*/
|
|
174
|
+
async proposeGoal(ownerId, conversationId, message, settings, signal) {
|
|
175
|
+
if (!this.proposer)
|
|
176
|
+
throw new SparkServiceError(501, "This host has not configured goal proposals.");
|
|
177
|
+
const conversation = await this.getConversation(ownerId, conversationId);
|
|
178
|
+
const [input] = await validateUIMessages({ messages: [normalizeUserMessage(message)], metadataSchema: sparkMessageMetadataSchema, dataSchemas: sparkDataPartSchemas });
|
|
179
|
+
const timeout = AbortSignal.timeout(30_000);
|
|
180
|
+
return sparkGoalProposalSchema.parse(await this.proposer({
|
|
181
|
+
ownerId, message: input, messages: conversation.messages, settings,
|
|
182
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
156
185
|
/** User controls are separate from model execution and work while streaming. */
|
|
157
186
|
async goalCommand(ownerId, conversationId, command, settings, message) {
|
|
158
187
|
command = sparkGoalCommandSchema.parse(command);
|
|
@@ -171,10 +200,18 @@ export class SparkAgentService {
|
|
|
171
200
|
throw new SparkServiceError(409, "This goal is already complete.");
|
|
172
201
|
if (wompifyContextSchema.safeParse(current.settings.wompify).success)
|
|
173
202
|
throw new SparkServiceError(409, "Wompify caps are set by the detail level.");
|
|
174
|
-
|
|
175
|
-
|
|
203
|
+
if (command.creditBudget === undefined && command.maxTurns === undefined)
|
|
204
|
+
throw new SparkServiceError(400, "Give a credit cap or a tries cap to change.");
|
|
205
|
+
const goal = await this.refreshGoalCredits(ownerId, {
|
|
206
|
+
...current,
|
|
207
|
+
...(command.creditBudget === undefined ? {} : { creditBudget: command.creditBudget }),
|
|
208
|
+
...(command.maxTurns === undefined ? {} : { maxTurns: command.maxTurns }),
|
|
209
|
+
});
|
|
210
|
+
if (command.creditBudget !== undefined && command.creditBudget < goal.creditsUsed)
|
|
176
211
|
throw new SparkServiceError(409, "The cap cannot be lower than credits already used and pending. Refresh and try again.");
|
|
177
|
-
if (
|
|
212
|
+
if (command.maxTurns !== undefined && command.maxTurns < goal.turns)
|
|
213
|
+
throw new SparkServiceError(409, "The tries cap cannot be lower than the tries already used. Refresh and try again.");
|
|
214
|
+
if (goal.creditBudget !== undefined && goal.creditsUsed >= goal.creditBudget) {
|
|
178
215
|
goal.status = "budget_limited";
|
|
179
216
|
goal.lastReason = "The goal reached its credit budget.";
|
|
180
217
|
}
|
|
@@ -224,19 +261,24 @@ export class SparkAgentService {
|
|
|
224
261
|
throw new SparkServiceError(400, "Run settings are required.");
|
|
225
262
|
const goal = command.action === "set" ? {
|
|
226
263
|
id: crypto.randomUUID(), objective: command.objective, status: "active",
|
|
264
|
+
...(command.title ? { title: command.title } : {}),
|
|
265
|
+
...(command.conditions ? { conditions: command.conditions } : {}),
|
|
227
266
|
settings: runSettings,
|
|
228
267
|
createdAt: now(), updatedAt: now(), turns: 0,
|
|
229
268
|
...(command.maxTurns ? { maxTurns: command.maxTurns } : {}),
|
|
230
269
|
...(command.creditBudget ? { creditBudget: command.creditBudget, creditsUsed: 0 } : {}),
|
|
231
270
|
} : { ...current, settings: runSettings, status: "active", updatedAt: now(),
|
|
232
271
|
idleTurns: 0, };
|
|
233
|
-
|
|
272
|
+
// Only the confirmed command says what the goal's conditions are, not the message a client built.
|
|
273
|
+
const inputMessage = normalizeUserMessage(message ? { ...message, parts: message.parts.filter((part) => part.type !== "data-goal") } : {
|
|
234
274
|
id: crypto.randomUUID(), role: "user", parts: [{ type: "text", text: goalStartText(goal, command.action) }],
|
|
235
275
|
});
|
|
276
|
+
if (command.action === "set" && command.conditions)
|
|
277
|
+
inputMessage.parts.push({ type: "data-goal", data: { title: command.title ?? titleFor(inputMessage), conditions: command.conditions } });
|
|
236
278
|
await validateUIMessages({ messages: [inputMessage], metadataSchema: sparkMessageMetadataSchema, dataSchemas: sparkDataPartSchemas });
|
|
237
279
|
await this.store.saveConversationGoal(conversationId, goal);
|
|
238
280
|
try {
|
|
239
|
-
const run = await this.submitInTurn({ ownerId, conversationId, trigger: "submit-message", message: inputMessage, settings: goal.settings }, command.action === "resume");
|
|
281
|
+
const run = await this.submitInTurn({ ownerId, conversationId, trigger: "submit-message", message: inputMessage, settings: goal.settings }, command.action === "resume", true);
|
|
240
282
|
return { goal: (await this.getConversation(ownerId, conversationId)).goal, run };
|
|
241
283
|
}
|
|
242
284
|
catch (error) {
|
|
@@ -300,7 +342,7 @@ export class SparkAgentService {
|
|
|
300
342
|
async submitMessage(input) {
|
|
301
343
|
return this.inConversationTurn(input.conversationId, () => this.submitInTurn(input));
|
|
302
344
|
}
|
|
303
|
-
async submitInTurn(input, goalContinuation = false) {
|
|
345
|
+
async submitInTurn(input, goalContinuation = false, confirmedGoal = false) {
|
|
304
346
|
if (this.closing)
|
|
305
347
|
throw new SparkServiceError(503, "The service is shutting down.");
|
|
306
348
|
const conversation = await this.getConversation(input.ownerId, input.conversationId);
|
|
@@ -328,7 +370,7 @@ export class SparkAgentService {
|
|
|
328
370
|
inputMessage = previous;
|
|
329
371
|
}
|
|
330
372
|
else {
|
|
331
|
-
inputMessage = normalizeUserMessage(input.message);
|
|
373
|
+
inputMessage = normalizeUserMessage(input.message, confirmedGoal);
|
|
332
374
|
if (goalContinuation)
|
|
333
375
|
inputMessage.metadata = { ...inputMessage.metadata, goalContinuation: true };
|
|
334
376
|
if (conversation.messages.some(({ id }) => id === inputMessage.id)) {
|
|
@@ -636,16 +678,26 @@ export class SparkAgentService {
|
|
|
636
678
|
await this.store.saveConversationGoal(conversation.id, current);
|
|
637
679
|
return;
|
|
638
680
|
}
|
|
681
|
+
const conditions = judgedConditions(current, evaluation);
|
|
639
682
|
const next = {
|
|
640
683
|
...current,
|
|
684
|
+
...(conditions ? { conditions } : {}),
|
|
685
|
+
...(conditions && evaluation ? {
|
|
686
|
+
tries: [...(current.tries ?? []), {
|
|
687
|
+
turn: current.turns, runId: run.id, reason: evaluation.reason,
|
|
688
|
+
conditions: conditions.map(({ met, reason }) => ({ ...(met === undefined ? {} : { met }), ...(reason === undefined ? {} : { reason }) })),
|
|
689
|
+
}],
|
|
690
|
+
} : {}),
|
|
641
691
|
idleTurns: usedTools ? 0 : (current.idleTurns ?? 0) + 1,
|
|
642
692
|
updatedAt: now(), lastReason: evaluation?.reason,
|
|
643
693
|
};
|
|
694
|
+
// A verdict of met cannot stand over a condition the same evaluation found unmet.
|
|
695
|
+
const met = evaluation?.verdict === "met" && !conditions?.some((condition) => condition.measurable && condition.met === false);
|
|
644
696
|
if (signal.aborted || this.closing) {
|
|
645
697
|
next.status = "paused";
|
|
646
698
|
next.lastReason = "The goal was interrupted. Resume to continue.";
|
|
647
699
|
}
|
|
648
|
-
else if (
|
|
700
|
+
else if (met && !wompify)
|
|
649
701
|
next.status = "completed";
|
|
650
702
|
else if (evaluation?.verdict === "blocked")
|
|
651
703
|
next.status = "blocked";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Express } from "express";
|
|
2
2
|
import { KakapoAPI } from "@womp/kakapo-sdk";
|
|
3
|
+
import type { SparkGoal } from "../goals.js";
|
|
4
|
+
import type { SparkAgentStore } from "./types.js";
|
|
3
5
|
export type StandaloneSparkAgentOptions = {
|
|
4
6
|
openAiApiKey?: string;
|
|
5
7
|
anthropicApiKey?: string;
|
|
@@ -17,6 +19,12 @@ export type StandaloneSparkAgentOptions = {
|
|
|
17
19
|
export type StandaloneSparkAgentApp = Express & {
|
|
18
20
|
dispose(): Promise<void>;
|
|
19
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* A development fixture, not billing: the standalone server charges nothing, so
|
|
24
|
+
* a goal's credit cap is checked against the model cost its finished tries
|
|
25
|
+
* reported. Evaluations and the try in progress are not counted.
|
|
26
|
+
*/
|
|
27
|
+
export declare function estimateGoalCredits(store: Pick<SparkAgentStore, "getRun" | "getConversation">, goal: SparkGoal): Promise<number>;
|
|
20
28
|
export declare function checkStandaloneServices(api: Pick<KakapoAPI, "listFonts" | "validateOpenScad">): Promise<{
|
|
21
29
|
fonts: import("@womp/kakapo-sdk").FontFamily[];
|
|
22
30
|
fontsAvailable: boolean;
|
|
@@ -18,11 +18,34 @@ import { hasSceneResourcePressure, } from "../scene-health.js";
|
|
|
18
18
|
import { QuickJSJavaScriptExecutor } from "../quickjs-executor.js";
|
|
19
19
|
import { sparkModelOptions } from "../model-options.js";
|
|
20
20
|
import { createSparkRunExecutor } from "./executor.js";
|
|
21
|
+
import { createSparkGoalEvaluator } from "./goal-evaluator.js";
|
|
22
|
+
import { createSparkGoalProposer } from "./goal-proposer.js";
|
|
21
23
|
import { createSparkAgentRouter } from "./express.js";
|
|
22
24
|
import { SparkAgentService } from "./service.js";
|
|
23
25
|
import { SqliteSparkStore } from "./sqlite-store.js";
|
|
24
26
|
import { clearSceneContents } from "./scene-actions.js";
|
|
25
27
|
import { standaloneSceneAccess } from "./scene-access.js";
|
|
28
|
+
/** Production's list rate: 1,000 credits per USD of model cost, with its 1.25 markup. */
|
|
29
|
+
const DEVELOPMENT_CREDITS_PER_USD = 1_250;
|
|
30
|
+
/**
|
|
31
|
+
* A development fixture, not billing: the standalone server charges nothing, so
|
|
32
|
+
* a goal's credit cap is checked against the model cost its finished tries
|
|
33
|
+
* reported. Evaluations and the try in progress are not counted.
|
|
34
|
+
*/
|
|
35
|
+
export async function estimateGoalCredits(store, goal) {
|
|
36
|
+
const run = goal.runId === undefined ? undefined : await store.getRun(goal.runId);
|
|
37
|
+
const conversation = run && await store.getConversation(run.conversationId);
|
|
38
|
+
let usd = 0;
|
|
39
|
+
for (const message of conversation?.messages ?? []) {
|
|
40
|
+
const runId = message.role === "assistant" ? message.metadata?.runId : undefined;
|
|
41
|
+
if (runId === undefined || (await store.getRun(runId))?.goalId !== goal.id)
|
|
42
|
+
continue;
|
|
43
|
+
for (const part of message.parts)
|
|
44
|
+
if (part.type === "data-step-metrics")
|
|
45
|
+
usd += part.data.cost?.totalUsd ?? 0;
|
|
46
|
+
}
|
|
47
|
+
return Math.ceil(usd * DEVELOPMENT_CREDITS_PER_USD);
|
|
48
|
+
}
|
|
26
49
|
export async function checkStandaloneServices(api) {
|
|
27
50
|
const [fonts, openScad] = await Promise.allSettled([
|
|
28
51
|
api.listFonts(),
|
|
@@ -172,49 +195,56 @@ export async function createStandaloneSparkAgentApp(options) {
|
|
|
172
195
|
throw error;
|
|
173
196
|
}
|
|
174
197
|
},
|
|
175
|
-
resolveModel
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
model: createAnthropic({ apiKey: options.anthropicApiKey })(selected.id),
|
|
187
|
-
modelId: selected.id,
|
|
188
|
-
providerOptions: sparkModelOptions(selected.id, selected.provider, settings.reasoningEffort),
|
|
189
|
-
maxOutputTokens: 48_000,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
if (selected.provider === "google") {
|
|
193
|
-
if (!options.geminiApiKey?.trim()) {
|
|
194
|
-
throw new Error("GEMINI_API_KEY is required for this model.");
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
model: createGoogleGenerativeAI({ apiKey: options.geminiApiKey })(selected.id),
|
|
198
|
-
modelId: selected.id,
|
|
199
|
-
providerOptions: sparkModelOptions(selected.id, selected.provider, settings.reasoningEffort),
|
|
200
|
-
maxOutputTokens: 48_000,
|
|
201
|
-
};
|
|
198
|
+
resolveModel,
|
|
199
|
+
});
|
|
200
|
+
function resolveModel(settings) {
|
|
201
|
+
const selected = getSparkModel(settings.modelId);
|
|
202
|
+
if (SPARK_MODELS.some(({ id }) => id === settings.modelId) === false) {
|
|
203
|
+
throw new Error(`Unsupported Spark model: ${settings.modelId}`);
|
|
204
|
+
}
|
|
205
|
+
if (selected.provider === "anthropic") {
|
|
206
|
+
if (options.anthropicApiKey?.trim() === "" ||
|
|
207
|
+
options.anthropicApiKey === undefined) {
|
|
208
|
+
throw new Error("ANTHROPIC_API_KEY is required for this model.");
|
|
202
209
|
}
|
|
203
|
-
|
|
204
|
-
options.
|
|
205
|
-
|
|
210
|
+
return {
|
|
211
|
+
model: createAnthropic({ apiKey: options.anthropicApiKey })(selected.id),
|
|
212
|
+
modelId: selected.id,
|
|
213
|
+
providerOptions: sparkModelOptions(selected.id, selected.provider, settings.reasoningEffort),
|
|
214
|
+
maxOutputTokens: 48_000,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (selected.provider === "google") {
|
|
218
|
+
if (!options.geminiApiKey?.trim()) {
|
|
219
|
+
throw new Error("GEMINI_API_KEY is required for this model.");
|
|
206
220
|
}
|
|
207
221
|
return {
|
|
208
|
-
model:
|
|
222
|
+
model: createGoogleGenerativeAI({ apiKey: options.geminiApiKey })(selected.id),
|
|
209
223
|
modelId: selected.id,
|
|
210
224
|
providerOptions: sparkModelOptions(selected.id, selected.provider, settings.reasoningEffort),
|
|
211
225
|
maxOutputTokens: 48_000,
|
|
212
226
|
};
|
|
213
|
-
}
|
|
214
|
-
|
|
227
|
+
}
|
|
228
|
+
if (options.openAiApiKey?.trim() === "" ||
|
|
229
|
+
options.openAiApiKey === undefined) {
|
|
230
|
+
throw new Error("OPENAI_API_KEY is required for this model.");
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
model: createOpenAI({ apiKey: options.openAiApiKey }).responses(selected.id),
|
|
234
|
+
modelId: selected.id,
|
|
235
|
+
providerOptions: sparkModelOptions(selected.id, selected.provider, settings.reasoningEffort),
|
|
236
|
+
maxOutputTokens: 48_000,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
215
239
|
let clearingScene = false;
|
|
216
240
|
let activeRuns = 0;
|
|
217
|
-
const service = new SparkAgentService({
|
|
241
|
+
const service = new SparkAgentService({
|
|
242
|
+
store,
|
|
243
|
+
evaluateGoal: createSparkGoalEvaluator({ resolveModel, resolveImage: (ownerId, artifactId) => artifactStore.get(ownerId, artifactId) }),
|
|
244
|
+
// Reading a request's conditions precedes every send, so it runs at the lowest effort.
|
|
245
|
+
proposeGoal: createSparkGoalProposer({ resolveModel: (settings) => resolveModel({ ...settings, reasoningEffort: "low" }) }),
|
|
246
|
+
readGoalCredits: (_ownerId, goal) => estimateGoalCredits(store, goal),
|
|
247
|
+
executeRun: async (context) => {
|
|
218
248
|
if (clearingScene)
|
|
219
249
|
throw new Error("The scene is being cleared. Try again when it finishes.");
|
|
220
250
|
activeRuns++;
|
|
@@ -226,7 +256,8 @@ export async function createStandaloneSparkAgentApp(options) {
|
|
|
226
256
|
activeRuns--;
|
|
227
257
|
throw error;
|
|
228
258
|
}
|
|
229
|
-
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
230
261
|
try {
|
|
231
262
|
for (const run of await store.listInterruptedRuns())
|
|
232
263
|
await service.recoverInterruptedRun(run.id);
|
package/dist/ui.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ export { sparkErrorDiagnostics, sparkErrorDiagnosticsSchema, type SparkErrorDiag
|
|
|
3
3
|
import type { InferUIMessageChunk, UIMessage } from "ai";
|
|
4
4
|
import type { SceneScreenshotOptions } from "./capabilities/scene.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
export type { SparkGoal, SparkGoalCommand } from "./goals.js";
|
|
6
|
+
export type { SparkGoal, SparkGoalCommand, SparkGoalCondition, SparkGoalProposal, SparkGoalTry } from "./goals.js";
|
|
7
|
+
import { type SparkGoalProposal } from "./goals.js";
|
|
7
8
|
import type { ExecutionReceipt, SparkSkill } from "./capabilities.js";
|
|
8
9
|
export { DEFAULT_MODEL_ID, getSparkModel, REASONING_EFFORT_LABELS, resolveReasoningEffort, SPARK_MODELS, } from "./models.js";
|
|
9
10
|
export type { ModelProvider, ReasoningEffort, SparkModel } from "./models.js";
|
|
@@ -90,11 +91,31 @@ export type SparkDataParts = {
|
|
|
90
91
|
receipt: ExecutionReceipt;
|
|
91
92
|
};
|
|
92
93
|
selection: SparkSelectionData;
|
|
94
|
+
compaction: SparkCompaction;
|
|
95
|
+
goal: SparkGoalProposal;
|
|
93
96
|
};
|
|
94
97
|
/** Editor node IDs selected when a user message was sent; never rendered as chat text. */
|
|
95
98
|
export type SparkSelectionData = {
|
|
96
99
|
nodeIds: string[];
|
|
97
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* A summary of a long conversation's earlier messages, recorded on the assistant
|
|
103
|
+
* message of the turn that wrote it. The model is sent the summary in place of
|
|
104
|
+
* every message before `firstKeptMessageId`; the chat still shows all of them.
|
|
105
|
+
*/
|
|
106
|
+
export type SparkCompaction = {
|
|
107
|
+
/** The user message the model reads verbatim from; everything before it is summarized. */
|
|
108
|
+
firstKeptMessageId: string;
|
|
109
|
+
summary: string;
|
|
110
|
+
/** Skills read in the summarized messages; their current text is sent after the summary. */
|
|
111
|
+
skills: string[];
|
|
112
|
+
/** The model that wrote the summary. */
|
|
113
|
+
modelId: string;
|
|
114
|
+
/** The conversation's size before and after, in tokens. */
|
|
115
|
+
tokensBefore: number;
|
|
116
|
+
tokensAfter: number;
|
|
117
|
+
createdAt: string;
|
|
118
|
+
};
|
|
98
119
|
export type ExecuteJavaScriptInput = {
|
|
99
120
|
purpose: string;
|
|
100
121
|
code: string;
|
|
@@ -333,6 +354,26 @@ export declare const sparkDataPartSchemas: {
|
|
|
333
354
|
readonly selection: z.ZodObject<{
|
|
334
355
|
nodeIds: z.ZodArray<z.ZodString>;
|
|
335
356
|
}, z.core.$strip>;
|
|
357
|
+
readonly compaction: z.ZodObject<{
|
|
358
|
+
firstKeptMessageId: z.ZodString;
|
|
359
|
+
summary: z.ZodString;
|
|
360
|
+
skills: z.ZodArray<z.ZodString>;
|
|
361
|
+
modelId: z.ZodString;
|
|
362
|
+
tokensBefore: z.ZodNumber;
|
|
363
|
+
tokensAfter: z.ZodNumber;
|
|
364
|
+
createdAt: z.ZodString;
|
|
365
|
+
}, z.core.$strip>;
|
|
366
|
+
/**
|
|
367
|
+
* The conditions the user confirmed for the goal this message starts. The model
|
|
368
|
+
* reads it as text (model-history.ts).
|
|
369
|
+
*/
|
|
370
|
+
readonly goal: z.ZodObject<{
|
|
371
|
+
title: z.ZodString;
|
|
372
|
+
conditions: z.ZodArray<z.ZodObject<{
|
|
373
|
+
text: z.ZodString;
|
|
374
|
+
measurable: z.ZodBoolean;
|
|
375
|
+
}, z.core.$strip>>;
|
|
376
|
+
}, z.core.$strip>;
|
|
336
377
|
};
|
|
337
378
|
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt, wompifyContextSchema, type WompifyRequest, type WompifyContext } from "./wompify.js";
|
|
338
|
-
export {
|
|
379
|
+
export { DEFAULT_GOAL_CREDITS, GOAL_CONFIRMATION_DEFAULTS, MAX_GOAL_LENGTH, sparkGoalProposalSchema } from "./goals.js";
|
package/dist/ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sparkErrorDiagnosticsSchema } from "./error-diagnostics.js";
|
|
2
2
|
export { sparkErrorDiagnostics, sparkErrorDiagnosticsSchema, } from "./error-diagnostics.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { sparkGoalProposalSchema } from "./goals.js";
|
|
4
5
|
export { DEFAULT_MODEL_ID, getSparkModel, REASONING_EFFORT_LABELS, resolveReasoningEffort, SPARK_MODELS, } from "./models.js";
|
|
5
6
|
export const SPARK_RUN_STATUSES = [
|
|
6
7
|
"queued",
|
|
@@ -85,6 +86,20 @@ export const sparkDataPartSchemas = {
|
|
|
85
86
|
*/
|
|
86
87
|
// Editor ids are the engine's decimal integers; 8192 covers a select-all at the engine's budgets.
|
|
87
88
|
selection: z.object({ nodeIds: z.array(z.string().min(1).max(64)).min(1).max(8192) }),
|
|
89
|
+
compaction: z.object({
|
|
90
|
+
firstKeptMessageId: z.string().min(1),
|
|
91
|
+
summary: z.string().min(1),
|
|
92
|
+
skills: z.array(z.string().min(1)),
|
|
93
|
+
modelId: z.string(),
|
|
94
|
+
tokensBefore: z.number().int().nonnegative(),
|
|
95
|
+
tokensAfter: z.number().int().nonnegative(),
|
|
96
|
+
createdAt: z.string().datetime(),
|
|
97
|
+
}),
|
|
98
|
+
/**
|
|
99
|
+
* The conditions the user confirmed for the goal this message starts. The model
|
|
100
|
+
* reads it as text (model-history.ts).
|
|
101
|
+
*/
|
|
102
|
+
goal: sparkGoalProposalSchema,
|
|
88
103
|
};
|
|
89
104
|
export { parseWompifyCommand, WOMPIFY_PRESETS, wompifyPrompt, wompifyContextSchema } from "./wompify.js";
|
|
90
|
-
export {
|
|
105
|
+
export { DEFAULT_GOAL_CREDITS, GOAL_CONFIRMATION_DEFAULTS, MAX_GOAL_LENGTH, sparkGoalProposalSchema } from "./goals.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SparkUIMessage } from "./ui.js";
|
|
2
|
+
export type SparkFilePart = Extract<SparkUIMessage["parts"][number], {
|
|
3
|
+
type: "file";
|
|
4
|
+
}>;
|
|
5
|
+
/**
|
|
6
|
+
* The images the user attached to `messages`, each once, in the order sent:
|
|
7
|
+
* what the work is often built from, so the model calls that do or judge it
|
|
8
|
+
* keep them longest.
|
|
9
|
+
*/
|
|
10
|
+
export declare function userImages(messages: readonly SparkUIMessage[]): SparkFilePart[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The images the user attached to `messages`, each once, in the order sent:
|
|
3
|
+
* what the work is often built from, so the model calls that do or judge it
|
|
4
|
+
* keep them longest.
|
|
5
|
+
*/
|
|
6
|
+
export function userImages(messages) {
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const images = [];
|
|
9
|
+
for (const message of messages) {
|
|
10
|
+
if (message.role !== "user")
|
|
11
|
+
continue;
|
|
12
|
+
for (const part of message.parts) {
|
|
13
|
+
if (part.type !== "file" || !part.mediaType.startsWith("image/") || seen.has(part.url))
|
|
14
|
+
continue;
|
|
15
|
+
seen.add(part.url);
|
|
16
|
+
images.push(part);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return images;
|
|
20
|
+
}
|