@camox/api-contract 0.65.1 → 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/router.d.ts +24 -1
- package/dist/threads.d.ts +57 -9
- package/dist/threads.js +114 -37
- package/package.json +1 -1
- package/src/threads.ts +98 -51
package/dist/router.d.ts
CHANGED
|
@@ -2298,6 +2298,25 @@ declare const schema: {
|
|
|
2298
2298
|
}, {}, {
|
|
2299
2299
|
length: number | undefined;
|
|
2300
2300
|
}>;
|
|
2301
|
+
overviewName: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2302
|
+
name: "overview_name";
|
|
2303
|
+
tableName: "drafts";
|
|
2304
|
+
dataType: "string";
|
|
2305
|
+
columnType: "SQLiteText";
|
|
2306
|
+
data: string;
|
|
2307
|
+
driverParam: string;
|
|
2308
|
+
notNull: false;
|
|
2309
|
+
hasDefault: false;
|
|
2310
|
+
isPrimaryKey: false;
|
|
2311
|
+
isAutoincrement: false;
|
|
2312
|
+
hasRuntimeDefault: false;
|
|
2313
|
+
enumValues: [string, ...string[]];
|
|
2314
|
+
baseColumn: never;
|
|
2315
|
+
identity: undefined;
|
|
2316
|
+
generated: undefined;
|
|
2317
|
+
}, {}, {
|
|
2318
|
+
length: number | undefined;
|
|
2319
|
+
}>;
|
|
2301
2320
|
overviewBullets: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
2302
2321
|
name: "overview_bullets";
|
|
2303
2322
|
tableName: "drafts";
|
|
@@ -10357,10 +10376,12 @@ type Overview =
|
|
|
10357
10376
|
} |
|
|
10358
10377
|
/**
|
|
10359
10378
|
* Written. A `stale` one describes an older version of the draft's changes: it's still shown
|
|
10360
|
-
* while a new one is written.
|
|
10379
|
+
* while a new one is written. `name` is the short name AI suggests keeping the draft under,
|
|
10380
|
+
* null for an overview written before overviews had one.
|
|
10361
10381
|
*/
|
|
10362
10382
|
{
|
|
10363
10383
|
status: "ready";
|
|
10384
|
+
name: string | null;
|
|
10364
10385
|
headline: string;
|
|
10365
10386
|
bullets: string[];
|
|
10366
10387
|
stale: boolean;
|
|
@@ -18121,6 +18142,7 @@ declare const router: {
|
|
|
18121
18142
|
status: "pending";
|
|
18122
18143
|
} | {
|
|
18123
18144
|
status: "ready";
|
|
18145
|
+
name: string | null;
|
|
18124
18146
|
headline: string;
|
|
18125
18147
|
bullets: string[];
|
|
18126
18148
|
stale: boolean;
|
|
@@ -18162,6 +18184,7 @@ declare const router: {
|
|
|
18162
18184
|
status: "pending";
|
|
18163
18185
|
} | {
|
|
18164
18186
|
status: "ready";
|
|
18187
|
+
name: string | null;
|
|
18165
18188
|
headline: string;
|
|
18166
18189
|
bullets: string[];
|
|
18167
18190
|
stale: boolean;
|
package/dist/threads.d.ts
CHANGED
|
@@ -45,6 +45,13 @@ export declare const BRIEF_ENTRY = "camox.brief";
|
|
|
45
45
|
* it at the end of the conversation; editors never see it, since the draft's card says so.
|
|
46
46
|
*/
|
|
47
47
|
export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
48
|
+
/**
|
|
49
|
+
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
50
|
+
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
51
|
+
* data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
|
|
52
|
+
* Everything before it is left out of both.
|
|
53
|
+
*/
|
|
54
|
+
export declare const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
48
55
|
/**
|
|
49
56
|
* The kind of the transcript entry recording a member's push to the draft's code from their own
|
|
50
57
|
* checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
|
|
@@ -67,6 +74,23 @@ export declare const START_DRAFT_TOOL = "startDraft";
|
|
|
67
74
|
export declare const BUILD_TOOL = "build";
|
|
68
75
|
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
69
76
|
export declare const LOOK_TOOL = "look";
|
|
77
|
+
/**
|
|
78
|
+
* The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
|
|
79
|
+
* it in a conversation of its own and answers the agent (ADR 0035).
|
|
80
|
+
*/
|
|
81
|
+
export declare const ARTIFACT_TOOL = "artifact";
|
|
82
|
+
/**
|
|
83
|
+
* The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
|
|
84
|
+
* Agent's own conversation, which editors never see: who the run acts for and the operation ids
|
|
85
|
+
* of its messages, as data (`artifactRequestDataSchema`).
|
|
86
|
+
*/
|
|
87
|
+
export declare const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
|
|
88
|
+
/** What an Artifact Agent's request records: the member the run acts for, and its messages. */
|
|
89
|
+
export declare const artifactRequestDataSchema: z.ZodObject<{
|
|
90
|
+
member: z.ZodString;
|
|
91
|
+
operationIds: z.ZodArray<z.ZodString>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
|
|
70
94
|
/**
|
|
71
95
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
72
96
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
@@ -155,16 +179,12 @@ export declare const pushDataSchema: z.ZodObject<{
|
|
|
155
179
|
}>;
|
|
156
180
|
}, z.core.$strip>;
|
|
157
181
|
export type PushData = z.infer<typeof pushDataSchema>;
|
|
158
|
-
/**
|
|
159
|
-
* What an editor's message entry records: who sent it, the operation id it was sent with, and for
|
|
160
|
-
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
161
|
-
*/
|
|
182
|
+
/** What an editor's message entry records: who sent it, and the operation id it was sent with. */
|
|
162
183
|
export declare const editorMessageDataSchema: z.ZodObject<{
|
|
163
184
|
id: z.ZodString;
|
|
164
185
|
name: z.ZodString;
|
|
165
186
|
image: z.ZodNullable<z.ZodString>;
|
|
166
187
|
operationId: z.ZodOptional<z.ZodString>;
|
|
167
|
-
handedOffTo: z.ZodOptional<z.ZodString>;
|
|
168
188
|
}, z.core.$strip>;
|
|
169
189
|
/**
|
|
170
190
|
* A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
|
|
@@ -344,7 +364,35 @@ export type ThreadMessage = {
|
|
|
344
364
|
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
345
365
|
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
346
366
|
*/
|
|
347
|
-
export
|
|
367
|
+
export declare const agentActivitySchema: z.ZodEnum<{
|
|
368
|
+
building: "building";
|
|
369
|
+
changing: "changing";
|
|
370
|
+
checking: "checking";
|
|
371
|
+
exploring: "exploring";
|
|
372
|
+
fixing: "fixing";
|
|
373
|
+
startingDraft: "startingDraft";
|
|
374
|
+
thinking: "thinking";
|
|
375
|
+
}>;
|
|
376
|
+
export type AgentActivity = z.infer<typeof agentActivitySchema>;
|
|
377
|
+
/**
|
|
378
|
+
* What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
|
|
379
|
+
* it's the agent's answer to the turn too, which then ends on it, and while it works, what the
|
|
380
|
+
* Artifact Agent is doing.
|
|
381
|
+
*/
|
|
382
|
+
export declare const artifactDetailsSchema: z.ZodObject<{
|
|
383
|
+
answer: z.ZodOptional<z.ZodString>;
|
|
384
|
+
final: z.ZodOptional<z.ZodBoolean>;
|
|
385
|
+
activity: z.ZodOptional<z.ZodEnum<{
|
|
386
|
+
building: "building";
|
|
387
|
+
changing: "changing";
|
|
388
|
+
checking: "checking";
|
|
389
|
+
exploring: "exploring";
|
|
390
|
+
fixing: "fixing";
|
|
391
|
+
startingDraft: "startingDraft";
|
|
392
|
+
thinking: "thinking";
|
|
393
|
+
}>>;
|
|
394
|
+
}, z.core.$strip>;
|
|
395
|
+
export type ArtifactDetails = z.infer<typeof artifactDetailsSchema>;
|
|
348
396
|
/** Everything the studio shows for a thread. */
|
|
349
397
|
export type ThreadView = {
|
|
350
398
|
messages: readonly ThreadMessage[];
|
|
@@ -376,6 +424,8 @@ export type ThreadState = {
|
|
|
376
424
|
changing?: boolean;
|
|
377
425
|
/** Whether the model's latest round calls `look`: from writing the call until it's called again. */
|
|
378
426
|
checking?: boolean;
|
|
427
|
+
/** What the Artifact Agent the running `artifact` call waits for is doing, as it last said. */
|
|
428
|
+
artifactActivity?: AgentActivity;
|
|
379
429
|
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
380
430
|
drafts?: DraftCardStates;
|
|
381
431
|
};
|
|
@@ -387,8 +437,6 @@ type ThreadStep = {
|
|
|
387
437
|
type: "editor";
|
|
388
438
|
sender: ThreadSender;
|
|
389
439
|
text: string;
|
|
390
|
-
/** The draft the message went to, as the agent on Live handed the request off. */
|
|
391
|
-
handedOffTo?: string;
|
|
392
440
|
} | {
|
|
393
441
|
type: "connectedAgent";
|
|
394
442
|
member: ThreadSender;
|
|
@@ -566,7 +614,7 @@ export declare const editorTextOf: (content: Message["content"]) => string;
|
|
|
566
614
|
* run is over, or a later message started another run after its last model call came back
|
|
567
615
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
568
616
|
*/
|
|
569
|
-
export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, drafts }: ThreadState): ThreadView;
|
|
617
|
+
export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, artifactActivity, drafts }: ThreadState): ThreadView;
|
|
570
618
|
/** Folds one of Pi's agent events into the thread's state. */
|
|
571
619
|
export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
|
|
572
620
|
/** Folds a batch of Pi's agent events into the thread's state. */
|
package/dist/threads.js
CHANGED
|
@@ -44,6 +44,13 @@ const BRIEF_ENTRY = "camox.brief";
|
|
|
44
44
|
*/
|
|
45
45
|
const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
46
46
|
/**
|
|
47
|
+
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
48
|
+
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
49
|
+
* data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
|
|
50
|
+
* Everything before it is left out of both.
|
|
51
|
+
*/
|
|
52
|
+
const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
53
|
+
/**
|
|
47
54
|
* The kind of the transcript entry recording a member's push to the draft's code from their own
|
|
48
55
|
* checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
|
|
49
56
|
* answering it, and editors see who pushed, their commits, and whether they built.
|
|
@@ -65,6 +72,22 @@ const BUILD_TOOL = "build";
|
|
|
65
72
|
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
66
73
|
const LOOK_TOOL = "look";
|
|
67
74
|
/**
|
|
75
|
+
* The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
|
|
76
|
+
* it in a conversation of its own and answers the agent (ADR 0035).
|
|
77
|
+
*/
|
|
78
|
+
const ARTIFACT_TOOL = "artifact";
|
|
79
|
+
/**
|
|
80
|
+
* The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
|
|
81
|
+
* Agent's own conversation, which editors never see: who the run acts for and the operation ids
|
|
82
|
+
* of its messages, as data (`artifactRequestDataSchema`).
|
|
83
|
+
*/
|
|
84
|
+
const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
|
|
85
|
+
/** What an Artifact Agent's request records: the member the run acts for, and its messages. */
|
|
86
|
+
const artifactRequestDataSchema = z.object({
|
|
87
|
+
member: z.string(),
|
|
88
|
+
operationIds: z.array(z.string())
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
68
91
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
69
92
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
70
93
|
* keeps this in step with the tools themselves.
|
|
@@ -132,14 +155,8 @@ const pushDataSchema = z.object({
|
|
|
132
155
|
subjects: z.array(z.string()),
|
|
133
156
|
outcome: pushOutcomeSchema
|
|
134
157
|
});
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
138
|
-
*/
|
|
139
|
-
const editorMessageDataSchema = threadSenderSchema.extend({
|
|
140
|
-
operationId: z.string().optional(),
|
|
141
|
-
handedOffTo: z.string().optional()
|
|
142
|
-
});
|
|
158
|
+
/** What an editor's message entry records: who sent it, and the operation id it was sent with. */
|
|
159
|
+
const editorMessageDataSchema = threadSenderSchema.extend({ operationId: z.string().optional() });
|
|
143
160
|
/**
|
|
144
161
|
* A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
|
|
145
162
|
* the run that started it: the studio that sent one of them enters the draft, if it still shows
|
|
@@ -193,6 +210,37 @@ const touchedBatchSchema = z.object({
|
|
|
193
210
|
operationIds: z.array(z.string()),
|
|
194
211
|
touches: z.array(touchSchema)
|
|
195
212
|
});
|
|
213
|
+
/**
|
|
214
|
+
* What the agent is doing during a run, as editors see it, without the details of how it works:
|
|
215
|
+
* - `thinking`: the model is called, and hasn't started writing a tool call.
|
|
216
|
+
* - `exploring`: it uses tools that only read, from writing a call until the model is called again.
|
|
217
|
+
* - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
|
|
218
|
+
* or, in a code mode script, as soon as the script calls one.
|
|
219
|
+
* - `building`: its `build` call runs, or the end of the turn builds its code changes.
|
|
220
|
+
* - `checking`: it looks at what it designed, from writing a `look` call until the model is
|
|
221
|
+
* called again.
|
|
222
|
+
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
223
|
+
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
224
|
+
*/
|
|
225
|
+
const agentActivitySchema = z.enum([
|
|
226
|
+
"thinking",
|
|
227
|
+
"exploring",
|
|
228
|
+
"changing",
|
|
229
|
+
"building",
|
|
230
|
+
"checking",
|
|
231
|
+
"fixing",
|
|
232
|
+
"startingDraft"
|
|
233
|
+
]);
|
|
234
|
+
/**
|
|
235
|
+
* What an `artifact` call's result carries for the studio: the Artifact Agent's answer, whether
|
|
236
|
+
* it's the agent's answer to the turn too, which then ends on it, and while it works, what the
|
|
237
|
+
* Artifact Agent is doing.
|
|
238
|
+
*/
|
|
239
|
+
const artifactDetailsSchema = z.object({
|
|
240
|
+
answer: z.string().optional(),
|
|
241
|
+
final: z.boolean().optional(),
|
|
242
|
+
activity: agentActivitySchema.optional()
|
|
243
|
+
});
|
|
196
244
|
const EMPTY_THREAD_VIEW = {
|
|
197
245
|
messages: [],
|
|
198
246
|
running: false
|
|
@@ -307,19 +355,23 @@ function threadStepOf(entry) {
|
|
|
307
355
|
...push
|
|
308
356
|
};
|
|
309
357
|
}
|
|
358
|
+
if (entry.kind === "camox.draft-card") {
|
|
359
|
+
const card = startedDraftCardSchema.safeParse(entry.data).data;
|
|
360
|
+
return card && {
|
|
361
|
+
id,
|
|
362
|
+
type: "draft",
|
|
363
|
+
...card
|
|
364
|
+
};
|
|
365
|
+
}
|
|
310
366
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
311
367
|
if (!message) return void 0;
|
|
312
368
|
if (message.role === "user") {
|
|
313
|
-
if (entry.kind === "camox.editor-message") {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
text: editorTextOf(message.content),
|
|
320
|
-
...handedOffTo && { handedOffTo }
|
|
321
|
-
};
|
|
322
|
-
}
|
|
369
|
+
if (entry.kind === "camox.editor-message") return sender && {
|
|
370
|
+
id,
|
|
371
|
+
type: "editor",
|
|
372
|
+
sender,
|
|
373
|
+
text: editorTextOf(message.content)
|
|
374
|
+
};
|
|
323
375
|
if (entry.kind === "camox.connected-agent-message") {
|
|
324
376
|
const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
|
|
325
377
|
return data && {
|
|
@@ -353,6 +405,14 @@ function threadStepOf(entry) {
|
|
|
353
405
|
}
|
|
354
406
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
355
407
|
if (message.role === "toolResult") {
|
|
408
|
+
if (message.toolName === "artifact" && !message.isError) {
|
|
409
|
+
const details = artifactDetailsSchema.safeParse(message.details).data;
|
|
410
|
+
if (details?.final && details.answer) return {
|
|
411
|
+
id,
|
|
412
|
+
type: "answer",
|
|
413
|
+
text: details.answer
|
|
414
|
+
};
|
|
415
|
+
}
|
|
356
416
|
const card = message.toolName === "startDraft" && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
|
|
357
417
|
return card ? {
|
|
358
418
|
id,
|
|
@@ -373,7 +433,7 @@ function threadStepOf(entry) {
|
|
|
373
433
|
* run is over, or a later message started another run after its last model call came back
|
|
374
434
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
375
435
|
*/
|
|
376
|
-
function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, drafts = {} }) {
|
|
436
|
+
function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, artifactActivity, drafts = {} }) {
|
|
377
437
|
const messages = [];
|
|
378
438
|
let waiting;
|
|
379
439
|
let unfinished = false;
|
|
@@ -395,17 +455,7 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
395
455
|
endTurn();
|
|
396
456
|
};
|
|
397
457
|
for (const step of steps) switch (step.type) {
|
|
398
|
-
case "editor":
|
|
399
|
-
const card = step.handedOffTo ? messages.findLastIndex((message) => message.from === "draft" && message.draftId === step.handedOffTo) : -1;
|
|
400
|
-
if (card !== -1) {
|
|
401
|
-
messages.splice(card, 0, {
|
|
402
|
-
id: step.id,
|
|
403
|
-
from: "editor",
|
|
404
|
-
sender: step.sender,
|
|
405
|
-
text: step.text
|
|
406
|
-
});
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
458
|
+
case "editor":
|
|
409
459
|
if (unfinished) fail();
|
|
410
460
|
messages.push({
|
|
411
461
|
id: step.id,
|
|
@@ -415,7 +465,6 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
415
465
|
});
|
|
416
466
|
waiting = step.id;
|
|
417
467
|
break;
|
|
418
|
-
}
|
|
419
468
|
case "connectedAgent":
|
|
420
469
|
if (unfinished) fail();
|
|
421
470
|
messages.push({
|
|
@@ -512,14 +561,16 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
512
561
|
toolCalls,
|
|
513
562
|
callingTools,
|
|
514
563
|
changing,
|
|
515
|
-
checking
|
|
564
|
+
checking,
|
|
565
|
+
artifactActivity
|
|
516
566
|
})
|
|
517
567
|
};
|
|
518
568
|
}
|
|
519
569
|
/** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
|
|
520
|
-
function activityOf({ status, toolCalls, callingTools, changing, checking }) {
|
|
570
|
+
function activityOf({ status, toolCalls, callingTools, changing, checking, artifactActivity }) {
|
|
521
571
|
const calling = (name) => toolCalls.some((call) => call.name === name);
|
|
522
572
|
if (status === "building" || calling("build")) return "building";
|
|
573
|
+
if (calling("artifact")) return artifactActivity ?? "changing";
|
|
523
574
|
if (checking) return "checking";
|
|
524
575
|
if (status === "fixing") return "fixing";
|
|
525
576
|
if (calling("startDraft")) return "startingDraft";
|
|
@@ -533,8 +584,22 @@ const changes = (name) => CHANGING_TOOLS.has(name);
|
|
|
533
584
|
const callsChangingTool = (block) => block.type === "toolCall" && changes(block.name);
|
|
534
585
|
/** Whether a block of the model's message is a call of `look`. */
|
|
535
586
|
const callsLook = (block) => block.type === "toolCall" && block.name === "look";
|
|
587
|
+
/**
|
|
588
|
+
* Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
|
|
589
|
+
* the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
|
|
590
|
+
* snapshots do.
|
|
591
|
+
*/
|
|
536
592
|
const append = (state, entry) => {
|
|
537
|
-
|
|
593
|
+
if (!entry) return state;
|
|
594
|
+
const step = threadStepOf(entry);
|
|
595
|
+
if (entry.head !== void 0) {
|
|
596
|
+
const head = Number(entry.head);
|
|
597
|
+
const kept = state.steps.filter((known) => Number(known.id) >= head && known.id !== String(entry.id));
|
|
598
|
+
return {
|
|
599
|
+
...state,
|
|
600
|
+
steps: step ? [step, ...kept] : kept
|
|
601
|
+
};
|
|
602
|
+
}
|
|
538
603
|
if (!step || state.steps.some((known) => known.id === step.id)) return state;
|
|
539
604
|
return {
|
|
540
605
|
...state,
|
|
@@ -550,9 +615,11 @@ function reduceThreadState(state, event) {
|
|
|
550
615
|
name: tool.name
|
|
551
616
|
}]);
|
|
552
617
|
const generated = event.generation?.message?.content ?? [];
|
|
618
|
+
const artifact = event.tools.find((tool) => tool.name === "artifact" && tool.status !== "done");
|
|
553
619
|
return {
|
|
554
620
|
...state,
|
|
555
621
|
steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
|
|
622
|
+
artifactActivity: artifactDetailsSchema.safeParse(artifact?.details).data?.activity,
|
|
556
623
|
running: event.run !== void 0,
|
|
557
624
|
toolCalls,
|
|
558
625
|
callingTools: generated.some((part) => part.type === "toolCall"),
|
|
@@ -570,7 +637,8 @@ function reduceThreadState(state, event) {
|
|
|
570
637
|
toolCalls: [],
|
|
571
638
|
callingTools: false,
|
|
572
639
|
changing: false,
|
|
573
|
-
checking: false
|
|
640
|
+
checking: false,
|
|
641
|
+
artifactActivity: void 0
|
|
574
642
|
};
|
|
575
643
|
case "message_start": return event.message.role === "assistant" ? {
|
|
576
644
|
...state,
|
|
@@ -604,9 +672,18 @@ function reduceThreadState(state, event) {
|
|
|
604
672
|
}]
|
|
605
673
|
};
|
|
606
674
|
}
|
|
675
|
+
case "tool_execution_update": {
|
|
676
|
+
if (event.toolName !== "artifact" || event.parentToolCallId !== void 0) return state;
|
|
677
|
+
const activity = artifactDetailsSchema.safeParse(event.details).data?.activity;
|
|
678
|
+
return activity ? {
|
|
679
|
+
...state,
|
|
680
|
+
artifactActivity: activity
|
|
681
|
+
} : state;
|
|
682
|
+
}
|
|
607
683
|
case "tool_execution_end": return append({
|
|
608
684
|
...state,
|
|
609
|
-
toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId)
|
|
685
|
+
toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
|
|
686
|
+
...event.toolName === "artifact" && event.parentToolCallId === void 0 && { artifactActivity: void 0 }
|
|
610
687
|
}, event.entry);
|
|
611
688
|
case "message_end":
|
|
612
689
|
case "entry_appended": return append(state, event.entry);
|
|
@@ -628,4 +705,4 @@ function reduceThreadFrame(state, frame) {
|
|
|
628
705
|
}
|
|
629
706
|
|
|
630
707
|
//#endregion
|
|
631
|
-
export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
|
708
|
+
export { ARTIFACT_REQUEST_ENTRY, ARTIFACT_TOOL, BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_CARD_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, agentActivitySchema, artifactDetailsSchema, artifactRequestDataSchema, buildStatusDataSchema, buildStatusSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
package/package.json
CHANGED
package/src/threads.ts
CHANGED
|
@@ -57,6 +57,14 @@ export const BRIEF_ENTRY = "camox.brief";
|
|
|
57
57
|
*/
|
|
58
58
|
export const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
59
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
|
+
|
|
60
68
|
/**
|
|
61
69
|
* The kind of the transcript entry recording a member's push to the draft's code from their own
|
|
62
70
|
* checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
|
|
@@ -80,6 +88,26 @@ export const BUILD_TOOL = "build";
|
|
|
80
88
|
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
81
89
|
export const LOOK_TOOL = "look";
|
|
82
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
|
+
|
|
83
111
|
/**
|
|
84
112
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
85
113
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
@@ -172,13 +200,9 @@ export const pushDataSchema = z.object({
|
|
|
172
200
|
});
|
|
173
201
|
export type PushData = z.infer<typeof pushDataSchema>;
|
|
174
202
|
|
|
175
|
-
/**
|
|
176
|
-
* What an editor's message entry records: who sent it, the operation id it was sent with, and for
|
|
177
|
-
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
178
|
-
*/
|
|
203
|
+
/** What an editor's message entry records: who sent it, and the operation id it was sent with. */
|
|
179
204
|
export const editorMessageDataSchema = threadSenderSchema.extend({
|
|
180
205
|
operationId: z.string().optional(),
|
|
181
|
-
handedOffTo: z.string().optional(),
|
|
182
206
|
});
|
|
183
207
|
|
|
184
208
|
/**
|
|
@@ -284,14 +308,28 @@ export type ThreadMessage =
|
|
|
284
308
|
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
285
309
|
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
286
310
|
*/
|
|
287
|
-
export
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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>;
|
|
295
333
|
|
|
296
334
|
/** Everything the studio shows for a thread. */
|
|
297
335
|
export type ThreadView = {
|
|
@@ -323,6 +361,8 @@ export type ThreadState = {
|
|
|
323
361
|
changing?: boolean;
|
|
324
362
|
/** Whether the model's latest round calls `look`: from writing the call until it's called again. */
|
|
325
363
|
checking?: boolean;
|
|
364
|
+
/** What the Artifact Agent the running `artifact` call waits for is doing, as it last said. */
|
|
365
|
+
artifactActivity?: AgentActivity;
|
|
326
366
|
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
327
367
|
drafts?: DraftCardStates;
|
|
328
368
|
};
|
|
@@ -331,13 +371,7 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
|
|
|
331
371
|
|
|
332
372
|
/** One transcript entry, as far as the studio's view is concerned. */
|
|
333
373
|
type ThreadStep = { id: string } & (
|
|
334
|
-
| {
|
|
335
|
-
type: "editor";
|
|
336
|
-
sender: ThreadSender;
|
|
337
|
-
text: string;
|
|
338
|
-
/** The draft the message went to, as the agent on Live handed the request off. */
|
|
339
|
-
handedOffTo?: string;
|
|
340
|
-
}
|
|
374
|
+
| { type: "editor"; sender: ThreadSender; text: string }
|
|
341
375
|
| { type: "connectedAgent"; member: ThreadSender; client: ConnectedAgentClient; text: string }
|
|
342
376
|
/** Camox starting a turn of its own, saying why. */
|
|
343
377
|
| { type: "camox"; text: string }
|
|
@@ -495,20 +529,16 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
495
529
|
const push = pushDataSchema.safeParse(entry.data).data;
|
|
496
530
|
return push && { id, type: "push", ...push };
|
|
497
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
|
+
}
|
|
498
537
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
499
538
|
if (!message) return undefined;
|
|
500
539
|
if (message.role === "user") {
|
|
501
540
|
if (entry.kind === EDITOR_MESSAGE_ENTRY) {
|
|
502
|
-
|
|
503
|
-
return (
|
|
504
|
-
sender && {
|
|
505
|
-
id,
|
|
506
|
-
type: "editor",
|
|
507
|
-
sender,
|
|
508
|
-
text: editorTextOf(message.content),
|
|
509
|
-
...(handedOffTo && { handedOffTo }),
|
|
510
|
-
}
|
|
511
|
-
);
|
|
541
|
+
return sender && { id, type: "editor", sender, text: editorTextOf(message.content) };
|
|
512
542
|
}
|
|
513
543
|
if (entry.kind === CONNECTED_AGENT_MESSAGE_ENTRY) {
|
|
514
544
|
const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
|
|
@@ -533,6 +563,11 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
533
563
|
}
|
|
534
564
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
535
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
|
+
}
|
|
536
571
|
const card =
|
|
537
572
|
message.toolName === START_DRAFT_TOOL && !message.isError
|
|
538
573
|
? startedDraftCardSchema.safeParse(message.details).data
|
|
@@ -558,6 +593,7 @@ export function threadViewOf({
|
|
|
558
593
|
callingTools = false,
|
|
559
594
|
changing = false,
|
|
560
595
|
checking = false,
|
|
596
|
+
artifactActivity,
|
|
561
597
|
drafts = {},
|
|
562
598
|
}: ThreadState): ThreadView {
|
|
563
599
|
const messages: ThreadMessage[] = [];
|
|
@@ -583,24 +619,7 @@ export function threadViewOf({
|
|
|
583
619
|
|
|
584
620
|
for (const step of steps) {
|
|
585
621
|
switch (step.type) {
|
|
586
|
-
case "editor":
|
|
587
|
-
// A message sent as the agent on Live handed the request off went to the draft with it:
|
|
588
|
-
// it belongs to the turn ending on that draft's card, which it shows just before. Pi
|
|
589
|
-
// placed it after the card, which leaves the transcript the model read as it was.
|
|
590
|
-
const card = step.handedOffTo
|
|
591
|
-
? messages.findLastIndex(
|
|
592
|
-
(message) => message.from === "draft" && message.draftId === step.handedOffTo,
|
|
593
|
-
)
|
|
594
|
-
: -1;
|
|
595
|
-
if (card !== -1) {
|
|
596
|
-
messages.splice(card, 0, {
|
|
597
|
-
id: step.id,
|
|
598
|
-
from: "editor",
|
|
599
|
-
sender: step.sender,
|
|
600
|
-
text: step.text,
|
|
601
|
-
});
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
622
|
+
case "editor":
|
|
604
623
|
// Pi places a steering message only once a model call is settled: after a round of tool
|
|
605
624
|
// calls, or with the answer ending the run. Never between a failed call and its retry, so
|
|
606
625
|
// an editor's message after an unfinished call means that call's turn is over.
|
|
@@ -608,7 +627,6 @@ export function threadViewOf({
|
|
|
608
627
|
messages.push({ id: step.id, from: "editor", sender: step.sender, text: step.text });
|
|
609
628
|
waiting = step.id;
|
|
610
629
|
break;
|
|
611
|
-
}
|
|
612
630
|
case "connectedAgent":
|
|
613
631
|
// Like an editor's message: it starts a turn, or steers the one going.
|
|
614
632
|
if (unfinished) fail();
|
|
@@ -691,7 +709,7 @@ export function threadViewOf({
|
|
|
691
709
|
return {
|
|
692
710
|
messages,
|
|
693
711
|
running,
|
|
694
|
-
activity: activityOf({ status, toolCalls, callingTools, changing, checking }),
|
|
712
|
+
activity: activityOf({ status, toolCalls, callingTools, changing, checking, artifactActivity }),
|
|
695
713
|
};
|
|
696
714
|
}
|
|
697
715
|
|
|
@@ -702,17 +720,21 @@ function activityOf({
|
|
|
702
720
|
callingTools,
|
|
703
721
|
changing,
|
|
704
722
|
checking,
|
|
723
|
+
artifactActivity,
|
|
705
724
|
}: {
|
|
706
725
|
status: BuildStatus | undefined;
|
|
707
726
|
toolCalls: NonNullable<ThreadState["toolCalls"]>;
|
|
708
727
|
callingTools: boolean;
|
|
709
728
|
changing: boolean;
|
|
710
729
|
checking: boolean;
|
|
730
|
+
artifactActivity: AgentActivity | undefined;
|
|
711
731
|
}): AgentActivity {
|
|
712
732
|
const calling = (name: string) => toolCalls.some((call) => call.name === name);
|
|
713
733
|
// Fixing a build error takes thinking, reading and writing, which all read as fixing, but its
|
|
714
734
|
// builds show as building.
|
|
715
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";
|
|
716
738
|
if (checking) return "checking";
|
|
717
739
|
if (status === "fixing") return "fixing";
|
|
718
740
|
if (calling(START_DRAFT_TOOL)) return "startingDraft";
|
|
@@ -732,8 +754,21 @@ const callsChangingTool = (block: AssistantMessage["content"][number]) =>
|
|
|
732
754
|
const callsLook = (block: AssistantMessage["content"][number]) =>
|
|
733
755
|
block.type === "toolCall" && block.name === LOOK_TOOL;
|
|
734
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
|
+
*/
|
|
735
762
|
const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
|
|
736
|
-
|
|
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
|
+
}
|
|
737
772
|
if (!step || state.steps.some((known) => known.id === step.id)) return state;
|
|
738
773
|
return { ...state, steps: [...state.steps, step] };
|
|
739
774
|
};
|
|
@@ -746,9 +781,13 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
746
781
|
tool.status === "done" ? [] : [{ callId: tool.callId, name: tool.name }],
|
|
747
782
|
);
|
|
748
783
|
const generated = event.generation?.message?.content ?? [];
|
|
784
|
+
const artifact = event.tools.find(
|
|
785
|
+
(tool) => tool.name === ARTIFACT_TOOL && tool.status !== "done",
|
|
786
|
+
);
|
|
749
787
|
return {
|
|
750
788
|
...state,
|
|
751
789
|
steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
|
|
790
|
+
artifactActivity: artifactDetailsSchema.safeParse(artifact?.details).data?.activity,
|
|
752
791
|
running: event.run !== undefined,
|
|
753
792
|
toolCalls,
|
|
754
793
|
callingTools: generated.some((part) => part.type === "toolCall"),
|
|
@@ -778,6 +817,7 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
778
817
|
callingTools: false,
|
|
779
818
|
changing: false,
|
|
780
819
|
checking: false,
|
|
820
|
+
artifactActivity: undefined,
|
|
781
821
|
};
|
|
782
822
|
case "message_start":
|
|
783
823
|
// The model is called again: it thinks until it writes a tool call.
|
|
@@ -811,11 +851,18 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
|
|
|
811
851
|
toolCalls: [...(state.toolCalls ?? []), { callId: event.toolCallId, name: event.toolName }],
|
|
812
852
|
};
|
|
813
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
|
+
}
|
|
814
859
|
case "tool_execution_end":
|
|
815
860
|
return append(
|
|
816
861
|
{
|
|
817
862
|
...state,
|
|
818
863
|
toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
|
|
864
|
+
...(event.toolName === ARTIFACT_TOOL &&
|
|
865
|
+
event.parentToolCallId === undefined && { artifactActivity: undefined }),
|
|
819
866
|
},
|
|
820
867
|
event.entry,
|
|
821
868
|
);
|