@camox/api-contract 0.63.0 → 0.66.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/dist/analytics.d.ts +1 -1
- package/dist/drafts.d.ts +50 -2
- package/dist/drafts.js +57 -3
- package/dist/layout-routes.d.ts +20 -0
- package/dist/layout-routes.js +43 -0
- package/dist/patrol.d.ts +116 -8
- package/dist/patrol.js +33 -1
- package/dist/router.d.ts +10023 -9206
- package/dist/threads.d.ts +74 -19
- package/dist/threads.js +130 -40
- package/package.json +8 -2
- package/src/drafts.ts +67 -2
- package/src/layout-routes.ts +51 -0
- package/src/patrol.ts +129 -9
- package/src/threads.ts +128 -53
package/src/threads.ts
CHANGED
|
@@ -45,7 +45,8 @@ export const RECONCILE_ENTRY = "camox.reconcile";
|
|
|
45
45
|
/**
|
|
46
46
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
47
47
|
* to: the Brief, which the model reads and editors see as from Camox, ahead of the editor's
|
|
48
|
-
* messages.
|
|
48
|
+
* messages. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
49
|
+
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
49
50
|
*/
|
|
50
51
|
export const BRIEF_ENTRY = "camox.brief";
|
|
51
52
|
|
|
@@ -56,6 +57,14 @@ export const BRIEF_ENTRY = "camox.brief";
|
|
|
56
57
|
*/
|
|
57
58
|
export const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
62
|
+
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
63
|
+
* data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
|
|
64
|
+
* Everything before it is left out of both.
|
|
65
|
+
*/
|
|
66
|
+
export const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
67
|
+
|
|
59
68
|
/**
|
|
60
69
|
* The kind of the transcript entry recording a member's push to the draft's code from their own
|
|
61
70
|
* checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
|
|
@@ -76,6 +85,29 @@ export const START_DRAFT_TOOL = "startDraft";
|
|
|
76
85
|
/** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
|
|
77
86
|
export const BUILD_TOOL = "build";
|
|
78
87
|
|
|
88
|
+
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
89
|
+
export const LOOK_TOOL = "look";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
|
|
93
|
+
* it in a conversation of its own and answers the agent (ADR 0035).
|
|
94
|
+
*/
|
|
95
|
+
export const ARTIFACT_TOOL = "artifact";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
|
|
99
|
+
* Agent's own conversation, which editors never see: who the run acts for and the operation ids
|
|
100
|
+
* of its messages, as data (`artifactRequestDataSchema`).
|
|
101
|
+
*/
|
|
102
|
+
export const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
|
|
103
|
+
|
|
104
|
+
/** What an Artifact Agent's request records: the member the run acts for, and its messages. */
|
|
105
|
+
export const artifactRequestDataSchema = z.object({
|
|
106
|
+
member: z.string(),
|
|
107
|
+
operationIds: z.array(z.string()),
|
|
108
|
+
});
|
|
109
|
+
export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
|
|
110
|
+
|
|
79
111
|
/**
|
|
80
112
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
81
113
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
@@ -85,6 +117,7 @@ export const CHANGING_TOOLS: ReadonlySet<string> = new Set([
|
|
|
85
117
|
// Content
|
|
86
118
|
"addBackToLocale",
|
|
87
119
|
"addLocale",
|
|
120
|
+
"applyFindingFix",
|
|
88
121
|
"createBlock",
|
|
89
122
|
"createCollectionRecord",
|
|
90
123
|
"createPage",
|
|
@@ -167,13 +200,9 @@ export const pushDataSchema = z.object({
|
|
|
167
200
|
});
|
|
168
201
|
export type PushData = z.infer<typeof pushDataSchema>;
|
|
169
202
|
|
|
170
|
-
/**
|
|
171
|
-
* What an editor's message entry records: who sent it, the operation id it was sent with, and for
|
|
172
|
-
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
173
|
-
*/
|
|
203
|
+
/** What an editor's message entry records: who sent it, and the operation id it was sent with. */
|
|
174
204
|
export const editorMessageDataSchema = threadSenderSchema.extend({
|
|
175
205
|
operationId: z.string().optional(),
|
|
176
|
-
handedOffTo: z.string().optional(),
|
|
177
206
|
});
|
|
178
207
|
|
|
179
208
|
/**
|
|
@@ -274,16 +303,33 @@ export type ThreadMessage =
|
|
|
274
303
|
* - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
|
|
275
304
|
* or, in a code mode script, as soon as the script calls one.
|
|
276
305
|
* - `building`: its `build` call runs, or the end of the turn builds its code changes.
|
|
306
|
+
* - `checking`: it looks at what it designed, from writing a `look` call until the model is
|
|
307
|
+
* called again.
|
|
277
308
|
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
278
309
|
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
279
310
|
*/
|
|
280
|
-
export
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
export const agentActivitySchema = z.enum([
|
|
312
|
+
"thinking",
|
|
313
|
+
"exploring",
|
|
314
|
+
"changing",
|
|
315
|
+
"building",
|
|
316
|
+
"checking",
|
|
317
|
+
"fixing",
|
|
318
|
+
"startingDraft",
|
|
319
|
+
]);
|
|
320
|
+
export type AgentActivity = z.infer<typeof agentActivitySchema>;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
|
|
324
|
+
* it's the agent's answer to the turn too, which then ends on it, and while it works, what the
|
|
325
|
+
* Artifact Agent is doing.
|
|
326
|
+
*/
|
|
327
|
+
export const artifactDetailsSchema = z.object({
|
|
328
|
+
answer: z.string().optional(),
|
|
329
|
+
final: z.boolean().optional(),
|
|
330
|
+
activity: agentActivitySchema.optional(),
|
|
331
|
+
});
|
|
332
|
+
export type ArtifactDetails = z.infer<typeof artifactDetailsSchema>;
|
|
287
333
|
|
|
288
334
|
/** Everything the studio shows for a thread. */
|
|
289
335
|
export type ThreadView = {
|
|
@@ -313,6 +359,10 @@ export type ThreadState = {
|
|
|
313
359
|
* or making it until the model is called again.
|
|
314
360
|
*/
|
|
315
361
|
changing?: boolean;
|
|
362
|
+
/** Whether the model's latest round calls `look`: from writing the call until it's called again. */
|
|
363
|
+
checking?: boolean;
|
|
364
|
+
/** What the Artifact Agent the running `artifact` call waits for is doing, as it last said. */
|
|
365
|
+
artifactActivity?: AgentActivity;
|
|
316
366
|
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
317
367
|
drafts?: DraftCardStates;
|
|
318
368
|
};
|
|
@@ -321,13 +371,7 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
|
|
|
321
371
|
|
|
322
372
|
/** One transcript entry, as far as the studio's view is concerned. */
|
|
323
373
|
type ThreadStep = { id: string } & (
|
|
324
|
-
| {
|
|
325
|
-
type: "editor";
|
|
326
|
-
sender: ThreadSender;
|
|
327
|
-
text: string;
|
|
328
|
-
/** The draft the message went to, as the agent on Live handed the request off. */
|
|
329
|
-
handedOffTo?: string;
|
|
330
|
-
}
|
|
374
|
+
| { type: "editor"; sender: ThreadSender; text: string }
|
|
331
375
|
| { type: "connectedAgent"; member: ThreadSender; client: ConnectedAgentClient; text: string }
|
|
332
376
|
/** Camox starting a turn of its own, saying why. */
|
|
333
377
|
| { type: "camox"; text: string }
|
|
@@ -485,20 +529,16 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
485
529
|
const push = pushDataSchema.safeParse(entry.data).data;
|
|
486
530
|
return push && { id, type: "push", ...push };
|
|
487
531
|
}
|
|
532
|
+
// The model reads a note: editors see the card it starts the Live thread afresh with.
|
|
533
|
+
if (entry.kind === DRAFT_CARD_ENTRY) {
|
|
534
|
+
const card = startedDraftCardSchema.safeParse(entry.data).data;
|
|
535
|
+
return card && { id, type: "draft", ...card };
|
|
536
|
+
}
|
|
488
537
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
489
538
|
if (!message) return undefined;
|
|
490
539
|
if (message.role === "user") {
|
|
491
540
|
if (entry.kind === EDITOR_MESSAGE_ENTRY) {
|
|
492
|
-
|
|
493
|
-
return (
|
|
494
|
-
sender && {
|
|
495
|
-
id,
|
|
496
|
-
type: "editor",
|
|
497
|
-
sender,
|
|
498
|
-
text: editorTextOf(message.content),
|
|
499
|
-
...(handedOffTo && { handedOffTo }),
|
|
500
|
-
}
|
|
501
|
-
);
|
|
541
|
+
return sender && { id, type: "editor", sender, text: editorTextOf(message.content) };
|
|
502
542
|
}
|
|
503
543
|
if (entry.kind === CONNECTED_AGENT_MESSAGE_ENTRY) {
|
|
504
544
|
const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
|
|
@@ -523,6 +563,11 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
523
563
|
}
|
|
524
564
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
525
565
|
if (message.role === "toolResult") {
|
|
566
|
+
// An Artifact Agent's answer that ends the turn is the agent's: editors never see it apart.
|
|
567
|
+
if (message.toolName === ARTIFACT_TOOL && !message.isError) {
|
|
568
|
+
const details = artifactDetailsSchema.safeParse(message.details).data;
|
|
569
|
+
if (details?.final && details.answer) return { id, type: "answer", text: details.answer };
|
|
570
|
+
}
|
|
526
571
|
const card =
|
|
527
572
|
message.toolName === START_DRAFT_TOOL && !message.isError
|
|
528
573
|
? startedDraftCardSchema.safeParse(message.details).data
|
|
@@ -547,6 +592,8 @@ export function threadViewOf({
|
|
|
547
592
|
toolCalls = [],
|
|
548
593
|
callingTools = false,
|
|
549
594
|
changing = false,
|
|
595
|
+
checking = false,
|
|
596
|
+
artifactActivity,
|
|
550
597
|
drafts = {},
|
|
551
598
|
}: ThreadState): ThreadView {
|
|
552
599
|
const messages: ThreadMessage[] = [];
|
|
@@ -572,24 +619,7 @@ export function threadViewOf({
|
|
|
572
619
|
|
|
573
620
|
for (const step of steps) {
|
|
574
621
|
switch (step.type) {
|
|
575
|
-
case "editor":
|
|
576
|
-
// A message sent as the agent on Live handed the request off went to the draft with it:
|
|
577
|
-
// it belongs to the turn ending on that draft's card, which it shows just before. Pi
|
|
578
|
-
// placed it after the card, which leaves the transcript the model read as it was.
|
|
579
|
-
const card = step.handedOffTo
|
|
580
|
-
? messages.findLastIndex(
|
|
581
|
-
(message) => message.from === "draft" && message.draftId === step.handedOffTo,
|
|
582
|
-
)
|
|
583
|
-
: -1;
|
|
584
|
-
if (card !== -1) {
|
|
585
|
-
messages.splice(card, 0, {
|
|
586
|
-
id: step.id,
|
|
587
|
-
from: "editor",
|
|
588
|
-
sender: step.sender,
|
|
589
|
-
text: step.text,
|
|
590
|
-
});
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
622
|
+
case "editor":
|
|
593
623
|
// Pi places a steering message only once a model call is settled: after a round of tool
|
|
594
624
|
// calls, or with the answer ending the run. Never between a failed call and its retry, so
|
|
595
625
|
// an editor's message after an unfinished call means that call's turn is over.
|
|
@@ -597,7 +627,6 @@ export function threadViewOf({
|
|
|
597
627
|
messages.push({ id: step.id, from: "editor", sender: step.sender, text: step.text });
|
|
598
628
|
waiting = step.id;
|
|
599
629
|
break;
|
|
600
|
-
}
|
|
601
630
|
case "connectedAgent":
|
|
602
631
|
// Like an editor's message: it starts a turn, or steers the one going.
|
|
603
632
|
if (unfinished) fail();
|
|
@@ -680,7 +709,7 @@ export function threadViewOf({
|
|
|
680
709
|
return {
|
|
681
710
|
messages,
|
|
682
711
|
running,
|
|
683
|
-
activity: activityOf({ status, toolCalls, callingTools, changing }),
|
|
712
|
+
activity: activityOf({ status, toolCalls, callingTools, changing, checking, artifactActivity }),
|
|
684
713
|
};
|
|
685
714
|
}
|
|
686
715
|
|
|
@@ -690,16 +719,23 @@ function activityOf({
|
|
|
690
719
|
toolCalls,
|
|
691
720
|
callingTools,
|
|
692
721
|
changing,
|
|
722
|
+
checking,
|
|
723
|
+
artifactActivity,
|
|
693
724
|
}: {
|
|
694
725
|
status: BuildStatus | undefined;
|
|
695
726
|
toolCalls: NonNullable<ThreadState["toolCalls"]>;
|
|
696
727
|
callingTools: boolean;
|
|
697
728
|
changing: boolean;
|
|
729
|
+
checking: boolean;
|
|
730
|
+
artifactActivity: AgentActivity | undefined;
|
|
698
731
|
}): AgentActivity {
|
|
699
732
|
const calling = (name: string) => toolCalls.some((call) => call.name === name);
|
|
700
733
|
// Fixing a build error takes thinking, reading and writing, which all read as fixing, but its
|
|
701
734
|
// builds show as building.
|
|
702
735
|
if (status === "building" || calling(BUILD_TOOL)) return "building";
|
|
736
|
+
// An Artifact Agent works on the agent's behalf: editors see what it does as the agent's.
|
|
737
|
+
if (calling(ARTIFACT_TOOL)) return artifactActivity ?? "changing";
|
|
738
|
+
if (checking) return "checking";
|
|
703
739
|
if (status === "fixing") return "fixing";
|
|
704
740
|
if (calling(START_DRAFT_TOOL)) return "startingDraft";
|
|
705
741
|
if (changing) return "changing";
|
|
@@ -714,8 +750,25 @@ const changes = (name: string) => CHANGING_TOOLS.has(name);
|
|
|
714
750
|
const callsChangingTool = (block: AssistantMessage["content"][number]) =>
|
|
715
751
|
block.type === "toolCall" && changes(block.name);
|
|
716
752
|
|
|
753
|
+
/** Whether a block of the model's message is a call of `look`. */
|
|
754
|
+
const callsLook = (block: AssistantMessage["content"][number]) =>
|
|
755
|
+
block.type === "toolCall" && block.name === LOOK_TOOL;
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
|
|
759
|
+
* the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
|
|
760
|
+
* snapshots do.
|
|
761
|
+
*/
|
|
717
762
|
const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
|
|
718
|
-
|
|
763
|
+
if (!entry) return state;
|
|
764
|
+
const step = threadStepOf(entry);
|
|
765
|
+
if (entry.head !== undefined) {
|
|
766
|
+
const head = Number(entry.head);
|
|
767
|
+
const kept = state.steps.filter(
|
|
768
|
+
(known) => Number(known.id) >= head && known.id !== String(entry.id),
|
|
769
|
+
);
|
|
770
|
+
return { ...state, steps: step ? [step, ...kept] : kept };
|
|
771
|
+
}
|
|
719
772
|
if (!step || state.steps.some((known) => known.id === step.id)) return state;
|
|
720
773
|
return { ...state, steps: [...state.steps, step] };
|
|
721
774
|
};
|
|
@@ -728,12 +781,17 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
728
781
|
tool.status === "done" ? [] : [{ callId: tool.callId, name: tool.name }],
|
|
729
782
|
);
|
|
730
783
|
const generated = event.generation?.message?.content ?? [];
|
|
784
|
+
const artifact = event.tools.find(
|
|
785
|
+
(tool) => tool.name === ARTIFACT_TOOL && tool.status !== "done",
|
|
786
|
+
);
|
|
731
787
|
return {
|
|
732
788
|
...state,
|
|
733
789
|
steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
|
|
790
|
+
artifactActivity: artifactDetailsSchema.safeParse(artifact?.details).data?.activity,
|
|
734
791
|
running: event.run !== undefined,
|
|
735
792
|
toolCalls,
|
|
736
793
|
callingTools: generated.some((part) => part.type === "toolCall"),
|
|
794
|
+
checking: generated.some(callsLook) || toolCalls.some((call) => call.name === LOOK_TOOL),
|
|
737
795
|
// A script's calls count while the script runs, finished or not.
|
|
738
796
|
changing:
|
|
739
797
|
generated.some(callsChangingTool) ||
|
|
@@ -752,11 +810,19 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
752
810
|
case "run_start":
|
|
753
811
|
return { ...state, running: true };
|
|
754
812
|
case "run_end":
|
|
755
|
-
return {
|
|
813
|
+
return {
|
|
814
|
+
...state,
|
|
815
|
+
running: false,
|
|
816
|
+
toolCalls: [],
|
|
817
|
+
callingTools: false,
|
|
818
|
+
changing: false,
|
|
819
|
+
checking: false,
|
|
820
|
+
artifactActivity: undefined,
|
|
821
|
+
};
|
|
756
822
|
case "message_start":
|
|
757
823
|
// The model is called again: it thinks until it writes a tool call.
|
|
758
824
|
return event.message.role === "assistant"
|
|
759
|
-
? { ...state, callingTools: false, changing: false }
|
|
825
|
+
? { ...state, callingTools: false, changing: false, checking: false }
|
|
760
826
|
: state;
|
|
761
827
|
case "message_update": {
|
|
762
828
|
// A call's name may come with its start, a later change to its block, or the whole message.
|
|
@@ -772,6 +838,7 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
772
838
|
...state,
|
|
773
839
|
callingTools: state.callingTools || calls.some((block) => block.type === "toolCall"),
|
|
774
840
|
changing: state.changing || calls.some(callsChangingTool),
|
|
841
|
+
checking: state.checking || calls.some(callsLook),
|
|
775
842
|
};
|
|
776
843
|
}
|
|
777
844
|
case "tool_execution_start": {
|
|
@@ -780,14 +847,22 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
780
847
|
return {
|
|
781
848
|
...state,
|
|
782
849
|
changing,
|
|
850
|
+
checking: state.checking || event.toolName === LOOK_TOOL,
|
|
783
851
|
toolCalls: [...(state.toolCalls ?? []), { callId: event.toolCallId, name: event.toolName }],
|
|
784
852
|
};
|
|
785
853
|
}
|
|
854
|
+
case "tool_execution_update": {
|
|
855
|
+
if (event.toolName !== ARTIFACT_TOOL || event.parentToolCallId !== undefined) return state;
|
|
856
|
+
const activity = artifactDetailsSchema.safeParse(event.details).data?.activity;
|
|
857
|
+
return activity ? { ...state, artifactActivity: activity } : state;
|
|
858
|
+
}
|
|
786
859
|
case "tool_execution_end":
|
|
787
860
|
return append(
|
|
788
861
|
{
|
|
789
862
|
...state,
|
|
790
863
|
toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
|
|
864
|
+
...(event.toolName === ARTIFACT_TOOL &&
|
|
865
|
+
event.parentToolCallId === undefined && { artifactActivity: undefined }),
|
|
791
866
|
},
|
|
792
867
|
event.entry,
|
|
793
868
|
);
|