@adecore/agent-contracts 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +20 -0
- package/dist/agent.d.ts +133 -0
- package/dist/agent.js +79 -0
- package/dist/chat.d.ts +5390 -0
- package/dist/chat.js +741 -0
- package/dist/envelope.d.ts +72 -0
- package/dist/envelope.js +40 -0
- package/dist/ids.d.ts +3 -0
- package/dist/ids.js +3 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/model.d.ts +282 -0
- package/dist/model.js +123 -0
- package/dist/port.d.ts +5 -0
- package/dist/port.js +1 -0
- package/dist/protocol.d.ts +4577 -0
- package/dist/protocol.js +64 -0
- package/dist/provider-accounts.d.ts +199 -0
- package/dist/provider-accounts.js +91 -0
- package/dist/task.d.ts +110 -0
- package/dist/task.js +33 -0
- package/dist/text.d.ts +1 -0
- package/dist/text.js +19 -0
- package/dist/usage.d.ts +392 -0
- package/dist/usage.js +165 -0
- package/dist/worktree.d.ts +30 -0
- package/dist/worktree.js +30 -0
- package/package.json +96 -3
- package/src/agent.ts +103 -0
- package/src/chat.ts +924 -0
- package/src/envelope.ts +59 -0
- package/src/ids.ts +5 -0
- package/src/index.ts +12 -0
- package/src/model.ts +147 -0
- package/src/port.ts +11 -0
- package/src/protocol.ts +120 -0
- package/src/provider-accounts.ts +114 -0
- package/src/task.ts +41 -0
- package/src/text.ts +20 -0
- package/src/usage.ts +204 -0
- package/src/worktree.ts +34 -0
package/src/chat.ts
ADDED
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AgentKindSchema, AgentStatusSchema, SuggestedTitleSchema } from './agent.ts';
|
|
3
|
+
import { WorktreeSchema } from './worktree.ts';
|
|
4
|
+
import { ModelSelectionSchema, RuntimeModeSchema } from './model.ts';
|
|
5
|
+
import { ProviderAccountIdSchema } from './provider-accounts.ts';
|
|
6
|
+
|
|
7
|
+
// The client picks the id (its node id), like a terminal session.
|
|
8
|
+
export const ChatIdSchema = z.string().min(1);
|
|
9
|
+
export type ChatId = z.infer<typeof ChatIdSchema>;
|
|
10
|
+
|
|
11
|
+
// The daemon's estimate of what `contextTokens` is made of, scaled to add up to it; `system` is whatever the thread cannot account for.
|
|
12
|
+
export const ChatContextBreakdownSchema = z.object({
|
|
13
|
+
toolOutput: z.number().int().nonnegative(),
|
|
14
|
+
filesRead: z.number().int().nonnegative(),
|
|
15
|
+
conversation: z.number().int().nonnegative(),
|
|
16
|
+
system: z.number().int().nonnegative()
|
|
17
|
+
});
|
|
18
|
+
export type ChatContextBreakdown = z.infer<typeof ChatContextBreakdownSchema>;
|
|
19
|
+
|
|
20
|
+
export const ChatUsageSchema = z.object({
|
|
21
|
+
// Tokens the last request carried, which is what the model saw as its context.
|
|
22
|
+
contextTokens: z.number().int().nonnegative(),
|
|
23
|
+
contextWindow: z.number().int().positive().nullable(),
|
|
24
|
+
costUsd: z.number().nonnegative(),
|
|
25
|
+
turns: z.number().int().nonnegative(),
|
|
26
|
+
// Absent while nothing is in the context yet, and from a daemon that does not estimate.
|
|
27
|
+
breakdown: ChatContextBreakdownSchema.optional()
|
|
28
|
+
});
|
|
29
|
+
export type ChatUsage = z.infer<typeof ChatUsageSchema>;
|
|
30
|
+
|
|
31
|
+
// Base64 for 10 MiB leaves room for the prompt and envelope inside the 16 MiB transport frame.
|
|
32
|
+
export const CHAT_ATTACHMENTS_MAX_BYTES = 10 * 1024 * 1024;
|
|
33
|
+
export const CHAT_ATTACHMENT_MAX_BYTES = CHAT_ATTACHMENTS_MAX_BYTES;
|
|
34
|
+
export const CHAT_ATTACHMENTS_MAX_COUNT = 8;
|
|
35
|
+
const MAX_BASE64_LENGTH = Math.ceil(CHAT_ATTACHMENT_MAX_BYTES / 3) * 4;
|
|
36
|
+
|
|
37
|
+
export function attachmentBytes(data: string): number {
|
|
38
|
+
return Math.floor((data.length * 3) / 4) - (data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isBase64(data: string): boolean {
|
|
42
|
+
const padding = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0;
|
|
43
|
+
return data.length % 4 === 0 && !/[^A-Za-z0-9+/]/.test(data.slice(0, data.length - padding));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const IMAGE_MIME_BY_EXTENSION = new Map([
|
|
47
|
+
['png', 'image/png'],
|
|
48
|
+
['jpg', 'image/jpeg'],
|
|
49
|
+
['jpeg', 'image/jpeg'],
|
|
50
|
+
['webp', 'image/webp'],
|
|
51
|
+
['gif', 'image/gif']
|
|
52
|
+
]);
|
|
53
|
+
const IMAGE_MIME_TYPES = new Set(IMAGE_MIME_BY_EXTENSION.values());
|
|
54
|
+
|
|
55
|
+
// A generic MIME type from Finder may still name a picture; a declared PDF must stay a PDF.
|
|
56
|
+
export function attachmentImageMime({ name, mime }: { name: string; mime: string }): string | null {
|
|
57
|
+
const type = mime.split(';')[0]!.trim().toLowerCase();
|
|
58
|
+
if (IMAGE_MIME_TYPES.has(type)) {
|
|
59
|
+
return type;
|
|
60
|
+
}
|
|
61
|
+
if (type !== '' && type !== 'application/octet-stream' && type !== 'binary/octet-stream') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const dot = name.lastIndexOf('.');
|
|
65
|
+
return dot < 0 ? null : (IMAGE_MIME_BY_EXTENSION.get(name.slice(dot + 1).toLowerCase()) ?? null);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// What the composer hands the daemon: the bytes, plus what the file is called and what it is.
|
|
69
|
+
export const ChatAttachmentUploadSchema = z.object({
|
|
70
|
+
name: z.string().min(1).max(255),
|
|
71
|
+
mime: z.string().min(1).max(255),
|
|
72
|
+
// Base64 without a data-URL prefix.
|
|
73
|
+
data: z
|
|
74
|
+
.string()
|
|
75
|
+
.min(1)
|
|
76
|
+
.max(MAX_BASE64_LENGTH)
|
|
77
|
+
.refine(isBase64, { message: 'Invalid attachment base64' })
|
|
78
|
+
.refine((data) => attachmentBytes(data) <= CHAT_ATTACHMENT_MAX_BYTES, { message: 'Attachment exceeds 10 MiB' })
|
|
79
|
+
});
|
|
80
|
+
export type ChatAttachmentUpload = z.infer<typeof ChatAttachmentUploadSchema>;
|
|
81
|
+
|
|
82
|
+
export const ChatAttachmentUploadsSchema = z
|
|
83
|
+
.array(ChatAttachmentUploadSchema)
|
|
84
|
+
.max(CHAT_ATTACHMENTS_MAX_COUNT)
|
|
85
|
+
.refine((uploads) => uploads.reduce((bytes, upload) => bytes + attachmentBytes(upload.data), 0) <= CHAT_ATTACHMENTS_MAX_BYTES, {
|
|
86
|
+
message: 'Attachments must total at most 10 MiB per message'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// What the thread keeps. The bytes live in the host attachment store, so a thread with
|
|
90
|
+
// a video in it is still a small JSON file.
|
|
91
|
+
export const ChatAttachmentSchema = z.object({
|
|
92
|
+
id: z.string().min(1),
|
|
93
|
+
name: z.string().min(1),
|
|
94
|
+
mime: z.string(),
|
|
95
|
+
size: z.number().int().nonnegative(),
|
|
96
|
+
// Absolute, on the machine the daemon runs on.
|
|
97
|
+
path: z.string()
|
|
98
|
+
});
|
|
99
|
+
export type ChatAttachment = z.infer<typeof ChatAttachmentSchema>;
|
|
100
|
+
|
|
101
|
+
// A message typed while a turn was running; the daemon sends it when that turn settles.
|
|
102
|
+
export const ChatQueuedMessageSchema = z.object({
|
|
103
|
+
id: z.string().min(1),
|
|
104
|
+
// Older saved queues have no reserved turn yet; draining them still mints one.
|
|
105
|
+
turnId: z.string().min(1).optional(),
|
|
106
|
+
text: z.string(),
|
|
107
|
+
mentions: z.array(z.string()).optional(),
|
|
108
|
+
skills: z.array(z.string()).optional(),
|
|
109
|
+
chats: z.array(z.string()).optional(),
|
|
110
|
+
attachments: z.array(ChatAttachmentSchema).optional(),
|
|
111
|
+
createdAt: z.number()
|
|
112
|
+
});
|
|
113
|
+
export type ChatQueuedMessage = z.infer<typeof ChatQueuedMessageSchema>;
|
|
114
|
+
|
|
115
|
+
// A command or a monitor the CLI keeps running beside its turns, until it ends or its process does.
|
|
116
|
+
export const ChatBackgroundTaskSchema = z.object({
|
|
117
|
+
// The CLI's own id for the task, which is what stopping it names.
|
|
118
|
+
id: z.string().min(1),
|
|
119
|
+
kind: z.enum(['shell', 'monitor']),
|
|
120
|
+
description: z.string(),
|
|
121
|
+
command: z.string().nullable(),
|
|
122
|
+
startedAt: z.number()
|
|
123
|
+
});
|
|
124
|
+
export type ChatBackgroundTask = z.infer<typeof ChatBackgroundTaskSchema>;
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* Why a turn stopped short, when its CLI said so in its own stream: the plan's usage limit, or a model
|
|
128
|
+
* too busy to answer. Not a state of its own, since a client that validates `state` knows four.
|
|
129
|
+
*/
|
|
130
|
+
export const ChatTurnLimitSchema = z.object({
|
|
131
|
+
kind: z.enum(['usage', 'overload']),
|
|
132
|
+
// When the limit lifts, in milliseconds since the epoch; absent when the CLI named no time.
|
|
133
|
+
resetsAt: z.number().optional()
|
|
134
|
+
});
|
|
135
|
+
export type ChatTurnLimit = z.infer<typeof ChatTurnLimitSchema>;
|
|
136
|
+
|
|
137
|
+
export const ChatQuestionSchema = z.object({
|
|
138
|
+
id: z.string(),
|
|
139
|
+
header: z.string(),
|
|
140
|
+
question: z.string(),
|
|
141
|
+
choices: z.array(z.object({ label: z.string(), description: z.string() })),
|
|
142
|
+
multiSelect: z.boolean()
|
|
143
|
+
});
|
|
144
|
+
export type ChatQuestion = z.infer<typeof ChatQuestionSchema>;
|
|
145
|
+
|
|
146
|
+
export const ChatRequestKindSchema = z.enum(['approval', 'question']);
|
|
147
|
+
export type ChatRequestKind = z.infer<typeof ChatRequestKindSchema>;
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* Where a request summary stops, in UTF-16 code units and lines. The schema holds no `max` on purpose: one
|
|
151
|
+
* cut a daemon got wrong would refuse every `chat.list` that carried it. Choice labels and question ids stay
|
|
152
|
+
* whole, since an answer is the label itself.
|
|
153
|
+
*/
|
|
154
|
+
export const CHAT_REQUEST_LIMITS = {
|
|
155
|
+
perChat: 8,
|
|
156
|
+
subject: 200,
|
|
157
|
+
description: 300,
|
|
158
|
+
command: 1000,
|
|
159
|
+
commandLines: 12,
|
|
160
|
+
diff: 1500,
|
|
161
|
+
diffLines: 12,
|
|
162
|
+
question: 500,
|
|
163
|
+
header: 100,
|
|
164
|
+
choiceDescription: 200
|
|
165
|
+
} as const;
|
|
166
|
+
|
|
167
|
+
export const ChatRequestApprovalSchema = z.object({
|
|
168
|
+
toolName: z.string(),
|
|
169
|
+
// One line on what the call is about: the file, the command, the address or the pattern; empty when the input names none.
|
|
170
|
+
subject: z.string(),
|
|
171
|
+
description: z.string().nullable(),
|
|
172
|
+
// The file the diff is of, and how many files the change touches when that is more than one.
|
|
173
|
+
path: z.string().optional(),
|
|
174
|
+
files: z.number().int().optional(),
|
|
175
|
+
// The lines the change takes out and puts in, each with its `-` or `+`, without the context around them.
|
|
176
|
+
diff: z.string().optional(),
|
|
177
|
+
command: z.string().optional(),
|
|
178
|
+
// Set when the diff or the command was cut short; the whole call is in the thread.
|
|
179
|
+
truncated: z.boolean().optional(),
|
|
180
|
+
canAllowAlways: z.boolean(),
|
|
181
|
+
allowAlways: z.object({ label: z.string(), description: z.string() }).optional()
|
|
182
|
+
});
|
|
183
|
+
export type ChatRequestApproval = z.infer<typeof ChatRequestApprovalSchema>;
|
|
184
|
+
|
|
185
|
+
/*
|
|
186
|
+
* An approval or a question a chat waits on, cut down to what a card needs to show and answer it with
|
|
187
|
+
* `chat.approve`, `chat.answer` or `chat.dismiss`, so a client need not attach the chat to read it.
|
|
188
|
+
*/
|
|
189
|
+
export const ChatRequestSummarySchema = z.object({
|
|
190
|
+
requestId: z.string(),
|
|
191
|
+
// The thread item, which `chat.dismiss` takes.
|
|
192
|
+
itemId: z.string(),
|
|
193
|
+
kind: ChatRequestKindSchema,
|
|
194
|
+
createdAt: z.number(),
|
|
195
|
+
approval: ChatRequestApprovalSchema.optional(),
|
|
196
|
+
question: z.object({ questions: z.array(ChatQuestionSchema), async: z.boolean().optional() }).optional()
|
|
197
|
+
});
|
|
198
|
+
export type ChatRequestSummary = z.infer<typeof ChatRequestSummarySchema>;
|
|
199
|
+
|
|
200
|
+
export const ChatInfoSchema = z.object({
|
|
201
|
+
chatId: ChatIdSchema,
|
|
202
|
+
provider: AgentKindSchema,
|
|
203
|
+
// The account of the provider the CLI runs under; absent is the provider's default account.
|
|
204
|
+
account: ProviderAccountIdSchema.optional(),
|
|
205
|
+
cwd: z.string(),
|
|
206
|
+
// Set once the CLI announced itself; what a terminal node needs for `--resume`.
|
|
207
|
+
agentSessionId: z.string().nullable(),
|
|
208
|
+
// The model the CLI reported, which can differ from the selection (aliases, reroutes).
|
|
209
|
+
model: z.string().nullable(),
|
|
210
|
+
selection: ModelSelectionSchema,
|
|
211
|
+
runtimeMode: RuntimeModeSchema,
|
|
212
|
+
// The CLI's reported permissions; absent until it confirms them for the current process.
|
|
213
|
+
effectiveRuntimeMode: RuntimeModeSchema.optional(),
|
|
214
|
+
permissionMode: z.string().optional(),
|
|
215
|
+
status: AgentStatusSchema,
|
|
216
|
+
// Whether the CLI process is alive right now. A dead one is started again with `--resume` on the next send.
|
|
217
|
+
running: z.boolean(),
|
|
218
|
+
// The turn in flight, if any; items carry the same id so the client can fold work per turn.
|
|
219
|
+
activeTurnId: z.string().nullable(),
|
|
220
|
+
slashCommands: z.array(z.string()),
|
|
221
|
+
// What the CLI's own init frame says it will run; empty until the first message named them.
|
|
222
|
+
skills: z.array(z.string()).optional(),
|
|
223
|
+
// Messages typed while a turn ran, in the order they go out once it settles.
|
|
224
|
+
queue: z.array(ChatQueuedMessageSchema).optional(),
|
|
225
|
+
queuePaused: z.boolean().optional(),
|
|
226
|
+
background: z.array(ChatBackgroundTaskSchema).optional(),
|
|
227
|
+
// Whether a subagent or workflow of the CLI's own still runs in the background; absent reads as none.
|
|
228
|
+
delegating: z.boolean().optional(),
|
|
229
|
+
usage: ChatUsageSchema,
|
|
230
|
+
// The name the CLI gave the session, when it gives one; a node that nobody named takes it.
|
|
231
|
+
suggestedTitle: SuggestedTitleSchema.optional(),
|
|
232
|
+
// A chat that runs an edit inline in an editor: no list shows it, and a client that lists chats skips it.
|
|
233
|
+
hidden: z.boolean().optional(),
|
|
234
|
+
// The chat this one was forked from and the turn it continues after; absent on a chat nobody forked.
|
|
235
|
+
forkOf: z.object({ chatId: ChatIdSchema, turnId: z.string().min(1), at: z.number() }).optional(),
|
|
236
|
+
// This chat's own switch for being taken up again after a limit; absent follows the machine's `resumeAtReset`.
|
|
237
|
+
resumeAtReset: z.boolean().optional(),
|
|
238
|
+
// The limit the last turn stopped on, until the next turn opens: what a header shows without the thread.
|
|
239
|
+
limit: ChatTurnLimitSchema.optional(),
|
|
240
|
+
// When the daemon takes the chat up again on its own, after the limit its last turn stopped on; absent while nothing is owed.
|
|
241
|
+
resumeAt: z.number().optional(),
|
|
242
|
+
// What the chat waits on a person for, oldest first and at most `CHAT_REQUEST_LIMITS.perChat`; absent while nothing waits.
|
|
243
|
+
requests: z.array(ChatRequestSummarySchema).optional(),
|
|
244
|
+
createdAt: z.number()
|
|
245
|
+
});
|
|
246
|
+
export type ChatInfo = z.infer<typeof ChatInfoSchema>;
|
|
247
|
+
|
|
248
|
+
// Where a skill was found: the person's own folder, the chat's folder, or a plugin.
|
|
249
|
+
export const ChatSkillSourceSchema = z.enum(['user', 'project', 'plugin']);
|
|
250
|
+
export type ChatSkillSource = z.infer<typeof ChatSkillSourceSchema>;
|
|
251
|
+
|
|
252
|
+
export const ChatSkillSchema = z.object({
|
|
253
|
+
name: z.string().min(1),
|
|
254
|
+
description: z.string(),
|
|
255
|
+
source: ChatSkillSourceSchema
|
|
256
|
+
});
|
|
257
|
+
export type ChatSkill = z.infer<typeof ChatSkillSchema>;
|
|
258
|
+
|
|
259
|
+
export const SkillsListPayloadSchema = z.object({ chatId: ChatIdSchema });
|
|
260
|
+
export type SkillsListPayload = z.infer<typeof SkillsListPayloadSchema>;
|
|
261
|
+
|
|
262
|
+
export const SkillsListResultSchema = z.object({ skills: z.array(ChatSkillSchema) });
|
|
263
|
+
export type SkillsListResult = z.infer<typeof SkillsListResultSchema>;
|
|
264
|
+
|
|
265
|
+
const base = {
|
|
266
|
+
id: z.string().min(1),
|
|
267
|
+
createdAt: z.number(),
|
|
268
|
+
turnId: z.string().nullable()
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const ChatUserItemSchema = z.object({
|
|
272
|
+
...base,
|
|
273
|
+
kind: z.literal('user'),
|
|
274
|
+
text: z.string(),
|
|
275
|
+
// Files the person picked with `@`; the paths also sit in the text, this is what the row highlights.
|
|
276
|
+
mentions: z.array(z.string()).optional(),
|
|
277
|
+
// Skills the person picked with `$`; the names also sit in the text, this is what the row chips.
|
|
278
|
+
skills: z.array(z.string()).optional(),
|
|
279
|
+
// Chats of the same project the person picked with `@`. Only their ids travel; the agent reads them itself.
|
|
280
|
+
chats: z.array(z.string()).optional(),
|
|
281
|
+
attachments: z.array(ChatAttachmentSchema).optional()
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
export const ChatAssistantItemSchema = z.object({
|
|
285
|
+
...base,
|
|
286
|
+
kind: z.literal('assistant'),
|
|
287
|
+
text: z.string(),
|
|
288
|
+
streaming: z.boolean(),
|
|
289
|
+
// Set for text a subagent wrote, with the id of the Agent call that spawned it.
|
|
290
|
+
parentToolUseId: z.string().nullable().optional()
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// One stretch of the model thinking out loud before it answers: Claude's thinking blocks, Codex's
|
|
294
|
+
// reasoning summaries. Consecutive blocks are one item, so the timeline has one row per stretch.
|
|
295
|
+
export const ChatThinkingItemSchema = z.object({
|
|
296
|
+
...base,
|
|
297
|
+
kind: z.literal('thinking'),
|
|
298
|
+
text: z.string(),
|
|
299
|
+
streaming: z.boolean(),
|
|
300
|
+
// When the stretch ended, so the row can say how long it took; null while it is still running.
|
|
301
|
+
endedAt: z.number().nullable()
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export const ChatToolStateSchema = z.enum(['running', 'done', 'error']);
|
|
305
|
+
export type ChatToolState = z.infer<typeof ChatToolStateSchema>;
|
|
306
|
+
|
|
307
|
+
// What is known about a tool call while it runs; absent until the CLI reports something.
|
|
308
|
+
export const ChatToolProgressSchema = z.object({
|
|
309
|
+
// Derived from the CLI's `elapsed_time_seconds`, so the client can count on from here; null when only the description came.
|
|
310
|
+
startedAt: z.number().nullable(),
|
|
311
|
+
// What the CLI says the call is doing (Claude Code's `task_started` frame), when it said so.
|
|
312
|
+
description: z.string().nullable(),
|
|
313
|
+
// Output seen so far, for a provider that streams it; the tool's `output` replaces it when the call settles.
|
|
314
|
+
output: z.string().nullable()
|
|
315
|
+
});
|
|
316
|
+
export type ChatToolProgress = z.infer<typeof ChatToolProgressSchema>;
|
|
317
|
+
|
|
318
|
+
// One file a tool call changed, as the CLI reports it; `diff` is unified text for a provider that
|
|
319
|
+
// gives one and empty for a provider whose edits only carry the text before and after.
|
|
320
|
+
export const ChatFileChangeSchema = z.object({
|
|
321
|
+
path: z.string(),
|
|
322
|
+
kind: z.enum(['add', 'update', 'delete']),
|
|
323
|
+
diff: z.string()
|
|
324
|
+
});
|
|
325
|
+
export type ChatFileChange = z.infer<typeof ChatFileChangeSchema>;
|
|
326
|
+
|
|
327
|
+
export const ChatSubagentStatusSchema = z.enum(['running', 'done', 'failed']);
|
|
328
|
+
export type ChatSubagentStatus = z.infer<typeof ChatSubagentStatusSchema>;
|
|
329
|
+
|
|
330
|
+
export const ChatWorkflowPhaseSchema = z.object({
|
|
331
|
+
index: z.number().int(),
|
|
332
|
+
title: z.string()
|
|
333
|
+
});
|
|
334
|
+
export type ChatWorkflowPhase = z.infer<typeof ChatWorkflowPhaseSchema>;
|
|
335
|
+
|
|
336
|
+
// One agent a Claude workflow started, as its latest progress report has it.
|
|
337
|
+
export const ChatWorkflowAgentSchema = z.object({
|
|
338
|
+
// Its place in the order the script started agents, which is what the CLI keys it on.
|
|
339
|
+
index: z.number().int(),
|
|
340
|
+
label: z.string(),
|
|
341
|
+
// The phase it runs in; null for an agent the script started outside any phase.
|
|
342
|
+
phaseIndex: z.number().int().nullable(),
|
|
343
|
+
// Names its transcript beside the session; null while it still waits for its turn to start.
|
|
344
|
+
agentId: z.string().nullable(),
|
|
345
|
+
status: ChatSubagentStatusSchema,
|
|
346
|
+
startedAt: z.number().nullable(),
|
|
347
|
+
durationMs: z.number().int().nonnegative().nullable(),
|
|
348
|
+
lastTool: z.string().nullable()
|
|
349
|
+
});
|
|
350
|
+
export type ChatWorkflowAgent = z.infer<typeof ChatWorkflowAgentSchema>;
|
|
351
|
+
|
|
352
|
+
// What a Workflow call runs: every phase the script announced, and the agents it started so far.
|
|
353
|
+
export const ChatWorkflowSchema = z.object({
|
|
354
|
+
name: z.string().nullable(),
|
|
355
|
+
taskId: z.string().optional(),
|
|
356
|
+
phases: z.array(ChatWorkflowPhaseSchema),
|
|
357
|
+
agents: z.array(ChatWorkflowAgentSchema),
|
|
358
|
+
lastProgressAt: z.number().optional(),
|
|
359
|
+
stalledAt: z.number().optional()
|
|
360
|
+
});
|
|
361
|
+
export type ChatWorkflow = z.infer<typeof ChatWorkflowSchema>;
|
|
362
|
+
|
|
363
|
+
// A workflow's agent has no call of its own, so `chat.subagent` names it by its agent id under this prefix.
|
|
364
|
+
const WORKFLOW_AGENT_REF = 'workflow-agent:';
|
|
365
|
+
|
|
366
|
+
export function workflowAgentRef(agentId: string): string {
|
|
367
|
+
return `${WORKFLOW_AGENT_REF}${agentId}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function workflowAgentIdOf(ref: string): string | null {
|
|
371
|
+
return ref.startsWith(WORKFLOW_AGENT_REF) ? ref.slice(WORKFLOW_AGENT_REF.length) || null : null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export const ChatToolItemSchema = z.object({
|
|
375
|
+
...base,
|
|
376
|
+
kind: z.literal('tool'),
|
|
377
|
+
toolUseId: z.string(),
|
|
378
|
+
name: z.string(),
|
|
379
|
+
input: z.unknown(),
|
|
380
|
+
output: z.string().nullable(),
|
|
381
|
+
state: ChatToolStateSchema,
|
|
382
|
+
// Set for a tool call made by a subagent, with the id of the Task call that spawned it.
|
|
383
|
+
parentToolUseId: z.string().nullable(),
|
|
384
|
+
progress: ChatToolProgressSchema.optional(),
|
|
385
|
+
changes: z.array(ChatFileChangeSchema).optional(),
|
|
386
|
+
// Set on a Workflow call once the CLI reports what the workflow runs, replaced whole by every report.
|
|
387
|
+
workflow: ChatWorkflowSchema.optional()
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
export const ChatSubagentUsageSchema = z.object({
|
|
391
|
+
totalTokens: z.number().int().nonnegative(),
|
|
392
|
+
toolUses: z.number().int().nonnegative(),
|
|
393
|
+
durationMs: z.number().int().nonnegative()
|
|
394
|
+
});
|
|
395
|
+
export type ChatSubagentUsage = z.infer<typeof ChatSubagentUsageSchema>;
|
|
396
|
+
|
|
397
|
+
/*
|
|
398
|
+
* One agent the agent delegated to, foreground or background. Its own work stays in the thread as
|
|
399
|
+
* ordinary items that carry `parentToolUseId`, so streaming keeps working; the timeline gathers
|
|
400
|
+
* them under this row. `result` is the report it ended with, as markdown.
|
|
401
|
+
*/
|
|
402
|
+
export const ChatSubagentItemSchema = z.object({
|
|
403
|
+
...base,
|
|
404
|
+
kind: z.literal('subagent'),
|
|
405
|
+
// The id of the Agent call that spawned it, which is what every later frame about it names.
|
|
406
|
+
toolUseId: z.string(),
|
|
407
|
+
description: z.string(),
|
|
408
|
+
subagentType: z.string().nullable(),
|
|
409
|
+
// The model it runs on: the one its call asked for, until the agent's own first answer names it.
|
|
410
|
+
model: z.string().optional(),
|
|
411
|
+
prompt: z.string().nullable(),
|
|
412
|
+
// Whether it runs beside the turn instead of blocking it, so the turn can end before it does.
|
|
413
|
+
background: z.boolean(),
|
|
414
|
+
status: ChatSubagentStatusSchema,
|
|
415
|
+
startedAt: z.number(),
|
|
416
|
+
finishedAt: z.number().nullable(),
|
|
417
|
+
// What the CLI says it is doing while it runs, and what it says came of it once it settled.
|
|
418
|
+
summary: z.string().nullable(),
|
|
419
|
+
result: z.string().nullable(),
|
|
420
|
+
usage: ChatSubagentUsageSchema.nullable(),
|
|
421
|
+
// The tool it reached for last, for the line while it is still running.
|
|
422
|
+
lastTool: z.string().nullable(),
|
|
423
|
+
// The CLI's own transcript of the run, when it wrote one.
|
|
424
|
+
outputFile: z.string().optional(),
|
|
425
|
+
// Set when it did more than the thread keeps; what is there is the beginning of its work.
|
|
426
|
+
itemsTruncated: z.boolean(),
|
|
427
|
+
// Where the CLI keeps this subagent's own conversation; set once the daemon found it.
|
|
428
|
+
native: z.object({ agentId: z.string().optional(), threadId: z.string().optional() }).optional(),
|
|
429
|
+
// Who opened it: the CLI with its own tool, or a verb with `--task` that made a node; absent is `native`.
|
|
430
|
+
origin: z.enum(['native', 'ruimte']).optional(),
|
|
431
|
+
// The node a `--task` opened, whose own conversation this row stands for.
|
|
432
|
+
childId: z.string().optional(),
|
|
433
|
+
// Set for an agent a subagent opened, with the id of that subagent's own Agent call; its row hangs under that one.
|
|
434
|
+
parentToolUseId: z.string().optional()
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
export const ChatApprovalDecisionSchema = z.enum(['pending', 'allow', 'allow-always', 'deny', 'cancelled']);
|
|
438
|
+
export type ChatApprovalDecision = z.infer<typeof ChatApprovalDecisionSchema>;
|
|
439
|
+
|
|
440
|
+
export const ChatApprovalItemSchema = z.object({
|
|
441
|
+
...base,
|
|
442
|
+
kind: z.literal('approval'),
|
|
443
|
+
requestId: z.string(),
|
|
444
|
+
toolUseId: z.string().nullable(),
|
|
445
|
+
toolName: z.string(),
|
|
446
|
+
input: z.unknown(),
|
|
447
|
+
description: z.string().nullable(),
|
|
448
|
+
// Whether the CLI offered a rule that would let this pass next time.
|
|
449
|
+
canAllowAlways: z.boolean(),
|
|
450
|
+
allowAlways: z.object({ label: z.string(), description: z.string() }).optional(),
|
|
451
|
+
decision: ChatApprovalDecisionSchema
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
export const ChatQuestionItemSchema = z.object({
|
|
455
|
+
...base,
|
|
456
|
+
kind: z.literal('question'),
|
|
457
|
+
requestId: z.string(),
|
|
458
|
+
questions: z.array(ChatQuestionSchema).min(1),
|
|
459
|
+
// Set when the CLI goes on while it waits, which is the only kind that may be dismissed.
|
|
460
|
+
async: z.boolean().optional(),
|
|
461
|
+
// Keyed by question id; null while the person has not answered.
|
|
462
|
+
answers: z.record(z.string(), z.string()).nullable(),
|
|
463
|
+
state: z.enum(['pending', 'answered', 'cancelled', 'dismissed'])
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// One file of a turn's checkpoint diff: the working tree against the tree the turn started from.
|
|
467
|
+
export const ChatCheckpointFileSchema = z.object({
|
|
468
|
+
path: z.string(),
|
|
469
|
+
kind: z.enum(['add', 'update', 'delete']),
|
|
470
|
+
added: z.number().int().nonnegative(),
|
|
471
|
+
deleted: z.number().int().nonnegative(),
|
|
472
|
+
// The unified diff of this file; empty when `omitted` says why there is none.
|
|
473
|
+
diff: z.string(),
|
|
474
|
+
omitted: z.enum(['binary', 'too-large']).optional()
|
|
475
|
+
});
|
|
476
|
+
export type ChatCheckpointFile = z.infer<typeof ChatCheckpointFileSchema>;
|
|
477
|
+
|
|
478
|
+
export const ChatCheckpointDiffSchema = z.object({
|
|
479
|
+
files: z.array(ChatCheckpointFileSchema),
|
|
480
|
+
// Set when more files changed than the list carries.
|
|
481
|
+
truncated: z.boolean()
|
|
482
|
+
});
|
|
483
|
+
export type ChatCheckpointDiff = z.infer<typeof ChatCheckpointDiffSchema>;
|
|
484
|
+
|
|
485
|
+
export const ChatTurnItemSchema = z.object({
|
|
486
|
+
...base,
|
|
487
|
+
kind: z.literal('turn'),
|
|
488
|
+
state: z.enum(['running', 'done', 'aborted', 'error']),
|
|
489
|
+
// Who started the turn. Absent means the person did, which is what every turn written before this field was.
|
|
490
|
+
origin: z.enum(['user', 'agent']).optional(),
|
|
491
|
+
// What the CLI woke up about (the summary of a background task that settled); only an agent turn has one.
|
|
492
|
+
label: z.string().optional(),
|
|
493
|
+
// The Agent call the CLI woke up about, so the header can point at the subagent row it belongs to.
|
|
494
|
+
taskToolUseId: z.string().optional(),
|
|
495
|
+
endedAt: z.number().nullable(),
|
|
496
|
+
costUsd: z.number().nonnegative(),
|
|
497
|
+
// The git tree of the chat's folder when the turn started; absent outside a repository.
|
|
498
|
+
checkpoint: z.string().optional(),
|
|
499
|
+
// What the working tree holds against that checkpoint, taken when the turn settled.
|
|
500
|
+
checkpointDiff: ChatCheckpointDiffSchema.optional(),
|
|
501
|
+
// How many CLI processes worked on this turn; absent is one, which is every turn before this field.
|
|
502
|
+
attempt: z.number().int().positive().optional(),
|
|
503
|
+
// A written resume attempt whose CLI has not accepted its prompt yet; retries reuse it.
|
|
504
|
+
resumePending: z.boolean().optional(),
|
|
505
|
+
// Kept until the CLI acknowledges the wake prompt, so a saved turn cannot consume an unsent result.
|
|
506
|
+
deliveryPending: z.boolean().optional(),
|
|
507
|
+
// The tasks whose results woke the chat for this turn; only a turn the daemon opened carries them.
|
|
508
|
+
taskIds: z.array(z.string()).optional(),
|
|
509
|
+
// The nodes whose messages woke the chat for this turn, which is where waking on a message stops: a turn with one wakes nobody.
|
|
510
|
+
messageFrom: z.array(z.string()).optional(),
|
|
511
|
+
// The CLI's own name for where this turn ended, which is what a fork after this turn is cut at.
|
|
512
|
+
native: z.object({ turnId: z.string().optional(), lastUuid: z.string().optional() }).optional(),
|
|
513
|
+
// The git tree of the chat's folder when the turn settled: what a fork after this turn starts its files from.
|
|
514
|
+
checkpointAfter: z.string().optional(),
|
|
515
|
+
// Set on the turn a fork writes a summary in: the chat it is for, which gets the last answer of the turn.
|
|
516
|
+
summaryFor: ChatIdSchema.optional(),
|
|
517
|
+
// Set on a turn that ended in an error because of a limit rather than a mistake.
|
|
518
|
+
limit: ChatTurnLimitSchema.optional()
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
/* What a turn a restart could not take up again ends with, so a client can tell it from a turn a person stopped. */
|
|
522
|
+
const NOT_RESUMED_PREFIX = 'This turn could not be resumed after the machine restarted: ';
|
|
523
|
+
|
|
524
|
+
export function notResumedNote(reason: string): string {
|
|
525
|
+
return `${NOT_RESUMED_PREFIX}${reason}`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* Whether the machine ended this aborted turn rather than a person: the daemon leaves its warning note in the turn. */
|
|
529
|
+
export function abortedByMachine(
|
|
530
|
+
turn: { id: string; state: string },
|
|
531
|
+
items: readonly { kind: string; turnId: string | null; level?: string; text?: string }[]
|
|
532
|
+
): boolean {
|
|
533
|
+
return (
|
|
534
|
+
turn.state === 'aborted' &&
|
|
535
|
+
items.some((item) => item.kind === 'note' && item.turnId === turn.id && item.level === 'warning' && item.text?.startsWith(NOT_RESUMED_PREFIX) === true)
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export const ChatNoteItemSchema = z.object({
|
|
540
|
+
...base,
|
|
541
|
+
kind: z.literal('note'),
|
|
542
|
+
level: z.enum(['info', 'warning', 'error']),
|
|
543
|
+
text: z.string(),
|
|
544
|
+
// The chat a delivered summary came from, so a client can offer to open it.
|
|
545
|
+
from: ChatIdSchema.optional()
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
export const ChatCompactionItemSchema = z.object({
|
|
549
|
+
...base,
|
|
550
|
+
kind: z.literal('compaction'),
|
|
551
|
+
preTokens: z.number().int().nonnegative().nullable()
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
/*
|
|
555
|
+
* Never a new member here, and never a new value in an enum a chat or a push already carries: the
|
|
556
|
+
* iPhone app validates `chat.attach` and `chat.history` whole, so one item it does not know rejects
|
|
557
|
+
* the entire conversation. Add optional fields instead.
|
|
558
|
+
*/
|
|
559
|
+
export const ChatItemSchema = z.discriminatedUnion('kind', [
|
|
560
|
+
ChatUserItemSchema,
|
|
561
|
+
ChatAssistantItemSchema,
|
|
562
|
+
ChatThinkingItemSchema,
|
|
563
|
+
ChatToolItemSchema,
|
|
564
|
+
ChatSubagentItemSchema,
|
|
565
|
+
ChatApprovalItemSchema,
|
|
566
|
+
ChatQuestionItemSchema,
|
|
567
|
+
ChatTurnItemSchema,
|
|
568
|
+
ChatNoteItemSchema,
|
|
569
|
+
ChatCompactionItemSchema
|
|
570
|
+
]);
|
|
571
|
+
export type ChatItem = z.infer<typeof ChatItemSchema>;
|
|
572
|
+
export type ChatUserItem = z.infer<typeof ChatUserItemSchema>;
|
|
573
|
+
export type ChatAssistantItem = z.infer<typeof ChatAssistantItemSchema>;
|
|
574
|
+
export type ChatThinkingItem = z.infer<typeof ChatThinkingItemSchema>;
|
|
575
|
+
export type ChatToolItem = z.infer<typeof ChatToolItemSchema>;
|
|
576
|
+
export type ChatSubagentItem = z.infer<typeof ChatSubagentItemSchema>;
|
|
577
|
+
export type ChatApprovalItem = z.infer<typeof ChatApprovalItemSchema>;
|
|
578
|
+
export type ChatQuestionItem = z.infer<typeof ChatQuestionItemSchema>;
|
|
579
|
+
export type ChatTurnItem = z.infer<typeof ChatTurnItemSchema>;
|
|
580
|
+
export type ChatNoteItem = z.infer<typeof ChatNoteItemSchema>;
|
|
581
|
+
export type ChatCompactionItem = z.infer<typeof ChatCompactionItemSchema>;
|
|
582
|
+
|
|
583
|
+
// Events apply after the `chat.attach` snapshot; item ids make upserts and streamed deltas replayable.
|
|
584
|
+
export const ChatEventSchema = z.discriminatedUnion('type', [
|
|
585
|
+
z.object({ type: z.literal('item'), item: ChatItemSchema, historyIndex: z.number().int().nonnegative().optional() }),
|
|
586
|
+
z.object({ type: z.literal('delta'), itemId: z.string(), text: z.string() }),
|
|
587
|
+
z.object({ type: z.literal('info'), info: ChatInfoSchema }),
|
|
588
|
+
z.object({ type: z.literal('reset'), info: ChatInfoSchema, items: z.array(ChatItemSchema) })
|
|
589
|
+
]);
|
|
590
|
+
export type ChatEvent = z.infer<typeof ChatEventSchema>;
|
|
591
|
+
|
|
592
|
+
export const ChatEventEnvelopeSchema = z.object({
|
|
593
|
+
chatId: ChatIdSchema,
|
|
594
|
+
event: ChatEventSchema,
|
|
595
|
+
// The place of this event in the chat's stream, for `since` on the next attach.
|
|
596
|
+
seq: z.number().int().positive().optional()
|
|
597
|
+
});
|
|
598
|
+
export type ChatEventEnvelope = z.infer<typeof ChatEventEnvelopeSchema>;
|
|
599
|
+
|
|
600
|
+
export const ChatCreatePayloadSchema = z.object({
|
|
601
|
+
chatId: ChatIdSchema,
|
|
602
|
+
provider: AgentKindSchema.optional(),
|
|
603
|
+
account: ProviderAccountIdSchema.optional(),
|
|
604
|
+
cwd: z.string().optional(),
|
|
605
|
+
// A CLI session to continue, for a chat opened from a terminal that ran the agent.
|
|
606
|
+
resume: z.string().optional(),
|
|
607
|
+
selection: ModelSelectionSchema.optional(),
|
|
608
|
+
runtimeMode: RuntimeModeSchema.optional()
|
|
609
|
+
});
|
|
610
|
+
export type ChatCreatePayload = z.infer<typeof ChatCreatePayloadSchema>;
|
|
611
|
+
|
|
612
|
+
export const ChatConfigurePayloadSchema = z.object({
|
|
613
|
+
chatId: ChatIdSchema,
|
|
614
|
+
// Only an account that can continue this chat's conversation: the same provider and the same transcript folder.
|
|
615
|
+
account: ProviderAccountIdSchema.optional(),
|
|
616
|
+
selection: ModelSelectionSchema.optional(),
|
|
617
|
+
runtimeMode: RuntimeModeSchema.optional(),
|
|
618
|
+
resumeAtReset: z.boolean().optional()
|
|
619
|
+
});
|
|
620
|
+
export type ChatConfigurePayload = z.infer<typeof ChatConfigurePayloadSchema>;
|
|
621
|
+
|
|
622
|
+
/*
|
|
623
|
+
* The composer preference of this client, for a chat the daemon starts with no client mounting it.
|
|
624
|
+
* One socket's answer, dropped with it. Among the clients connected the newest `changedAt` wins, so
|
|
625
|
+
* a client that reconnects with an older pick does not override a fresher one made elsewhere.
|
|
626
|
+
*/
|
|
627
|
+
export const ChatPreferencesPayloadSchema = z.object({
|
|
628
|
+
runtimeMode: RuntimeModeSchema.optional(),
|
|
629
|
+
// The mode a terminal agent node starts in, for the terminals the daemon starts on its own.
|
|
630
|
+
terminalRuntimeMode: RuntimeModeSchema.optional(),
|
|
631
|
+
// Per provider, because a model slug only means something in its own CLI's catalog.
|
|
632
|
+
selections: z.partialRecord(AgentKindSchema, ModelSelectionSchema).optional(),
|
|
633
|
+
// The account last picked per provider.
|
|
634
|
+
accounts: z.partialRecord(AgentKindSchema, ProviderAccountIdSchema).optional(),
|
|
635
|
+
// When the person last changed it, in milliseconds since the epoch; absent is older than any pick.
|
|
636
|
+
changedAt: z.number().nonnegative().optional()
|
|
637
|
+
});
|
|
638
|
+
export type ChatPreferencesPayload = z.infer<typeof ChatPreferencesPayloadSchema>;
|
|
639
|
+
|
|
640
|
+
export const ChatTargetPayloadSchema = z.object({ chatId: ChatIdSchema });
|
|
641
|
+
export type ChatTargetPayload = z.infer<typeof ChatTargetPayloadSchema>;
|
|
642
|
+
|
|
643
|
+
// Without `force` a chat in the middle of a turn is refused, so a person is asked before that turn is thrown away.
|
|
644
|
+
export const ChatClearPayloadSchema = ChatTargetPayloadSchema.extend({ force: z.boolean().optional() });
|
|
645
|
+
export type ChatClearPayload = z.infer<typeof ChatClearPayloadSchema>;
|
|
646
|
+
|
|
647
|
+
// With `subagents` the stop also ends every agent the chat opened and marks its CLI's own subagents stopped; the chat stays.
|
|
648
|
+
export const ChatCancelPayloadSchema = ChatTargetPayloadSchema.extend({ subagents: z.boolean().optional() });
|
|
649
|
+
export type ChatCancelPayload = z.infer<typeof ChatCancelPayloadSchema>;
|
|
650
|
+
|
|
651
|
+
export const ChatAttachPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
652
|
+
historyLimit: z.number().int().min(1).max(100).optional(),
|
|
653
|
+
// The last seq this client saw; honored when the daemon still holds everything after it.
|
|
654
|
+
since: z.number().int().nonnegative().optional()
|
|
655
|
+
});
|
|
656
|
+
export const ChatHistoryPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
657
|
+
cursor: z.string().min(1).max(128),
|
|
658
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
659
|
+
});
|
|
660
|
+
export const ChatHistoryPageSchema = z.object({
|
|
661
|
+
start: z.number().int().nonnegative(),
|
|
662
|
+
cursor: z.string().nullable()
|
|
663
|
+
});
|
|
664
|
+
export const ChatHistoryResultSchema = z.object({ items: z.array(ChatItemSchema), history: ChatHistoryPageSchema });
|
|
665
|
+
export type ChatHistoryResult = z.infer<typeof ChatHistoryResultSchema>;
|
|
666
|
+
|
|
667
|
+
export const ChatSubagentPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
668
|
+
// The call that spawned it: a row of the chat's own thread, or of a subagent's conversation; a workflow's agent by `workflowAgentRef`.
|
|
669
|
+
toolUseId: z.string().min(1).max(256),
|
|
670
|
+
cursor: z.string().min(1).max(256).optional(),
|
|
671
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
672
|
+
// True keeps this client told while the conversation grows, false lets go; absent leaves it as it was.
|
|
673
|
+
watch: z.boolean().optional()
|
|
674
|
+
});
|
|
675
|
+
export type ChatSubagentPayload = z.infer<typeof ChatSubagentPayloadSchema>;
|
|
676
|
+
|
|
677
|
+
export const ChatStopSubagentPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
678
|
+
// A running row of the chat's own thread: a task's node is stopped, a subagent of the CLI's own only marked.
|
|
679
|
+
toolUseId: z.string().min(1).max(256)
|
|
680
|
+
});
|
|
681
|
+
export type ChatStopSubagentPayload = z.infer<typeof ChatStopSubagentPayloadSchema>;
|
|
682
|
+
|
|
683
|
+
export const ChatStopTaskPayloadSchema = ChatTargetPayloadSchema.extend({ taskId: z.string().min(1).max(256) });
|
|
684
|
+
export type ChatStopTaskPayload = z.infer<typeof ChatStopTaskPayloadSchema>;
|
|
685
|
+
|
|
686
|
+
// `start` is the place in the conversation for a source that numbers it; a Codex thread pages by its own cursor only.
|
|
687
|
+
export const ChatSubagentPageSchema = z.object({
|
|
688
|
+
start: z.number().int().nonnegative().optional(),
|
|
689
|
+
cursor: z.string().nullable()
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
export const ChatSubagentSourceSchema = z.enum(['claude-transcript', 'codex-thread']);
|
|
693
|
+
export type ChatSubagentSource = z.infer<typeof ChatSubagentSourceSchema>;
|
|
694
|
+
|
|
695
|
+
export const ChatSubagentResultSchema = z.object({
|
|
696
|
+
items: z.array(ChatItemSchema),
|
|
697
|
+
history: ChatSubagentPageSchema,
|
|
698
|
+
source: ChatSubagentSourceSchema,
|
|
699
|
+
context: z.object({ provider: AgentKindSchema, cwd: z.string(), chatId: ChatIdSchema.optional() }).optional(),
|
|
700
|
+
// Whether the subagent is still writing, so a client knows to keep reading.
|
|
701
|
+
live: z.boolean()
|
|
702
|
+
});
|
|
703
|
+
export type ChatSubagentResult = z.infer<typeof ChatSubagentResultSchema>;
|
|
704
|
+
|
|
705
|
+
/*
|
|
706
|
+
* The status of one chat, sent to every client on this machine rather than only to the ones
|
|
707
|
+
* attached to it. A thread's events are only worth streaming to whoever reads them, but what a chat
|
|
708
|
+
* is doing belongs to the whole project: a node waiting on a person has to say so on a view nobody
|
|
709
|
+
* has open. Terminals have said this all along through `session.status`.
|
|
710
|
+
*/
|
|
711
|
+
export const ChatStatusEventSchema = z.object({ chatId: ChatIdSchema, info: ChatInfoSchema });
|
|
712
|
+
export type ChatStatusEvent = z.infer<typeof ChatStatusEventSchema>;
|
|
713
|
+
|
|
714
|
+
// Carries nothing of the conversation: a client that holds it asks for the newest page again.
|
|
715
|
+
export const ChatSubagentChangedEventSchema = z.object({ chatId: ChatIdSchema, toolUseId: z.string() });
|
|
716
|
+
export type ChatSubagentChangedEvent = z.infer<typeof ChatSubagentChangedEventSchema>;
|
|
717
|
+
|
|
718
|
+
export const CHAT_BOOKMARK_LIMITS = {
|
|
719
|
+
name: 120,
|
|
720
|
+
excerpt: 160,
|
|
721
|
+
perChat: 200
|
|
722
|
+
} as const;
|
|
723
|
+
|
|
724
|
+
/*
|
|
725
|
+
* A message a person marked to come back to. It hangs on the item's id, never on a place in the
|
|
726
|
+
* thread, and lives beside the chat in the host data directory, so every client of the chat sees the same.
|
|
727
|
+
*/
|
|
728
|
+
export const ChatBookmarkSchema = z.object({
|
|
729
|
+
itemId: z.string().min(1),
|
|
730
|
+
// Absent while nobody named it; a list shows the excerpt instead.
|
|
731
|
+
name: z.string().max(CHAT_BOOKMARK_LIMITS.name).optional(),
|
|
732
|
+
// The start of the message when it was marked, so a list needs no thread to say what it points at.
|
|
733
|
+
excerpt: z.string().max(CHAT_BOOKMARK_LIMITS.excerpt),
|
|
734
|
+
createdAt: z.number()
|
|
735
|
+
});
|
|
736
|
+
export type ChatBookmark = z.infer<typeof ChatBookmarkSchema>;
|
|
737
|
+
|
|
738
|
+
export const ChatBookmarksSchema = z.array(ChatBookmarkSchema);
|
|
739
|
+
|
|
740
|
+
// Marking a message that already has a bookmark keeps it, and names it when a name comes along.
|
|
741
|
+
export const ChatAddBookmarkPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
742
|
+
itemId: z.string().min(1),
|
|
743
|
+
name: z.string().max(CHAT_BOOKMARK_LIMITS.name).optional()
|
|
744
|
+
});
|
|
745
|
+
export type ChatAddBookmarkPayload = z.infer<typeof ChatAddBookmarkPayloadSchema>;
|
|
746
|
+
|
|
747
|
+
// An empty name takes the name away.
|
|
748
|
+
export const ChatRenameBookmarkPayloadSchema = ChatTargetPayloadSchema.extend({
|
|
749
|
+
itemId: z.string().min(1),
|
|
750
|
+
name: z.string().max(CHAT_BOOKMARK_LIMITS.name)
|
|
751
|
+
});
|
|
752
|
+
export type ChatRenameBookmarkPayload = z.infer<typeof ChatRenameBookmarkPayloadSchema>;
|
|
753
|
+
|
|
754
|
+
// A bookmark that is already gone is no refusal: another client took it away first.
|
|
755
|
+
export const ChatRemoveBookmarkPayloadSchema = ChatTargetPayloadSchema.extend({ itemId: z.string().min(1) });
|
|
756
|
+
export type ChatRemoveBookmarkPayload = z.infer<typeof ChatRemoveBookmarkPayloadSchema>;
|
|
757
|
+
|
|
758
|
+
export const ChatBookmarksResultSchema = z.object({ bookmarks: ChatBookmarksSchema });
|
|
759
|
+
export type ChatBookmarksResult = z.infer<typeof ChatBookmarksResultSchema>;
|
|
760
|
+
|
|
761
|
+
// The whole list after every change, to every client attached to the chat.
|
|
762
|
+
export const ChatBookmarksEventSchema = z.object({ chatId: ChatIdSchema, bookmarks: ChatBookmarksSchema });
|
|
763
|
+
export type ChatBookmarksEvent = z.infer<typeof ChatBookmarksEventSchema>;
|
|
764
|
+
|
|
765
|
+
export const ChatAttachResultSchema = z.object({
|
|
766
|
+
info: ChatInfoSchema,
|
|
767
|
+
items: z.array(ChatItemSchema),
|
|
768
|
+
history: ChatHistoryPageSchema.optional(),
|
|
769
|
+
pending: z.array(ChatItemSchema).optional(),
|
|
770
|
+
seq: z.number().int().nonnegative().optional(),
|
|
771
|
+
// Only when `since` was honored: what happened after it, in order; `items` is then empty.
|
|
772
|
+
events: z.array(ChatEventSchema).optional(),
|
|
773
|
+
// Absent from a daemon that keeps no bookmarks.
|
|
774
|
+
bookmarks: ChatBookmarksSchema.optional()
|
|
775
|
+
});
|
|
776
|
+
export type ChatAttachResult = z.infer<typeof ChatAttachResultSchema>;
|
|
777
|
+
|
|
778
|
+
export const ChatSendPayloadSchema = z
|
|
779
|
+
.object({
|
|
780
|
+
chatId: ChatIdSchema,
|
|
781
|
+
text: z.string(),
|
|
782
|
+
mentions: z.array(z.string().min(1)).max(64).optional(),
|
|
783
|
+
skills: z.array(z.string().min(1)).max(16).optional(),
|
|
784
|
+
chats: z.array(ChatIdSchema).max(16).optional(),
|
|
785
|
+
attachments: ChatAttachmentUploadsSchema.optional()
|
|
786
|
+
})
|
|
787
|
+
.refine((payload) => payload.text.trim() !== '' || (payload.attachments?.length ?? 0) > 0, { message: 'A message needs text or an attachment' });
|
|
788
|
+
export type ChatSendPayload = z.infer<typeof ChatSendPayloadSchema>;
|
|
789
|
+
|
|
790
|
+
// Daemons before completion follow-ups omit `turnId`; keeping it optional lets newer clients finish the send.
|
|
791
|
+
export const ChatSendResultSchema = z.object({ queued: z.boolean(), turnId: z.string().min(1).optional() });
|
|
792
|
+
export type ChatSendResult = z.infer<typeof ChatSendResultSchema>;
|
|
793
|
+
|
|
794
|
+
export const ChatQueuePayloadSchema = z.object({
|
|
795
|
+
chatId: ChatIdSchema,
|
|
796
|
+
messageId: z.string().min(1)
|
|
797
|
+
});
|
|
798
|
+
export type ChatQueuePayload = z.infer<typeof ChatQueuePayloadSchema>;
|
|
799
|
+
|
|
800
|
+
// The message as it left the queue. An older daemon answers `{}` and refuses one it no longer holds.
|
|
801
|
+
export const ChatUnqueueResultSchema = z.object({
|
|
802
|
+
message: ChatQueuedMessageSchema.optional()
|
|
803
|
+
});
|
|
804
|
+
export type ChatUnqueueResult = z.infer<typeof ChatUnqueueResultSchema>;
|
|
805
|
+
|
|
806
|
+
export const ChatApprovePayloadSchema = z.object({
|
|
807
|
+
chatId: ChatIdSchema,
|
|
808
|
+
requestId: z.string().min(1),
|
|
809
|
+
decision: z.enum(['allow', 'allow-always', 'deny']),
|
|
810
|
+
message: z.string().optional()
|
|
811
|
+
});
|
|
812
|
+
export type ChatApprovePayload = z.infer<typeof ChatApprovePayloadSchema>;
|
|
813
|
+
|
|
814
|
+
// Leaves an asynchronous question alone; the agent never hears about it and the item settles.
|
|
815
|
+
export const ChatDismissPayloadSchema = z.object({
|
|
816
|
+
chatId: ChatIdSchema,
|
|
817
|
+
itemId: z.string().min(1)
|
|
818
|
+
});
|
|
819
|
+
export type ChatDismissPayload = z.infer<typeof ChatDismissPayloadSchema>;
|
|
820
|
+
|
|
821
|
+
export const ChatAnswerPayloadSchema = z.object({
|
|
822
|
+
chatId: ChatIdSchema,
|
|
823
|
+
requestId: z.string().min(1),
|
|
824
|
+
answers: z.record(z.string(), z.string())
|
|
825
|
+
});
|
|
826
|
+
export type ChatAnswerPayload = z.infer<typeof ChatAnswerPayloadSchema>;
|
|
827
|
+
|
|
828
|
+
export const ChatTurnDiffPayloadSchema = z.object({
|
|
829
|
+
chatId: ChatIdSchema,
|
|
830
|
+
turnId: z.string().min(1)
|
|
831
|
+
});
|
|
832
|
+
export type ChatTurnDiffPayload = z.infer<typeof ChatTurnDiffPayloadSchema>;
|
|
833
|
+
|
|
834
|
+
// Null when the turn has no checkpoint to diff against: no repository, or git could not be read.
|
|
835
|
+
export const ChatTurnDiffResultSchema = z.object({ diff: ChatCheckpointDiffSchema.nullable() });
|
|
836
|
+
export type ChatTurnDiffResult = z.infer<typeof ChatTurnDiffResultSchema>;
|
|
837
|
+
|
|
838
|
+
// A title of a node the fork makes; the same cap the canvas verbs hold a title to.
|
|
839
|
+
export const CHAT_FORK_TITLE_MAX = 120;
|
|
840
|
+
|
|
841
|
+
/*
|
|
842
|
+
* A new chat that goes on after `turnId`, with the history up to and including that turn. The fork of
|
|
843
|
+
* a node is a node beside it unless `asView` asks for a chat view of its own, listed right after the
|
|
844
|
+
* canvas the node stands on. The fork of a chat that is a view of its own is a view listed right after
|
|
845
|
+
* it, unless `viewId` names a canvas to put a node on instead.
|
|
846
|
+
*/
|
|
847
|
+
export const ChatForkPayloadSchema = z.object({
|
|
848
|
+
chatId: ChatIdSchema,
|
|
849
|
+
turnId: z.string().min(1),
|
|
850
|
+
title: z.string().trim().min(1).max(CHAT_FORK_TITLE_MAX).optional(),
|
|
851
|
+
viewId: z.string().min(1).optional(),
|
|
852
|
+
asView: z.boolean().optional(),
|
|
853
|
+
// A git worktree of its own on a new branch; absent is the original's folder. The branch defaults to one named after the title.
|
|
854
|
+
worktree: z.object({ branch: z.string().trim().min(1).max(CHAT_FORK_TITLE_MAX).optional() }).optional(),
|
|
855
|
+
// With a worktree: its files as they were after the turn rather than the branch's HEAD.
|
|
856
|
+
filesAfterTurn: z.boolean().optional(),
|
|
857
|
+
// Another CLI to go on with, which gets the conversation as text; absent is the original's CLI.
|
|
858
|
+
provider: AgentKindSchema.optional(),
|
|
859
|
+
// The model of that CLI; absent is the newest composer pick for it.
|
|
860
|
+
selection: ModelSelectionSchema.optional(),
|
|
861
|
+
// The account to go on under; absent is the original's account when the fork stays with its CLI.
|
|
862
|
+
account: ProviderAccountIdSchema.optional()
|
|
863
|
+
});
|
|
864
|
+
export type ChatForkPayload = z.infer<typeof ChatForkPayloadSchema>;
|
|
865
|
+
|
|
866
|
+
/*
|
|
867
|
+
* `viewId` is the canvas the node landed on, or the fork's own view, whose id is `nodeId`. `edgeId`
|
|
868
|
+
* is null when no line could be drawn from the original: it stands on no canvas, or the fork does not.
|
|
869
|
+
*/
|
|
870
|
+
export const ChatForkResultSchema = z.object({
|
|
871
|
+
info: ChatInfoSchema,
|
|
872
|
+
nodeId: z.string(),
|
|
873
|
+
viewId: z.string(),
|
|
874
|
+
edgeId: z.string().nullable(),
|
|
875
|
+
worktree: WorktreeSchema.optional()
|
|
876
|
+
});
|
|
877
|
+
export type ChatForkResult = z.infer<typeof ChatForkResultSchema>;
|
|
878
|
+
|
|
879
|
+
/*
|
|
880
|
+
* Goes on after the last turn of a chat, which stopped on a limit, under another account of its CLI.
|
|
881
|
+
* An account that reads the same transcripts takes the chat over in place; any other goes on in a
|
|
882
|
+
* fork that gets the conversation handed over. Only ever asked by a person, never done on its own.
|
|
883
|
+
*/
|
|
884
|
+
export const ChatContinueOnPayloadSchema = z.object({
|
|
885
|
+
chatId: ChatIdSchema,
|
|
886
|
+
account: ProviderAccountIdSchema
|
|
887
|
+
});
|
|
888
|
+
export type ChatContinueOnPayload = z.infer<typeof ChatContinueOnPayloadSchema>;
|
|
889
|
+
|
|
890
|
+
export const ChatContinueOnResultSchema = z.object({
|
|
891
|
+
// The chat that goes on: this one, or the fork.
|
|
892
|
+
chatId: ChatIdSchema,
|
|
893
|
+
// Set when it went on in a fork, which is where the client goes.
|
|
894
|
+
fork: ChatForkResultSchema.optional()
|
|
895
|
+
});
|
|
896
|
+
export type ChatContinueOnResult = z.infer<typeof ChatContinueOnResultSchema>;
|
|
897
|
+
|
|
898
|
+
export const ChatSummarizePayloadSchema = z.object({ chatId: ChatIdSchema });
|
|
899
|
+
export type ChatSummarizePayload = z.infer<typeof ChatSummarizePayloadSchema>;
|
|
900
|
+
|
|
901
|
+
// The turn the fork writes its summary in; its last answer goes to the original once the turn ends.
|
|
902
|
+
export const ChatSummarizeResultSchema = z.object({ turnId: z.string() });
|
|
903
|
+
export type ChatSummarizeResult = z.infer<typeof ChatSummarizeResultSchema>;
|
|
904
|
+
|
|
905
|
+
export const ChatForkInfoPayloadSchema = z.object({ chatId: ChatIdSchema, turnId: z.string().min(1) });
|
|
906
|
+
export type ChatForkInfoPayload = z.infer<typeof ChatForkInfoPayloadSchema>;
|
|
907
|
+
|
|
908
|
+
/*
|
|
909
|
+
* What the fork dialog asks before it offers a worktree: whether the chat's folder is in a repository,
|
|
910
|
+
* the branches taken there with a free one to suggest, and whether the files after that turn can
|
|
911
|
+
* still be put back (a tree git collected, or a turn that never had one, cannot).
|
|
912
|
+
*/
|
|
913
|
+
export const ChatForkInfoResultSchema = z.object({
|
|
914
|
+
repository: z.boolean(),
|
|
915
|
+
branches: z.array(z.string()),
|
|
916
|
+
branch: z.string().nullable(),
|
|
917
|
+
filesAfterTurn: z.boolean()
|
|
918
|
+
});
|
|
919
|
+
export type ChatForkInfoResult = z.infer<typeof ChatForkInfoResultSchema>;
|
|
920
|
+
|
|
921
|
+
export const ChatListResultSchema = z.object({
|
|
922
|
+
chats: z.array(ChatInfoSchema)
|
|
923
|
+
});
|
|
924
|
+
export type ChatListResult = z.infer<typeof ChatListResultSchema>;
|