@camox/api-contract 0.59.0 → 0.60.2
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/dist/router.d.ts +83 -76
- package/dist/threads.d.ts +97 -14
- package/dist/threads.js +139 -25
- package/package.json +1 -1
- package/src/threads.ts +201 -32
package/src/threads.ts
CHANGED
|
@@ -10,7 +10,9 @@ import type { DraftOutcome } from "./drafts.ts";
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The kind of the transcript entry recording an editor's message, with its sender and the
|
|
13
|
-
* operation id the studio sent it with as data (`editorMessageDataSchema`).
|
|
13
|
+
* operation id the studio sent it with as data (`editorMessageDataSchema`). Its first text part
|
|
14
|
+
* is what the editor wrote; a second one, which only the model reads, says where they were in the
|
|
15
|
+
* studio as they sent it (`editorTextOf`).
|
|
14
16
|
*/
|
|
15
17
|
export const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
16
18
|
|
|
@@ -62,6 +64,40 @@ export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
|
|
|
62
64
|
*/
|
|
63
65
|
export const START_DRAFT_TOOL = "startDraft";
|
|
64
66
|
|
|
67
|
+
/** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
|
|
68
|
+
export const BUILD_TOOL = "build";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
72
|
+
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
73
|
+
* keeps this in step with the tools themselves.
|
|
74
|
+
*/
|
|
75
|
+
export const CHANGING_TOOLS: ReadonlySet<string> = new Set([
|
|
76
|
+
// Content
|
|
77
|
+
"addBackToLocale",
|
|
78
|
+
"addLocale",
|
|
79
|
+
"createBlock",
|
|
80
|
+
"createCollectionRecord",
|
|
81
|
+
"createPage",
|
|
82
|
+
"deleteBlock",
|
|
83
|
+
"deleteCollectionRecord",
|
|
84
|
+
"deletePage",
|
|
85
|
+
"editBlock",
|
|
86
|
+
"editCollectionRecord",
|
|
87
|
+
"moveBlock",
|
|
88
|
+
"removeLocale",
|
|
89
|
+
"setPageLayout",
|
|
90
|
+
"setPageSeo",
|
|
91
|
+
"updateFile",
|
|
92
|
+
"updatePage",
|
|
93
|
+
"uploadFile",
|
|
94
|
+
// Code
|
|
95
|
+
"edit",
|
|
96
|
+
"mv",
|
|
97
|
+
"rm",
|
|
98
|
+
"write",
|
|
99
|
+
]);
|
|
100
|
+
|
|
65
101
|
/** The draft a `startDraft` call started, as its result's details name it for the card. */
|
|
66
102
|
export const startedDraftCardSchema = z.object({ draftId: z.string(), name: z.string() });
|
|
67
103
|
export type StartedDraftCard = z.infer<typeof startedDraftCardSchema>;
|
|
@@ -193,21 +229,31 @@ export type ThreadMessage =
|
|
|
193
229
|
*/
|
|
194
230
|
| { id: string; from: "push"; sender: ThreadSender; subjects: string[]; outcome: PushOutcome };
|
|
195
231
|
|
|
232
|
+
/**
|
|
233
|
+
* What the agent is doing during a run, as editors see it, without the details of how it works:
|
|
234
|
+
* - `thinking`: the model is called, and hasn't started writing a tool call.
|
|
235
|
+
* - `exploring`: it uses tools that only read, from writing a call until the model is called again.
|
|
236
|
+
* - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
|
|
237
|
+
* or, in a code mode script, as soon as the script calls one.
|
|
238
|
+
* - `building`: its `build` call runs, or the end of the turn builds its code changes.
|
|
239
|
+
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
240
|
+
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
241
|
+
*/
|
|
242
|
+
export type AgentActivity =
|
|
243
|
+
| "thinking"
|
|
244
|
+
| "exploring"
|
|
245
|
+
| "changing"
|
|
246
|
+
| "building"
|
|
247
|
+
| "fixing"
|
|
248
|
+
| "startingDraft";
|
|
249
|
+
|
|
196
250
|
/** Everything the studio shows for a thread. */
|
|
197
251
|
export type ThreadView = {
|
|
198
252
|
messages: readonly ThreadMessage[];
|
|
199
253
|
/** Whether a run is going: the agent is working on a turn, and stopping would end it. */
|
|
200
254
|
running: boolean;
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
* turn is building it, or it failed and the agent is fixing it.
|
|
204
|
-
*/
|
|
205
|
-
status?: BuildStatus;
|
|
206
|
-
/**
|
|
207
|
-
* While a run is going, whether the agent is using its tools, code mode included, rather than
|
|
208
|
-
* thinking: from writing a call until the model is called again.
|
|
209
|
-
*/
|
|
210
|
-
usingTools?: true;
|
|
255
|
+
/** While a run is going, what the agent is doing. */
|
|
256
|
+
activity?: AgentActivity;
|
|
211
257
|
};
|
|
212
258
|
|
|
213
259
|
export const EMPTY_THREAD_VIEW: ThreadView = { messages: [], running: false };
|
|
@@ -220,10 +266,15 @@ export const EMPTY_THREAD_VIEW: ThreadView = { messages: [], running: false };
|
|
|
220
266
|
export type ThreadState = {
|
|
221
267
|
steps: readonly ThreadStep[];
|
|
222
268
|
running: boolean;
|
|
223
|
-
/** The model's calls still running
|
|
224
|
-
toolCalls?: readonly string[];
|
|
269
|
+
/** The model's calls still running. A tool's own calls aren't the agent's. */
|
|
270
|
+
toolCalls?: readonly { callId: string; name: string }[];
|
|
225
271
|
/** Whether the model's latest message makes tool calls: it writes them, then they run. */
|
|
226
272
|
callingTools?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Whether a call of the latest round changes the draft, the model's or a script's: from writing
|
|
275
|
+
* or making it until the model is called again.
|
|
276
|
+
*/
|
|
277
|
+
changing?: boolean;
|
|
227
278
|
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
228
279
|
drafts?: DraftCardStates;
|
|
229
280
|
};
|
|
@@ -260,13 +311,54 @@ type ThreadStep = { id: string } & (
|
|
|
260
311
|
| { type: "continued" }
|
|
261
312
|
);
|
|
262
313
|
|
|
314
|
+
/**
|
|
315
|
+
* What an editor had selected on the page as they sent a message, unless they cleared it from
|
|
316
|
+
* the message: its path as the studio names it, from the block down to the selection, with the
|
|
317
|
+
* ids the agent's tools take. A record is the one the selection shows, a field the selection's
|
|
318
|
+
* own, of the record when a record is selected.
|
|
319
|
+
*/
|
|
320
|
+
export const editorSelectionSchema = z.object({
|
|
321
|
+
path: z.array(z.string().max(512)).min(1).max(16),
|
|
322
|
+
blockId: z.string().max(64).optional(),
|
|
323
|
+
itemId: z.string().max(64).optional(),
|
|
324
|
+
recordId: z.string().max(64).optional(),
|
|
325
|
+
fieldName: z.string().max(256).optional(),
|
|
326
|
+
});
|
|
327
|
+
export type EditorSelection = z.infer<typeof editorSelectionSchema>;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Where an editor was in the studio as they sent a message, in the Locale they viewed: a Page,
|
|
331
|
+
* by its path after the Locale's prefix and its title, with its id for a curated page and its
|
|
332
|
+
* Layout's for a layout page, and what they had selected on it, or the Canvas with no page
|
|
333
|
+
* selected. A hint for the agent, never what the message is about.
|
|
334
|
+
*/
|
|
335
|
+
export const editorWhereSchema = z.discriminatedUnion("on", [
|
|
336
|
+
z.object({
|
|
337
|
+
on: z.literal("page"),
|
|
338
|
+
locale: z.string().max(64),
|
|
339
|
+
path: z.string().max(2048),
|
|
340
|
+
title: z.string().max(512),
|
|
341
|
+
pageId: z.string().max(64).optional(),
|
|
342
|
+
layoutId: z.string().max(256).optional(),
|
|
343
|
+
selection: editorSelectionSchema.optional(),
|
|
344
|
+
}),
|
|
345
|
+
z.object({ on: z.literal("canvas"), locale: z.string().max(64) }),
|
|
346
|
+
]);
|
|
347
|
+
export type EditorWhere = z.infer<typeof editorWhereSchema>;
|
|
348
|
+
|
|
263
349
|
/** Studio → thread. */
|
|
264
350
|
export const threadClientMessageSchema = z.discriminatedUnion("type", [
|
|
265
351
|
/**
|
|
266
|
-
* An editor's message. The operation id is generated by
|
|
267
|
-
* a message resent after a dropped connection is one
|
|
352
|
+
* An editor's message, and where they were as they sent it. The operation id is generated by
|
|
353
|
+
* the studio and kept across resends, so a message resent after a dropped connection is one
|
|
354
|
+
* submission.
|
|
268
355
|
*/
|
|
269
|
-
z.object({
|
|
356
|
+
z.object({
|
|
357
|
+
type: z.literal("send"),
|
|
358
|
+
operationId: z.string().min(1),
|
|
359
|
+
text: z.string().min(1),
|
|
360
|
+
where: editorWhereSchema.optional(),
|
|
361
|
+
}),
|
|
270
362
|
/** Stops the run going, if any. What the agent changed before stays. */
|
|
271
363
|
z.object({ type: z.literal("stop") }),
|
|
272
364
|
/** Asks for a fresh snapshot of the thread. */
|
|
@@ -315,6 +407,15 @@ export const textOf = (content: Message["content"]) =>
|
|
|
315
407
|
.join("")
|
|
316
408
|
.trim();
|
|
317
409
|
|
|
410
|
+
/**
|
|
411
|
+
* What an editor wrote, of their message entry's content: its first text part. The rest, where
|
|
412
|
+
* they were in the studio, only the model reads.
|
|
413
|
+
*/
|
|
414
|
+
export const editorTextOf = (content: Message["content"]) =>
|
|
415
|
+
typeof content === "string"
|
|
416
|
+
? content
|
|
417
|
+
: (content.find((part) => part.type === "text")?.text ?? "").trim();
|
|
418
|
+
|
|
318
419
|
/** What an assistant message is to the thread: a final answer, a tool round, or neither. */
|
|
319
420
|
function assistantStep(id: string, message: AssistantMessage): ThreadStep {
|
|
320
421
|
if (message.content.some((part) => part.type === "toolCall")) return { id, type: "tools" };
|
|
@@ -355,7 +456,7 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
355
456
|
id,
|
|
356
457
|
type: "editor",
|
|
357
458
|
sender,
|
|
358
|
-
text:
|
|
459
|
+
text: editorTextOf(message.content),
|
|
359
460
|
...(handedOffTo && { handedOffTo }),
|
|
360
461
|
}
|
|
361
462
|
);
|
|
@@ -394,6 +495,7 @@ export function threadViewOf({
|
|
|
394
495
|
running,
|
|
395
496
|
toolCalls = [],
|
|
396
497
|
callingTools = false,
|
|
498
|
+
changing = false,
|
|
397
499
|
drafts = {},
|
|
398
500
|
}: ThreadState): ThreadView {
|
|
399
501
|
const messages: ThreadMessage[] = [];
|
|
@@ -511,15 +613,44 @@ export function threadViewOf({
|
|
|
511
613
|
}
|
|
512
614
|
}
|
|
513
615
|
if (!running) fail();
|
|
514
|
-
|
|
616
|
+
if (!running) return { messages, running };
|
|
515
617
|
return {
|
|
516
618
|
messages,
|
|
517
619
|
running,
|
|
518
|
-
|
|
519
|
-
...(usingTools && { usingTools }),
|
|
620
|
+
activity: activityOf({ status, toolCalls, callingTools, changing }),
|
|
520
621
|
};
|
|
521
622
|
}
|
|
522
623
|
|
|
624
|
+
/** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
|
|
625
|
+
function activityOf({
|
|
626
|
+
status,
|
|
627
|
+
toolCalls,
|
|
628
|
+
callingTools,
|
|
629
|
+
changing,
|
|
630
|
+
}: {
|
|
631
|
+
status: BuildStatus | undefined;
|
|
632
|
+
toolCalls: NonNullable<ThreadState["toolCalls"]>;
|
|
633
|
+
callingTools: boolean;
|
|
634
|
+
changing: boolean;
|
|
635
|
+
}): AgentActivity {
|
|
636
|
+
const calling = (name: string) => toolCalls.some((call) => call.name === name);
|
|
637
|
+
// Fixing a build error takes thinking, reading and writing, which all read as fixing, but its
|
|
638
|
+
// builds show as building.
|
|
639
|
+
if (status === "building" || calling(BUILD_TOOL)) return "building";
|
|
640
|
+
if (status === "fixing") return "fixing";
|
|
641
|
+
if (calling(START_DRAFT_TOOL)) return "startingDraft";
|
|
642
|
+
if (changing) return "changing";
|
|
643
|
+
if (callingTools || toolCalls.length > 0) return "exploring";
|
|
644
|
+
return "thinking";
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** Whether a tool call changes the draft. */
|
|
648
|
+
const changes = (name: string) => CHANGING_TOOLS.has(name);
|
|
649
|
+
|
|
650
|
+
/** Whether a block of the model's message is a call of a tool that changes the draft. */
|
|
651
|
+
const callsChangingTool = (block: AssistantMessage["content"][number]) =>
|
|
652
|
+
block.type === "toolCall" && changes(block.name);
|
|
653
|
+
|
|
523
654
|
const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
|
|
524
655
|
const step = entry && threadStepOf(entry);
|
|
525
656
|
if (!step || state.steps.some((known) => known.id === step.id)) return state;
|
|
@@ -529,33 +660,71 @@ const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState
|
|
|
529
660
|
/** Folds one of Pi's agent events into the thread's state. */
|
|
530
661
|
export function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState {
|
|
531
662
|
switch (event.type) {
|
|
532
|
-
case "snapshot":
|
|
663
|
+
case "snapshot": {
|
|
664
|
+
const toolCalls = event.tools.flatMap((tool) =>
|
|
665
|
+
tool.status === "done" ? [] : [{ callId: tool.callId, name: tool.name }],
|
|
666
|
+
);
|
|
667
|
+
const generated = event.generation?.message?.content ?? [];
|
|
533
668
|
return {
|
|
534
669
|
...state,
|
|
535
670
|
steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
|
|
536
671
|
running: event.run !== undefined,
|
|
537
|
-
toolCalls
|
|
538
|
-
callingTools:
|
|
672
|
+
toolCalls,
|
|
673
|
+
callingTools: generated.some((part) => part.type === "toolCall"),
|
|
674
|
+
// A script's calls count while the script runs, finished or not.
|
|
675
|
+
changing:
|
|
676
|
+
generated.some(callsChangingTool) ||
|
|
677
|
+
toolCalls.some((call) => changes(call.name)) ||
|
|
678
|
+
event.nestedTools.some(
|
|
679
|
+
(nested) =>
|
|
680
|
+
changes(nested.name) &&
|
|
681
|
+
toolCalls.some(
|
|
682
|
+
(call) =>
|
|
683
|
+
nested.parentCallId === call.callId ||
|
|
684
|
+
nested.parentCallId.startsWith(`${call.callId}/`),
|
|
685
|
+
),
|
|
686
|
+
),
|
|
539
687
|
};
|
|
688
|
+
}
|
|
540
689
|
case "run_start":
|
|
541
690
|
return { ...state, running: true };
|
|
542
691
|
case "run_end":
|
|
543
|
-
return { ...state, running: false, toolCalls: [], callingTools: false };
|
|
692
|
+
return { ...state, running: false, toolCalls: [], callingTools: false, changing: false };
|
|
544
693
|
case "message_start":
|
|
545
694
|
// The model is called again: it thinks until it writes a tool call.
|
|
546
|
-
return event.message.role === "assistant"
|
|
547
|
-
|
|
548
|
-
return event.changes.some((change) => change.type === "toolcall_start")
|
|
549
|
-
? { ...state, callingTools: true }
|
|
695
|
+
return event.message.role === "assistant"
|
|
696
|
+
? { ...state, callingTools: false, changing: false }
|
|
550
697
|
: state;
|
|
551
|
-
case "
|
|
552
|
-
|
|
553
|
-
|
|
698
|
+
case "message_update": {
|
|
699
|
+
// A call's name may come with its start, a later change to its block, or the whole message.
|
|
700
|
+
const calls = event.changes.flatMap((change) =>
|
|
701
|
+
change.type === "toolcall_start" || change.type === "block"
|
|
702
|
+
? [change.block]
|
|
703
|
+
: change.type === "message"
|
|
704
|
+
? change.message.content
|
|
705
|
+
: [],
|
|
706
|
+
);
|
|
707
|
+
if (calls.length === 0) return state;
|
|
708
|
+
return {
|
|
709
|
+
...state,
|
|
710
|
+
callingTools: state.callingTools || calls.some((block) => block.type === "toolCall"),
|
|
711
|
+
changing: state.changing || calls.some(callsChangingTool),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
case "tool_execution_start": {
|
|
715
|
+
const changing = state.changing || changes(event.toolName);
|
|
716
|
+
if (event.parentToolCallId !== undefined) return { ...state, changing };
|
|
717
|
+
return {
|
|
718
|
+
...state,
|
|
719
|
+
changing,
|
|
720
|
+
toolCalls: [...(state.toolCalls ?? []), { callId: event.toolCallId, name: event.toolName }],
|
|
721
|
+
};
|
|
722
|
+
}
|
|
554
723
|
case "tool_execution_end":
|
|
555
724
|
return append(
|
|
556
725
|
{
|
|
557
726
|
...state,
|
|
558
|
-
toolCalls: state.toolCalls?.filter((
|
|
727
|
+
toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
|
|
559
728
|
},
|
|
560
729
|
event.entry,
|
|
561
730
|
);
|