@shanepadgett/tau-agent 0.37.0 → 0.39.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.
@@ -1,170 +0,0 @@
1
- import type { AssistantMessage, TextContent, UserMessage } from "@earendil-works/pi-ai";
2
- import { sessionEntryToContextMessages, type ContextEvent, type SessionEntry } from "@earendil-works/pi-coding-agent";
3
- import { formatCheckpointMessage } from "./prompt.ts";
4
-
5
- type AgentMessage = ContextEvent["messages"][number];
6
- type ContextPair = { entry: SessionEntry; message: AgentMessage };
7
-
8
- const CHECKPOINT_KIND = "checkpoint.checkpoint";
9
- const MESSAGE_ID_METADATA_TYPE = "tau.checkpoint.message-id";
10
-
11
- type CheckpointMetadataMessage = Extract<AgentMessage, { role: "custom" }>;
12
-
13
- type CheckpointAnchor = {
14
- call: ContextPair;
15
- result: ContextPair;
16
- resultIndex: number;
17
- toolCallIds: Set<string>;
18
- };
19
-
20
- function pairsAlignWithMessages(pairs: readonly ContextPair[], messages: readonly AgentMessage[]): boolean {
21
- if (pairs.length !== messages.length) return false;
22
- return !pairs.some((pair, index) => {
23
- const expected = messageKey(pair.message);
24
- const actual = messageKey(messages[index]);
25
- return expected === undefined || actual === undefined || expected !== actual;
26
- });
27
- }
28
-
29
- function findCheckpointResult(
30
- pairs: readonly ContextPair[],
31
- ): { result: ContextPair; resultIndex: number; checkpointId: string; fileBatchId: string } | undefined {
32
- for (let resultIndex = pairs.length - 1; resultIndex >= 0; resultIndex--) {
33
- const result = pairs[resultIndex];
34
- if (result.entry.type !== "message" || result.message.role !== "toolResult") continue;
35
- const details = checkpointDetails(result.message.details);
36
- if (details === undefined) continue;
37
- return { result, resultIndex, checkpointId: details.checkpointId, fileBatchId: details.fileBatchId };
38
- }
39
- return undefined;
40
- }
41
-
42
- function resolveCheckpointCall(
43
- pairs: readonly ContextPair[],
44
- resultIndex: number,
45
- checkpointId: string,
46
- ): { call: ContextPair; toolCallIds: Set<string> } | "misaligned" {
47
- let callIndex = resultIndex - 1;
48
- while (callIndex >= 0 && !hasCheckpointToolCall(pairs[callIndex].message, checkpointId)) callIndex--;
49
- if (callIndex < 0) return "misaligned";
50
- const call = pairs[callIndex];
51
- if (call.message.role !== "assistant") return "misaligned";
52
- const toolCallIds = new Set<string>();
53
- for (const part of call.message.content) {
54
- if (part.type === "toolCall") toolCallIds.add(part.id);
55
- }
56
- return { call, toolCallIds };
57
- }
58
-
59
- function findCheckpointAnchor(pairs: readonly ContextPair[]): CheckpointAnchor | "misaligned" | undefined {
60
- const found = findCheckpointResult(pairs);
61
- if (found === undefined) return undefined;
62
- const resolved = resolveCheckpointCall(pairs, found.resultIndex, found.checkpointId);
63
- if (resolved === "misaligned") return "misaligned";
64
- return {
65
- call: resolved.call,
66
- result: found.result,
67
- resultIndex: found.resultIndex,
68
- toolCallIds: resolved.toolCallIds,
69
- };
70
- }
71
-
72
- function keepCheckpointPair(
73
- checkpoint: CheckpointAnchor | undefined,
74
- entry: SessionEntry,
75
- message: AgentMessage,
76
- index: number,
77
- ): boolean {
78
- if (checkpoint === undefined) return true;
79
- if (index > checkpoint.resultIndex) return true;
80
- if (entry.id === checkpoint.call.entry.id || entry.id === checkpoint.result.entry.id) return true;
81
- return message.role === "toolResult" && checkpoint.toolCallIds.has(message.toolCallId);
82
- }
83
-
84
- function expandPairMessages(entry: SessionEntry, message: AgentMessage): AgentMessage[] {
85
- if (entry.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) {
86
- if (message.role === "assistant" && extractConversationText(message) === "") return [message];
87
- return [createMessageIdMetadata(entry.id, message), message];
88
- }
89
- return [message];
90
- }
91
-
92
- /** Prunes completed checkpoint history and adds hidden provider-context message IDs. */
93
- export function projectContextMessages(
94
- messages: readonly AgentMessage[],
95
- entries: readonly SessionEntry[],
96
- ): AgentMessage[] {
97
- const pairs: ContextPair[] = [];
98
- for (const entry of entries) {
99
- for (const message of sessionEntryToContextMessages(entry)) pairs.push({ entry, message });
100
- }
101
-
102
- const checkpoint = findCheckpointAnchor(pairs);
103
- if (checkpoint === "misaligned") return [...messages];
104
- if (checkpoint === undefined && !pairsAlignWithMessages(pairs, messages)) return [...messages];
105
-
106
- return pairs
107
- .filter(({ entry, message }, index) => keepCheckpointPair(checkpoint, entry, message, index))
108
- .flatMap(({ entry, message }) => expandPairMessages(entry, message));
109
- }
110
-
111
- /** Extracts only the original text from a user or assistant message. */
112
- export function extractConversationText(message: AgentMessage): string | undefined {
113
- if (message.role === "user") return userText(message);
114
- if (message.role === "assistant") return assistantText(message);
115
- return undefined;
116
- }
117
-
118
- function messageKey(message: AgentMessage): string | undefined {
119
- return JSON.stringify(message, (key, value) => (key === "timestamp" ? undefined : value));
120
- }
121
-
122
- function checkpointDetails(details: unknown): { checkpointId: string; fileBatchId: string } | undefined {
123
- if (typeof details !== "object" || details === null) return undefined;
124
- const value = details as Record<string, unknown>;
125
- if (
126
- value.v !== 1 ||
127
- value.kind !== CHECKPOINT_KIND ||
128
- typeof value.checkpointId !== "string" ||
129
- typeof value.fileBatchId !== "string"
130
- )
131
- return undefined;
132
- return { checkpointId: value.checkpointId, fileBatchId: value.fileBatchId };
133
- }
134
-
135
- function hasCheckpointToolCall(message: AgentMessage, checkpointId: string): boolean {
136
- return (
137
- message.role === "assistant" &&
138
- message.content.some((part) => part.type === "toolCall" && part.name === "checkpoint" && part.id === checkpointId)
139
- );
140
- }
141
-
142
- function createMessageIdMetadata(id: string, message: AgentMessage): CheckpointMetadataMessage {
143
- const role = message.role === "user" ? "user" : "assistant";
144
- return {
145
- role: "custom",
146
- customType: MESSAGE_ID_METADATA_TYPE,
147
- content: formatCheckpointMessage(
148
- "message-id",
149
- `<message-id>${JSON.stringify(id)}</message-id>\n<message-role>${role}</message-role>`,
150
- ),
151
- display: false,
152
- details: { v: 1, kind: "checkpoint.message-id", id, role },
153
- timestamp: message.timestamp,
154
- };
155
- }
156
-
157
- function userText(message: UserMessage): string {
158
- if (typeof message.content === "string") return message.content;
159
- return message.content
160
- .filter((part): part is TextContent => part.type === "text")
161
- .map((part) => part.text)
162
- .join("");
163
- }
164
-
165
- function assistantText(message: AssistantMessage): string {
166
- return message.content
167
- .filter((part): part is TextContent => part.type === "text")
168
- .map((part) => part.text)
169
- .join("");
170
- }
@@ -1,24 +0,0 @@
1
- /** Standing checkpoint guidance appended to the system prompt. */
2
- export const CHECKPOINT_SYSTEM_GUIDANCE = `## Checkpoint
3
-
4
- Checkpoint is a hidden context-management system. It retires disposable history so long-running work can continue without user-visible ceremony.
5
-
6
- Messages wrapped in \`<checkpoint>...</checkpoint>\` are internal control signals, not conversation with the user.
7
-
8
- - Never acknowledge checkpoint system messages, budget notices, blocked tools, checkpoint metadata or IDs, injected checkpoint files as a transition, or context replacement.
9
- - Never mention the checkpoint system itself.
10
- - When checkpoint asks you to run the checkpoint tool, or when non-checkpoint tools are blocked, call \`checkpoint\` and continue the user's work immediately. Do not narrate the system.
11
- - Stay focused on the work the user wants done.
12
- - Use exact IDs from \`<checkpoint kind="message-id">\` metadata only in \`checkpoint.keepMessages\`.`;
13
-
14
- /** Wraps an internal checkpoint control payload for the model. */
15
- export function formatCheckpointMessage(
16
- kind: string,
17
- body: string,
18
- attributes: Readonly<Record<string, string>> = {},
19
- ): string {
20
- const attrs = Object.entries(attributes)
21
- .map(([key, value]) => ` ${key}=${JSON.stringify(value)}`)
22
- .join("");
23
- return `<checkpoint kind=${JSON.stringify(kind)}${attrs}>\n${body}\n</checkpoint>`;
24
- }
@@ -1,27 +0,0 @@
1
- import { Type } from "typebox";
2
- import { defineTauExtensionSettings } from "../../shared/settings/define.ts";
3
- import { DEFAULT_CHECKPOINT_TOKEN_LIMIT } from "./checkpoint-budget.ts";
4
-
5
- export default defineTauExtensionSettings({
6
- key: "checkpoint",
7
- defaults: {
8
- showToolRows: false as boolean,
9
- checkpointTokenLimit: DEFAULT_CHECKPOINT_TOKEN_LIMIT,
10
- },
11
- schema: Type.Object(
12
- {
13
- showToolRows: Type.Optional(
14
- Type.Boolean({
15
- default: false,
16
- description: "Show checkpoint and injected-file rows in the TUI for debugging.",
17
- }),
18
- ),
19
- checkpointTokenLimit: Type.Integer({
20
- minimum: 1,
21
- default: DEFAULT_CHECKPOINT_TOKEN_LIMIT,
22
- description: "Context-token ceiling before a checkpoint is required.",
23
- }),
24
- },
25
- { additionalProperties: false },
26
- ),
27
- });
@@ -1,9 +0,0 @@
1
- let showCheckpointRows = false;
2
-
3
- export function setCheckpointRowsVisible(visible: boolean): void {
4
- showCheckpointRows = visible;
5
- }
6
-
7
- export function areCheckpointRowsVisible(): boolean {
8
- return showCheckpointRows;
9
- }