@womp/spark-agent 6.3.0 → 6.5.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 +81 -0
- package/README.md +13 -11
- package/dist/agent-tools.d.ts +19 -3
- package/dist/agent-tools.js +38 -0
- package/dist/agent.d.ts +15 -3
- package/dist/agent.js +32 -1
- package/dist/client.d.ts +2 -4
- package/dist/client.js +0 -11
- 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 +41 -9
- package/dist/goals.js +28 -8
- package/dist/index.d.ts +1 -0
- package/dist/model-history.d.ts +13 -3
- package/dist/model-history.js +10 -17
- package/dist/server/executor.d.ts +2 -0
- package/dist/server/executor.js +3 -10
- package/dist/server/express.js +0 -9
- package/dist/server/goal-evaluator.d.ts +7 -0
- package/dist/server/goal-evaluator.js +22 -0
- package/dist/server/index.d.ts +2 -3
- package/dist/server/index.js +2 -3
- package/dist/server/service.d.ts +24 -10
- package/dist/server/service.js +55 -30
- package/dist/server/standalone.d.ts +1 -1
- package/dist/server/standalone.js +21 -3
- package/dist/server/types.d.ts +3 -0
- package/dist/ui.d.ts +50 -7
- package/dist/ui.js +15 -5
- package/dist/user-images.d.ts +10 -0
- package/dist/user-images.js +20 -0
- package/package.json +1 -1
- package/dist/server/goal-proposer.d.ts +0 -15
- package/dist/server/goal-proposer.js +0 -64
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,87 @@ Notable changes to `@womp/spark-agent`, newest first. Versions follow
|
|
|
4
4
|
[Semantic Versioning](https://semver.org/). Add an entry with each version bump; describe what
|
|
5
5
|
changed for hosts and users, and how to migrate across breaking changes.
|
|
6
6
|
|
|
7
|
+
## 6.5.0 — 2026-10-12
|
|
8
|
+
|
|
9
|
+
The agent proposes a goal during its turn, instead of the host asking a separate model call to
|
|
10
|
+
read every message before it is sent. Ordinary messages start at once, the proposal shares the
|
|
11
|
+
run's prompt cache, and when to propose is decided in one place: the tool's description.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- The `propose_goal({ title, conditions })` tool, declared only when the host can ask the user.
|
|
16
|
+
The run waits in `requires_action` for the answer, so nothing is built before it. The tool
|
|
17
|
+
result tells the agent to build toward the goal, to carry on as a single try, or to stop and
|
|
18
|
+
wait.
|
|
19
|
+
- The `data-goal-proposal` part (`sparkGoalProposalPartSchema`) stores the proposal on the
|
|
20
|
+
assistant message that made it: `title`, `conditions`, the `toolCallId`, and the `outcome` once
|
|
21
|
+
there is one. Hosts render it as the confirmation while its run waits and as the goal once it
|
|
22
|
+
started; the model does not read it.
|
|
23
|
+
- `SparkAgentServiceOptions.confirmGoal` shows a proposal and resolves a `SparkGoalDecision`
|
|
24
|
+
(`sparkGoalDecisionSchema`): a goal with its caps, a single try, or unanswered. A confirmed goal
|
|
25
|
+
starts with the run that proposed it as its first try, with the run's request as its objective.
|
|
26
|
+
- `SparkAgentServiceOptions.onGoalStart`, called when a confirmed goal is stored and before the
|
|
27
|
+
agent continues. A goal it rejects does not start.
|
|
28
|
+
- `SparkRunExecutionContext.proposeGoal` and `SparkAgentOptions.proposeGoal` (`SparkGoalOffer`)
|
|
29
|
+
carry the offer to the agent. A custom `executeRun` that drops it leaves the tool undeclared.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `createSparkGoalProposer`, `SparkGoalProposer`, `SparkAgentService.proposeGoal`,
|
|
34
|
+
`SparkAgentServiceOptions.proposeGoal`, `POST /conversations/:id/goal/proposal` and
|
|
35
|
+
`SparkChatTransport.proposeGoal`.
|
|
36
|
+
- `title` and `conditions` on the goal `set` command, and the `data-goal` part with its
|
|
37
|
+
`sparkDataPartSchemas.goal` schema: conditions now come only from a proposal the user confirmed.
|
|
38
|
+
A conversation that holds a `data-goal` part, written by 6.3.0 or 6.4.0, no longer validates.
|
|
39
|
+
- `SparkChatTransport.sendMessages` no longer forwards `body.goal`.
|
|
40
|
+
- `MAX_GOAL_LENGTH` from `@womp/spark-agent/ui`.
|
|
41
|
+
|
|
42
|
+
### Migration
|
|
43
|
+
|
|
44
|
+
- Replace the pre-send proposal call and the `set` command that followed it with `confirmGoal`:
|
|
45
|
+
show the proposal when asked, resolve the user's answer, and send every message as an ordinary
|
|
46
|
+
one. Render the `data-goal-proposal` part as the confirmation while its run waits.
|
|
47
|
+
- A host that billed a goal from the `set` command starts counting in `onGoalStart`; the first
|
|
48
|
+
try's run began before the goal existed.
|
|
49
|
+
- A host that stores tool calls by name must accept `propose_goal`, and must offer the tool again
|
|
50
|
+
when it replays that history, so the earlier call reads as it did live.
|
|
51
|
+
- A host that validates stored messages against `sparkDataPartSchemas` must be on this version
|
|
52
|
+
before it reads a conversation that holds a `data-goal-proposal` part.
|
|
53
|
+
|
|
54
|
+
## 6.4.0 — 2026-10-12
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- Conversation compaction. Once a conversation reaches 256k tokens (by the provider's count for its
|
|
59
|
+
last step, or an estimate), the run's own model summarizes everything before the most recent
|
|
60
|
+
16k tokens of whole turns, before the turn's first step. Later requests send the summary, the
|
|
61
|
+
current text of the skills read in the summarized messages, and the newest four images the user
|
|
62
|
+
attached to them (`COMPACTION_USER_IMAGES`), in their place. The summary is
|
|
63
|
+
recorded as a `data-compaction` part (`SparkCompaction`) on the turn's assistant message, so
|
|
64
|
+
hosts store it with the message; the chat still has every message. A summary that fails leaves
|
|
65
|
+
the turn uncompacted, and context trimming still applies.
|
|
66
|
+
- `SparkAgentOptions.history`: the stored conversation, converted and compacted by the run. Pass it
|
|
67
|
+
instead of `messages` to get compaction; `createSparkRunExecutor` now does.
|
|
68
|
+
- `convertSparkMessages` takes `{ compaction, skills }` and applies the latest compaction in the
|
|
69
|
+
messages it is given.
|
|
70
|
+
- `SparkAgentOptions.compaction` and `SparkRunExecutorOptions.compaction` (`SparkCompactionLimits`)
|
|
71
|
+
lower the threshold and the recent tail kept, for a host that exercises compaction without a
|
|
72
|
+
production-sized conversation, such as local development. Absent, the defaults apply.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- Goal evaluation attaches the newest `GOAL_REFERENCE_IMAGES` images the user attached to the
|
|
77
|
+
conversation, however far back, as the reference, ahead of the inspection screenshots. It reads
|
|
78
|
+
them only from `data:` URLs, so a host that stores them elsewhere passes `messages` with those
|
|
79
|
+
images inline; others are listed as unavailable.
|
|
80
|
+
- When a conversation is over its context window, trimming omits the agent's older screenshots
|
|
81
|
+
before the older images the user attached.
|
|
82
|
+
|
|
83
|
+
### Migration
|
|
84
|
+
|
|
85
|
+
- Hosts that validate stored messages with `sparkDataPartSchemas` must take this version before any
|
|
86
|
+
host writing compactions reads the same conversations, or the new part fails validation.
|
|
87
|
+
|
|
7
88
|
## 6.3.0 — 2026-10-09
|
|
8
89
|
|
|
9
90
|
### Added
|
package/README.md
CHANGED
|
@@ -35,8 +35,8 @@ Kakapo, Express, or React at runtime.
|
|
|
35
35
|
|
|
36
36
|
## Running in a host application
|
|
37
37
|
|
|
38
|
-
`runSparkAgent` accepts a configured model,
|
|
39
|
-
and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
|
|
38
|
+
`runSparkAgent` accepts a configured model, the stored conversation (`history`, compacted once it
|
|
39
|
+
is long) or model messages, an executor, a capability composition, and an artifact resolver. It exposes exactly `read_skill` and `execute_javascript` to the model.
|
|
40
40
|
Skills carry instructions, declarations, and JavaScript examples. Kakapo skill declarations are
|
|
41
41
|
generated from its typed API at build time and packaged as JavaScript strings.
|
|
42
42
|
|
|
@@ -138,7 +138,6 @@ Closing or reloading the page only detaches the SSE subscriber. It does not canc
|
|
|
138
138
|
POST /agent/conversations/:conversationId/messages
|
|
139
139
|
PATCH /agent/conversations/:conversationId/settings
|
|
140
140
|
POST /agent/conversations/:conversationId/goal
|
|
141
|
-
POST /agent/conversations/:conversationId/goal/proposal
|
|
142
141
|
GET /agent/runs/:runId/stream?after=0
|
|
143
142
|
POST /agent/runs/:runId/cancel
|
|
144
143
|
```
|
|
@@ -147,14 +146,17 @@ Conversation settings contain `modelId` and `reasoningEffort`; save them with
|
|
|
147
146
|
`transport.saveConversationSettings(id, settings)`. The goal endpoint accepts `set`, `pause`,
|
|
148
147
|
`resume`, `clear`, and `update_budget` commands.
|
|
149
148
|
|
|
150
|
-
A goal starts from an ordinary message
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
A goal starts from an ordinary message, when the agent proposes one. A host that sets
|
|
150
|
+
`SparkAgentServiceOptions.confirmGoal` (with `evaluateGoal`) gives the agent the `propose_goal`
|
|
151
|
+
tool: it calls it before building when the request sets constraints a result can be measured
|
|
152
|
+
against, with a title and the conditions, each marked `measurable` or left to the user's review.
|
|
153
|
+
The run then waits in `requires_action` while `confirmGoal` shows the proposal, which the
|
|
154
|
+
assistant message stores as a `data-goal-proposal` part, and resolves the user's answer: `{ kind: "goal", maxTurns,
|
|
155
|
+
creditBudget }`, `{ kind: "once" }`, or `{ kind: "unanswered" }` when the host stops waiting. A
|
|
156
|
+
confirmed goal starts with that run as its first try; the service evaluates each measurable
|
|
157
|
+
condition after every try and records the result on `goal.conditions`. Any other answer leaves an
|
|
158
|
+
ordinary turn. The part then carries the `outcome`: `started`, `declined`, `unanswered`, or `active`
|
|
159
|
+
when a goal was already running. `onGoalStart` tells a host that counts a goal's spending when one starts.
|
|
158
160
|
|
|
159
161
|
The SSE response contains ordered persistence envelopes around standard AI SDK chunks:
|
|
160
162
|
|
package/dist/agent-tools.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { type Tool } from "ai";
|
|
2
2
|
import type { ExecutionReceipt, SparkArtifactResolver, SparkCapabilities } from "./capabilities.js";
|
|
3
3
|
import type { JavaScriptExecutor } from "./executor.js";
|
|
4
|
+
import { type SparkGoalOutcome, type SparkGoalProposal } from "./goals.js";
|
|
4
5
|
import type { SparkUIMessage, SparkUIMessageChunk, SparkUITools } from "./ui.js";
|
|
5
6
|
export type SparkToolCall = {
|
|
6
7
|
id: string;
|
|
7
|
-
name: "read_skill" | "execute_javascript";
|
|
8
|
+
name: "read_skill" | "execute_javascript" | "propose_goal";
|
|
8
9
|
input: unknown;
|
|
9
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Shows the user a goal the model proposed and waits for their answer, starting
|
|
13
|
+
* the goal for this run when they confirm it.
|
|
14
|
+
*/
|
|
15
|
+
export type SparkGoalOffer = (proposal: SparkGoalProposal, call: {
|
|
16
|
+
toolCallId: string;
|
|
17
|
+
signal: AbortSignal;
|
|
18
|
+
}) => Promise<SparkGoalOutcome>;
|
|
10
19
|
export interface SparkPersistence {
|
|
11
20
|
beforeToolCall(call: SparkToolCall): Promise<void>;
|
|
12
21
|
recordReceipt(callId: string, receipt: ExecutionReceipt): Promise<void>;
|
|
@@ -22,9 +31,16 @@ export type SparkToolOptions = {
|
|
|
22
31
|
controller: AbortController;
|
|
23
32
|
persistence?: SparkPersistence;
|
|
24
33
|
executionTimeoutMs?: number;
|
|
34
|
+
/** Present when the host can ask the user: the model then has `propose_goal`. */
|
|
35
|
+
proposeGoal?: SparkGoalOffer;
|
|
25
36
|
write(chunk: SparkUIMessageChunk): void;
|
|
26
37
|
};
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
type SparkTool<Name extends keyof SparkUITools> = Tool<SparkUITools[Name]["input"], SparkUITools[Name]["output"]>;
|
|
39
|
+
type SparkCoreTools = {
|
|
40
|
+
[Name in "read_skill" | "execute_javascript"]: SparkTool<Name>;
|
|
29
41
|
};
|
|
42
|
+
export type SparkTools = SparkCoreTools | (SparkCoreTools & {
|
|
43
|
+
propose_goal: SparkTool<"propose_goal">;
|
|
44
|
+
});
|
|
30
45
|
export declare function createSparkTools(options: SparkToolOptions): SparkTools;
|
|
46
|
+
export {};
|
package/dist/agent-tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { boundedResultText, canonicalJsonValue } from "./bounded-output.js";
|
|
4
4
|
import { completedInputString } from "./tool-input-label.js";
|
|
5
5
|
import { UserWait } from "./execution-lifetime.js";
|
|
6
|
+
import { sparkGoalProposalSchema } from "./goals.js";
|
|
6
7
|
function errorMessage(error) {
|
|
7
8
|
return error instanceof Error ? error.message : String(error);
|
|
8
9
|
}
|
|
@@ -21,6 +22,13 @@ const artifactRefSchema = z.object({
|
|
|
21
22
|
mediaType: z.string().min(1),
|
|
22
23
|
name: z.string().optional(),
|
|
23
24
|
});
|
|
25
|
+
/** What the model is told a proposal came to, and so what to do next. */
|
|
26
|
+
const GOAL_OUTCOMES = {
|
|
27
|
+
started: "The user confirmed the goal. Build it now, and verify every measurable condition with tool results before you finish. An independent check follows each try and sends back what is still unmet.",
|
|
28
|
+
declined: "The user chose a single try. Carry out the request as usual and do not propose a goal for it again.",
|
|
29
|
+
unanswered: "The user has not answered. Build nothing: say that you are waiting for their answer, and stop.",
|
|
30
|
+
active: "A goal is already running in this conversation. Keep working toward it.",
|
|
31
|
+
};
|
|
24
32
|
export function createSparkTools(options) {
|
|
25
33
|
const { controller, persistence } = options;
|
|
26
34
|
const executionTimeoutMs = options.executionTimeoutMs ?? 60_000;
|
|
@@ -76,7 +84,37 @@ export function createSparkTools(options) {
|
|
|
76
84
|
onInputAvailable: ({ toolCallId }) => { inputs.delete(toolCallId); },
|
|
77
85
|
};
|
|
78
86
|
};
|
|
87
|
+
const { proposeGoal } = options;
|
|
79
88
|
return {
|
|
89
|
+
...(proposeGoal === undefined ? {} : {
|
|
90
|
+
propose_goal: tool({
|
|
91
|
+
description: "Offer to run the user's request as a goal: work that repeats until an independent check verifies its conditions. Call it once, before building or generating anything, when the request sets a constraint the finished result passes or fails by measurement: a size, a count, a limit, a fit to something real, a clearance, an angle, a weight, a price, or a physical requirement such as being one piece, resting on the ground or parts not intersecting. Do not call it for a question, a small direct edit, a request that only describes what to make, or while a goal is running. It shows the user the conditions and waits for their answer.",
|
|
92
|
+
inputSchema: sparkGoalProposalSchema.extend({
|
|
93
|
+
title: sparkGoalProposalSchema.shape.title.describe("Two to four words in sentence case naming what is being made."),
|
|
94
|
+
conditions: sparkGoalProposalSchema.shape.conditions.describe("Only constraints the user stated or directly implied, never your own, each marked measurable. The description of what to make is not a list of constraints: leave out its parts and features, and any instruction about how to work, what to check or what to render. State each as a fact about the finished result in a few words of the user's language, with its number and unit: \"Height is 120 mm\", not \"Make it 120 mm tall\". Split compound constraints. If the user asked for a look, style, feel or quality that takes judgement, add it as one condition that is not measurable, left to their review."),
|
|
95
|
+
}),
|
|
96
|
+
execute: (input, { toolCallId }) => execute({ id: toolCallId, name: "propose_goal", input }, async () => {
|
|
97
|
+
const status = (status, label) => options.write({ type: "data-run-status", data: { runId: options.runId, status, label } });
|
|
98
|
+
// The message keeps the proposal, and then what became of it, under one id.
|
|
99
|
+
const store = (outcome) => options.write({
|
|
100
|
+
type: "data-goal-proposal", id: `goal-proposal-${toolCallId}`,
|
|
101
|
+
data: { ...input, toolCallId, ...(outcome ? { outcome } : {}) },
|
|
102
|
+
});
|
|
103
|
+
store();
|
|
104
|
+
status("requires_action", "Waiting for your answer");
|
|
105
|
+
try {
|
|
106
|
+
const goal = await proposeGoal(input, { toolCallId, signal: controller.signal });
|
|
107
|
+
store(goal);
|
|
108
|
+
return { goal };
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (!controller.signal.aborted)
|
|
112
|
+
status("running", "Continuing after user input");
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
toModelOutput: ({ output }) => ({ type: "text", value: GOAL_OUTCOMES[output.goal] }),
|
|
116
|
+
}),
|
|
117
|
+
}),
|
|
80
118
|
read_skill: tool({
|
|
81
119
|
...inputLabels("name"),
|
|
82
120
|
description: "Read the skills listed in names. Returns instructions, TypeScript API declarations, and JavaScript examples from the available directory.",
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { streamText, type LanguageModel, type ModelMessage } from "ai";
|
|
2
|
-
import { type SparkPersistence } from "./agent-tools.js";
|
|
2
|
+
import { type SparkGoalOffer, type SparkPersistence } from "./agent-tools.js";
|
|
3
3
|
import type { SparkArtifactResolver, SparkCapabilities } from "./capabilities.js";
|
|
4
4
|
import type { JavaScriptExecutor } from "./executor.js";
|
|
5
5
|
import { type ContextBudgetReport } from "./context-budget.js";
|
|
6
|
-
import type
|
|
6
|
+
import { type SparkCompactionLimits } from "./compaction.js";
|
|
7
|
+
import type { SparkUIMessage, SparkUIMessageChunk } from "./ui.js";
|
|
7
8
|
export type SparkAgentResult = {
|
|
8
9
|
text: string;
|
|
9
10
|
steps: number;
|
|
@@ -12,7 +13,14 @@ export type SparkAgentResult = {
|
|
|
12
13
|
finishReason: string;
|
|
13
14
|
};
|
|
14
15
|
export type SparkAgentOptions = {
|
|
15
|
-
messages
|
|
16
|
+
/** The conversation as model messages, sent as given. Pass `history` instead to have long conversations compacted. */
|
|
17
|
+
messages?: ModelMessage[];
|
|
18
|
+
/**
|
|
19
|
+
* The stored conversation. It is converted with `convertSparkMessages`, and
|
|
20
|
+
* compacted first once it is long enough (./compaction.ts); the new
|
|
21
|
+
* compaction is written to this run's message as a `data-compaction` part.
|
|
22
|
+
*/
|
|
23
|
+
history?: SparkUIMessage[];
|
|
16
24
|
model: LanguageModel;
|
|
17
25
|
modelId: string;
|
|
18
26
|
providerOptions?: Parameters<typeof streamText>[0]["providerOptions"];
|
|
@@ -27,9 +35,13 @@ export type SparkAgentOptions = {
|
|
|
27
35
|
maxOutputTokens?: number;
|
|
28
36
|
executionTimeoutMs?: number;
|
|
29
37
|
persistence?: SparkPersistence;
|
|
38
|
+
/** Gives the model `propose_goal`; absent, it has no way to offer a goal. */
|
|
39
|
+
proposeGoal?: SparkGoalOffer;
|
|
30
40
|
prepareMessages?(messages: ModelMessage[]): Promise<ModelMessage[]>;
|
|
31
41
|
/** The model's context window. Absent: `contextWindowFor(modelId)`. */
|
|
32
42
|
contextWindow?: number;
|
|
43
|
+
/** When `history` is compacted. Absent: the defaults in ./compaction.ts. */
|
|
44
|
+
compaction?: SparkCompactionLimits;
|
|
33
45
|
/** Called when a step's messages had to be trimmed to fit, or still do not fit. */
|
|
34
46
|
onContextBudget?(report: ContextBudgetReport): void;
|
|
35
47
|
};
|
package/dist/agent.js
CHANGED
|
@@ -5,6 +5,8 @@ import { createSparkTools } from "./agent-tools.js";
|
|
|
5
5
|
import { sparkSystemPrompt } from "./prompt.js";
|
|
6
6
|
import { calculateStepCost } from "./pricing.js";
|
|
7
7
|
import { contextWindowFor, fitMessagesToContext } from "./context-budget.js";
|
|
8
|
+
import { compactSparkHistory } from "./compaction.js";
|
|
9
|
+
import { convertSparkMessages } from "./model-history.js";
|
|
8
10
|
function errorMessage(error) {
|
|
9
11
|
return sparkErrorDiagnostics(error).message;
|
|
10
12
|
}
|
|
@@ -37,6 +39,9 @@ export function runSparkAgent(options) {
|
|
|
37
39
|
if (Number.isInteger(maxSteps) === false || maxSteps < 1) {
|
|
38
40
|
throw new Error("maxSteps must be a positive integer.");
|
|
39
41
|
}
|
|
42
|
+
if ((options.messages === undefined) === (options.history === undefined)) {
|
|
43
|
+
throw new Error("Pass either messages or history.");
|
|
44
|
+
}
|
|
40
45
|
const startedAt = Date.now();
|
|
41
46
|
const controller = new AbortController();
|
|
42
47
|
const onAbort = () => controller.abort(options.signal?.reason);
|
|
@@ -125,10 +130,36 @@ export function runSparkAgent(options) {
|
|
|
125
130
|
write: (chunk) => writer.write(chunk),
|
|
126
131
|
});
|
|
127
132
|
const readSystem = () => sparkSystemPrompt(options.capabilities.skills, options.instructions);
|
|
133
|
+
let messages = options.messages ?? [];
|
|
134
|
+
if (options.history !== undefined) {
|
|
135
|
+
const history = options.history;
|
|
136
|
+
const compacted = await compactSparkHistory({
|
|
137
|
+
history,
|
|
138
|
+
convert: (compaction) => convertSparkMessages(history, tools, { compaction, skills: options.capabilities.skills }),
|
|
139
|
+
model: options.model,
|
|
140
|
+
modelId: options.modelId,
|
|
141
|
+
system: readSystem(),
|
|
142
|
+
tools,
|
|
143
|
+
providerOptions: options.providerOptions,
|
|
144
|
+
maxOutputTokens: options.maxOutputTokens,
|
|
145
|
+
contextWindow,
|
|
146
|
+
signal: controller.signal,
|
|
147
|
+
limits: options.compaction,
|
|
148
|
+
onCompacting: () => writer.write({
|
|
149
|
+
type: "data-run-status",
|
|
150
|
+
data: { runId: options.runId, status: "running", label: "Making room so we can keep talking" },
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
messages = compacted.messages;
|
|
154
|
+
if (compacted.compaction !== undefined) {
|
|
155
|
+
writer.write({ type: "data-compaction", id: `compaction-${options.messageId}`, data: compacted.compaction });
|
|
156
|
+
}
|
|
157
|
+
controller.signal.throwIfAborted();
|
|
158
|
+
}
|
|
128
159
|
const generation = streamText({
|
|
129
160
|
model: options.model,
|
|
130
161
|
system: readSystem(),
|
|
131
|
-
messages
|
|
162
|
+
messages,
|
|
132
163
|
tools,
|
|
133
164
|
toolChoice: "auto",
|
|
134
165
|
abortSignal: controller.signal,
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChatRequestOptions, ChatTransport, UIMessageChunk } from "ai";
|
|
2
2
|
import type { SparkRunSettings } from "./server/types.js";
|
|
3
|
-
import type { SparkGoal, SparkGoalCommand
|
|
4
|
-
export type { SparkGoal, SparkGoalCommand, SparkGoalCondition, SparkGoalProposal, SparkGoalTry } from "./goals.js";
|
|
3
|
+
import type { SparkGoal, SparkGoalCommand } from "./goals.js";
|
|
4
|
+
export type { SparkGoal, SparkGoalCommand, SparkGoalCondition, SparkGoalDecision, SparkGoalProposal, SparkGoalTry } from "./goals.js";
|
|
5
5
|
import type { SparkConversation, SparkConversationSummary, SparkRun } from "./server/types.js";
|
|
6
6
|
import type { SparkUIMessage } from "./ui.js";
|
|
7
7
|
export type { SparkConversation, SparkConversationSummary, SparkRun, SparkRunSettings, } from "./server/types.js";
|
|
@@ -44,8 +44,6 @@ export declare class SparkChatTransport implements ChatTransport<SparkUIMessage>
|
|
|
44
44
|
goal?: SparkGoal;
|
|
45
45
|
run?: SparkRun;
|
|
46
46
|
}>;
|
|
47
|
-
/** The success conditions read from a message, to confirm before sending it as a goal. */
|
|
48
|
-
proposeGoal(id: string, message: SparkUIMessage, settings: SparkRunSettings, signal?: AbortSignal): Promise<SparkGoalProposal>;
|
|
49
47
|
getGoal(id: string): Promise<SparkGoal | undefined>;
|
|
50
48
|
getRun(id: string): Promise<SparkRun>;
|
|
51
49
|
saveConversationSettings(id: string, settings: SparkRunSettings): Promise<void>;
|
package/dist/client.js
CHANGED
|
@@ -60,8 +60,6 @@ export class SparkChatTransport {
|
|
|
60
60
|
message,
|
|
61
61
|
messageId: options.messageId,
|
|
62
62
|
settings,
|
|
63
|
-
// A confirmed goal: the message starts it, and the run is its first try.
|
|
64
|
-
...(requestBody?.goal === undefined ? {} : { goal: requestBody.goal }),
|
|
65
63
|
}),
|
|
66
64
|
signal: options.abortSignal,
|
|
67
65
|
}, options.headers);
|
|
@@ -129,15 +127,6 @@ export class SparkChatTransport {
|
|
|
129
127
|
throw await responseError(response);
|
|
130
128
|
return response.json();
|
|
131
129
|
}
|
|
132
|
-
/** The success conditions read from a message, to confirm before sending it as a goal. */
|
|
133
|
-
async proposeGoal(id, message, settings, signal) {
|
|
134
|
-
const response = await this.request(`/conversations/${encodeURIComponent(id)}/goal/proposal`, {
|
|
135
|
-
method: "POST", body: JSON.stringify({ message, settings }), signal,
|
|
136
|
-
});
|
|
137
|
-
if (!response.ok)
|
|
138
|
-
throw await responseError(response);
|
|
139
|
-
return (await response.json()).proposal;
|
|
140
|
-
}
|
|
141
130
|
async getGoal(id) {
|
|
142
131
|
const response = await this.request(`/conversations/${encodeURIComponent(id)}/goal`, { method: "GET" });
|
|
143
132
|
if (!response.ok)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { generateText, type LanguageModel, type ModelMessage, type ToolSet } from "ai";
|
|
2
|
+
import type { SparkSkill } from "./capabilities.js";
|
|
3
|
+
import { type SparkCompaction, type SparkUIMessage } from "./ui.js";
|
|
4
|
+
/** Compact once the conversation reaches this many tokens. */
|
|
5
|
+
export declare const COMPACTION_THRESHOLD_TOKENS = 256000;
|
|
6
|
+
/** Recent whole turns kept verbatim, up to this many tokens, besides the current turn. */
|
|
7
|
+
export declare const COMPACTION_KEEP_RECENT_TOKENS = 16000;
|
|
8
|
+
/** The user's newest images from the summarized messages that the summary message carries. */
|
|
9
|
+
export declare const COMPACTION_USER_IMAGES = 4;
|
|
10
|
+
/**
|
|
11
|
+
* When a conversation is compacted, for a host that exercises compaction
|
|
12
|
+
* without filling a production-sized conversation, such as local development.
|
|
13
|
+
*/
|
|
14
|
+
export type SparkCompactionLimits = {
|
|
15
|
+
/** Default COMPACTION_THRESHOLD_TOKENS. */
|
|
16
|
+
thresholdTokens?: number;
|
|
17
|
+
/** Default COMPACTION_KEEP_RECENT_TOKENS. */
|
|
18
|
+
keepRecentTokens?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare const COMPACTION_PROMPT = "Write a checkpoint summary of this conversation so far. It replaces the earlier messages in your context, so the work must be able to continue from it alone. Reply with the summary only, without calling tools. This request is not part of the conversation: leave it out of the summary.\nIf the conversation begins with an earlier summary, carry its content forward and update it.\nUse these sections:\n## Goal\n## User preferences and constraints\n## Decisions and current scene state (objects with their names and IDs)\n## Done\n## Open and next steps\n## Artifacts and images (each artifact ID, and what each image the user attached shows)\n## Errors and approaches to avoid\nKeep exact names, IDs, numbers and the user's own wording for requirements. Stay under 1,500 words.";
|
|
21
|
+
export declare const COMPACTION_NOTE = "[Earlier messages in this conversation were replaced by the summary below. The scene itself is unchanged; inspect it for its exact state.]";
|
|
22
|
+
/** The latest compaction in the conversation whose kept message is still in it. */
|
|
23
|
+
export declare function latestCompaction(history: SparkUIMessage[]): {
|
|
24
|
+
compaction: SparkCompaction;
|
|
25
|
+
carrier: number;
|
|
26
|
+
firstKept: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* The conversation as the model reads it: the messages before the compaction's
|
|
30
|
+
* kept message replaced by its summary, with the newest COMPACTION_USER_IMAGES
|
|
31
|
+
* images the user attached to them. `compaction` defaults to the latest one in `history`; `skills` are the
|
|
32
|
+
* host's, to send the current text of the skills it names. Without a
|
|
33
|
+
* compaction, `history` is returned as it is.
|
|
34
|
+
*/
|
|
35
|
+
export declare function applyCompaction(history: SparkUIMessage[], options?: {
|
|
36
|
+
compaction?: SparkCompaction;
|
|
37
|
+
skills?: readonly SparkSkill[];
|
|
38
|
+
}): SparkUIMessage[];
|
|
39
|
+
export type CompactSparkHistoryOptions = {
|
|
40
|
+
history: SparkUIMessage[];
|
|
41
|
+
/** `history` as model messages, with `compaction` applied when given. */
|
|
42
|
+
convert(compaction?: SparkCompaction): Promise<ModelMessage[]>;
|
|
43
|
+
model: LanguageModel;
|
|
44
|
+
modelId: string;
|
|
45
|
+
system: string;
|
|
46
|
+
/** The run's tools; the summary request sends their definitions without `execute`. */
|
|
47
|
+
tools: ToolSet;
|
|
48
|
+
providerOptions?: Parameters<typeof generateText>[0]["providerOptions"];
|
|
49
|
+
maxOutputTokens?: number;
|
|
50
|
+
contextWindow?: number;
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
/** Called just before the summary request, which can take a while. */
|
|
53
|
+
onCompacting?(): void;
|
|
54
|
+
limits?: SparkCompactionLimits;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The turn's model messages, compacted first when the conversation has reached
|
|
58
|
+
* the threshold. `compaction` is the new one, for the host to record; it is
|
|
59
|
+
* absent when nothing was compacted, including when the summary failed.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compactSparkHistory(options: CompactSparkHistoryOptions): Promise<{
|
|
62
|
+
messages: ModelMessage[];
|
|
63
|
+
compaction?: SparkCompaction;
|
|
64
|
+
}>;
|