@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 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, model messages, an executor, a capability composition,
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. `transport.proposeGoal(id, message, settings)` returns a
151
- title and the success conditions read from the message, each marked `measurable` or left to the
152
- user's review; it stores and starts nothing. After the user confirms them with a tries cap
153
- (`maxTurns`) and a credit cap, send the message with `body.goal` set to a `set` command carrying
154
- `title`, `conditions`, `maxTurns` and `creditBudget`. The service attaches the confirmed conditions
155
- to that message as a `data-goal` part, evaluates each measurable condition after every try, and
156
- records the result on `goal.conditions`. Send the message without `body.goal` to try once. Hosts
157
- enable proposals with `SparkAgentServiceOptions.proposeGoal`, such as `createSparkGoalProposer`.
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
 
@@ -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
- export type SparkTools = {
28
- [Name in keyof SparkUITools]: Tool<SparkUITools[Name]["input"], SparkUITools[Name]["output"]>;
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 {};
@@ -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 { SparkUIMessageChunk } from "./ui.js";
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: ModelMessage[];
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: options.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, SparkGoalProposal } from "./goals.js";
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
+ }>;