@camox/api-contract 0.58.1 → 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/threads.d.ts CHANGED
@@ -5,7 +5,9 @@ import { Message } from "@earendil-works/pi-ai";
5
5
  //#region src/threads.d.ts
6
6
  /**
7
7
  * The kind of the transcript entry recording an editor's message, with its sender and the
8
- * operation id the studio sent it with as data (`editorMessageDataSchema`).
8
+ * operation id the studio sent it with as data (`editorMessageDataSchema`). Its first text part
9
+ * is what the editor wrote; a second one, which only the model reads, says where they were in the
10
+ * studio as they sent it (`editorTextOf`).
9
11
  */
10
12
  export declare const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
11
13
  /** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
@@ -34,11 +36,32 @@ export declare const BRIEF_ENTRY = "camox.brief";
34
36
  * it at the end of the conversation; editors never see it, since the draft's card says so.
35
37
  */
36
38
  export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
39
+ /**
40
+ * The kind of the transcript entry recording a member's push to the draft's code from their own
41
+ * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
42
+ * answering it, and editors see who pushed, their commits, and whether they built.
43
+ */
44
+ export declare const PUSH_ENTRY = "camox.push";
45
+ /** How a pushed head's build ended: built, built with type errors, or built but not previewed. */
46
+ export declare const pushOutcomeSchema: z.ZodEnum<{
47
+ built: "built";
48
+ typeErrors: "typeErrors";
49
+ notPreviewed: "notPreviewed";
50
+ }>;
51
+ export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
37
52
  /**
38
53
  * The agent's tool on Live handing a request off to a draft it starts (ADR 0025). A run whose
39
54
  * call of it went through ends on the draft's card.
40
55
  */
41
56
  export declare const START_DRAFT_TOOL = "startDraft";
57
+ /** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
58
+ export declare const BUILD_TOOL = "build";
59
+ /**
60
+ * The tools whose calls change a draft: the content tools that write, and the code tools that
61
+ * write files. The model calls the code tools, and code mode scripts call both. A test in the API
62
+ * keeps this in step with the tools themselves.
63
+ */
64
+ export declare const CHANGING_TOOLS: ReadonlySet<string>;
42
65
  /** The draft a `startDraft` call started, as its result's details name it for the card. */
43
66
  export declare const startedDraftCardSchema: z.ZodObject<{
44
67
  draftId: z.ZodString;
@@ -80,6 +103,21 @@ export declare const threadSenderSchema: z.ZodObject<{
80
103
  export type ThreadSender = z.infer<typeof threadSenderSchema>;
81
104
  /** Whether `data` names a sender. */
82
105
  export declare const isSender: (data: unknown) => data is ThreadSender;
106
+ /** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
107
+ export declare const pushDataSchema: z.ZodObject<{
108
+ pusher: z.ZodObject<{
109
+ id: z.ZodString;
110
+ name: z.ZodString;
111
+ image: z.ZodNullable<z.ZodString>;
112
+ }, z.core.$strip>;
113
+ subjects: z.ZodArray<z.ZodString>;
114
+ outcome: z.ZodEnum<{
115
+ built: "built";
116
+ typeErrors: "typeErrors";
117
+ notPreviewed: "notPreviewed";
118
+ }>;
119
+ }, z.core.$strip>;
120
+ export type PushData = z.infer<typeof pushDataSchema>;
83
121
  /**
84
122
  * What an editor's message entry records: who sent it, the operation id it was sent with, and for
85
123
  * a message sent on Live as the agent handed the request off, the draft it went to with it.
@@ -233,22 +271,37 @@ export type ThreadMessage = {
233
271
  id: string;
234
272
  from: "draft";
235
273
  ended?: DraftOutcome;
236
- } & StartedDraftCard);
274
+ } & StartedDraftCard) |
275
+ /**
276
+ * A member's push to the draft's code from their own checkout: who pushed, their commits'
277
+ * subjects, oldest first, and how the pushed head built. It's no turn: the agent doesn't
278
+ * answer it.
279
+ */
280
+ {
281
+ id: string;
282
+ from: "push";
283
+ sender: ThreadSender;
284
+ subjects: string[];
285
+ outcome: PushOutcome;
286
+ };
287
+ /**
288
+ * What the agent is doing during a run, as editors see it, without the details of how it works:
289
+ * - `thinking`: the model is called, and hasn't started writing a tool call.
290
+ * - `exploring`: it uses tools that only read, from writing a call until the model is called again.
291
+ * - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
292
+ * or, in a code mode script, as soon as the script calls one.
293
+ * - `building`: its `build` call runs, or the end of the turn builds its code changes.
294
+ * - `fixing`: the end of the turn's build failed, and the agent fixes it.
295
+ * - `startingDraft`: on Live, its `startDraft` call runs.
296
+ */
297
+ export type AgentActivity = "thinking" | "exploring" | "changing" | "building" | "fixing" | "startingDraft";
237
298
  /** Everything the studio shows for a thread. */
238
299
  export type ThreadView = {
239
300
  messages: readonly ThreadMessage[];
240
301
  /** Whether a run is going: the agent is working on a turn, and stopping would end it. */
241
302
  running: boolean;
242
- /**
243
- * While a run is going, where the build of the code its turn changed stands: the end of the
244
- * turn is building it, or it failed and the agent is fixing it.
245
- */
246
- status?: BuildStatus;
247
- /**
248
- * While a run is going, whether the agent is using its tools, code mode included, rather than
249
- * thinking: from writing a call until the model is called again.
250
- */
251
- usingTools?: true;
303
+ /** While a run is going, what the agent is doing. */
304
+ activity?: AgentActivity;
252
305
  };
253
306
  export declare const EMPTY_THREAD_VIEW: ThreadView;
254
307
  /**
@@ -259,10 +312,18 @@ export declare const EMPTY_THREAD_VIEW: ThreadView;
259
312
  export type ThreadState = {
260
313
  steps: readonly ThreadStep[];
261
314
  running: boolean;
262
- /** The model's calls still running, by call id. A tool's own calls aren't the agent's. */
263
- toolCalls?: readonly string[];
315
+ /** The model's calls still running. A tool's own calls aren't the agent's. */
316
+ toolCalls?: readonly {
317
+ callId: string;
318
+ name: string;
319
+ }[];
264
320
  /** Whether the model's latest message makes tool calls: it writes them, then they run. */
265
321
  callingTools?: boolean;
322
+ /**
323
+ * Whether a call of the latest round changes the draft, the model's or a script's: from writing
324
+ * or making it until the model is called again.
325
+ */
326
+ changing?: boolean;
266
327
  /** The drafts the thread's cards name, as the thread last looked them up. */
267
328
  drafts?: DraftCardStates;
268
329
  };
@@ -305,6 +366,10 @@ type ThreadStep = {
305
366
  type: "build";
306
367
  status: BuildStatus;
307
368
  } |
369
+ /** A member's push, between turns. */
370
+ ({
371
+ type: "push";
372
+ } & PushData) |
308
373
  /**
309
374
  * The end of the turn sent the agent back to work, after its answer: the answer wasn't the
310
375
  * turn's last, and what the agent was told is never shown.
@@ -312,11 +377,68 @@ type ThreadStep = {
312
377
  {
313
378
  type: "continued";
314
379
  });
380
+ /**
381
+ * What an editor had selected on the page as they sent a message, unless they cleared it from
382
+ * the message: its path as the studio names it, from the block down to the selection, with the
383
+ * ids the agent's tools take. A record is the one the selection shows, a field the selection's
384
+ * own, of the record when a record is selected.
385
+ */
386
+ export declare const editorSelectionSchema: z.ZodObject<{
387
+ path: z.ZodArray<z.ZodString>;
388
+ blockId: z.ZodOptional<z.ZodString>;
389
+ itemId: z.ZodOptional<z.ZodString>;
390
+ recordId: z.ZodOptional<z.ZodString>;
391
+ fieldName: z.ZodOptional<z.ZodString>;
392
+ }, z.core.$strip>;
393
+ export type EditorSelection = z.infer<typeof editorSelectionSchema>;
394
+ /**
395
+ * Where an editor was in the studio as they sent a message, in the Locale they viewed: a Page,
396
+ * by its path after the Locale's prefix and its title, with its id for a curated page and its
397
+ * Layout's for a layout page, and what they had selected on it, or the Canvas with no page
398
+ * selected. A hint for the agent, never what the message is about.
399
+ */
400
+ export declare const editorWhereSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
401
+ on: z.ZodLiteral<"page">;
402
+ locale: z.ZodString;
403
+ path: z.ZodString;
404
+ title: z.ZodString;
405
+ pageId: z.ZodOptional<z.ZodString>;
406
+ layoutId: z.ZodOptional<z.ZodString>;
407
+ selection: z.ZodOptional<z.ZodObject<{
408
+ path: z.ZodArray<z.ZodString>;
409
+ blockId: z.ZodOptional<z.ZodString>;
410
+ itemId: z.ZodOptional<z.ZodString>;
411
+ recordId: z.ZodOptional<z.ZodString>;
412
+ fieldName: z.ZodOptional<z.ZodString>;
413
+ }, z.core.$strip>>;
414
+ }, z.core.$strip>, z.ZodObject<{
415
+ on: z.ZodLiteral<"canvas">;
416
+ locale: z.ZodString;
417
+ }, z.core.$strip>], "on">;
418
+ export type EditorWhere = z.infer<typeof editorWhereSchema>;
315
419
  /** Studio → thread. */
316
420
  export declare const threadClientMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
317
421
  type: z.ZodLiteral<"send">;
318
422
  operationId: z.ZodString;
319
423
  text: z.ZodString;
424
+ where: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
425
+ on: z.ZodLiteral<"page">;
426
+ locale: z.ZodString;
427
+ path: z.ZodString;
428
+ title: z.ZodString;
429
+ pageId: z.ZodOptional<z.ZodString>;
430
+ layoutId: z.ZodOptional<z.ZodString>;
431
+ selection: z.ZodOptional<z.ZodObject<{
432
+ path: z.ZodArray<z.ZodString>;
433
+ blockId: z.ZodOptional<z.ZodString>;
434
+ itemId: z.ZodOptional<z.ZodString>;
435
+ recordId: z.ZodOptional<z.ZodString>;
436
+ fieldName: z.ZodOptional<z.ZodString>;
437
+ }, z.core.$strip>>;
438
+ }, z.core.$strip>, z.ZodObject<{
439
+ on: z.ZodLiteral<"canvas">;
440
+ locale: z.ZodString;
441
+ }, z.core.$strip>], "on">>;
320
442
  }, z.core.$strip>, z.ZodObject<{
321
443
  type: z.ZodLiteral<"stop">;
322
444
  }, z.core.$strip>, z.ZodObject<{
@@ -368,12 +490,16 @@ export type ThreadServerMessage =
368
490
  };
369
491
  /**
370
492
  * The code a thread closes its sockets with once it ended with its draft, and for good: it's
371
- * deleted, so reconnecting reaches nothing. A development draft, which stays open, gets a new
372
- * thread the next time the studio connects.
493
+ * deleted, so reconnecting reaches nothing.
373
494
  */
374
495
  export declare const THREAD_ENDED_CLOSE_CODE = 4000;
375
496
  /** The text of a message's content: its text parts, joined. */
376
497
  export declare const textOf: (content: Message["content"]) => string;
498
+ /**
499
+ * What an editor wrote, of their message entry's content: its first text part. The rest, where
500
+ * they were in the studio, only the model reads.
501
+ */
502
+ export declare const editorTextOf: (content: Message["content"]) => string;
377
503
  /**
378
504
  * What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
379
505
  * when it began the turn itself, and messages sent
@@ -383,7 +509,7 @@ export declare const textOf: (content: Message["content"]) => string;
383
509
  * run is over, or a later message started another run after its last model call came back
384
510
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
385
511
  */
386
- export declare function threadViewOf({ steps, running, toolCalls, callingTools, drafts }: ThreadState): ThreadView;
512
+ export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, drafts }: ThreadState): ThreadView;
387
513
  /** Folds one of Pi's agent events into the thread's state. */
388
514
  export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
389
515
  /** Folds a batch of Pi's agent events into the thread's state. */
package/dist/threads.js CHANGED
@@ -3,7 +3,9 @@ import { z } from "zod";
3
3
  //#region src/threads.ts
4
4
  /**
5
5
  * The kind of the transcript entry recording an editor's message, with its sender and the
6
- * operation id the studio sent it with as data (`editorMessageDataSchema`).
6
+ * operation id the studio sent it with as data (`editorMessageDataSchema`). Its first text part
7
+ * is what the editor wrote; a second one, which only the model reads, says where they were in the
8
+ * studio as they sent it (`editorTextOf`).
7
9
  */
8
10
  const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
9
11
  /** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
@@ -33,10 +35,52 @@ const BRIEF_ENTRY = "camox.brief";
33
35
  */
34
36
  const DRAFT_ENDED_ENTRY = "camox.draft-ended";
35
37
  /**
38
+ * The kind of the transcript entry recording a member's push to the draft's code from their own
39
+ * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
40
+ * answering it, and editors see who pushed, their commits, and whether they built.
41
+ */
42
+ const PUSH_ENTRY = "camox.push";
43
+ /** How a pushed head's build ended: built, built with type errors, or built but not previewed. */
44
+ const pushOutcomeSchema = z.enum([
45
+ "built",
46
+ "typeErrors",
47
+ "notPreviewed"
48
+ ]);
49
+ /**
36
50
  * The agent's tool on Live handing a request off to a draft it starts (ADR 0025). A run whose
37
51
  * call of it went through ends on the draft's card.
38
52
  */
39
53
  const START_DRAFT_TOOL = "startDraft";
54
+ /** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
55
+ const BUILD_TOOL = "build";
56
+ /**
57
+ * The tools whose calls change a draft: the content tools that write, and the code tools that
58
+ * write files. The model calls the code tools, and code mode scripts call both. A test in the API
59
+ * keeps this in step with the tools themselves.
60
+ */
61
+ const CHANGING_TOOLS = /* @__PURE__ */ new Set([
62
+ "addBackToLocale",
63
+ "addLocale",
64
+ "createBlock",
65
+ "createCollectionRecord",
66
+ "createPage",
67
+ "deleteBlock",
68
+ "deleteCollectionRecord",
69
+ "deletePage",
70
+ "editBlock",
71
+ "editCollectionRecord",
72
+ "moveBlock",
73
+ "removeLocale",
74
+ "setPageLayout",
75
+ "setPageSeo",
76
+ "updateFile",
77
+ "updatePage",
78
+ "uploadFile",
79
+ "edit",
80
+ "mv",
81
+ "rm",
82
+ "write"
83
+ ]);
40
84
  /** The draft a `startDraft` call started, as its result's details name it for the card. */
41
85
  const startedDraftCardSchema = z.object({
42
86
  draftId: z.string(),
@@ -53,6 +97,12 @@ const threadSenderSchema = z.object({
53
97
  });
54
98
  /** Whether `data` names a sender. */
55
99
  const isSender = (data) => threadSenderSchema.safeParse(data).success;
100
+ /** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
101
+ const pushDataSchema = z.object({
102
+ pusher: threadSenderSchema,
103
+ subjects: z.array(z.string()),
104
+ outcome: pushOutcomeSchema
105
+ });
56
106
  /**
57
107
  * What an editor's message entry records: who sent it, the operation id it was sent with, and for
58
108
  * a message sent on Live as the agent handed the request off, the draft it went to with it.
@@ -122,24 +172,60 @@ const EMPTY_THREAD_STATE = {
122
172
  steps: [],
123
173
  running: false
124
174
  };
175
+ /**
176
+ * What an editor had selected on the page as they sent a message, unless they cleared it from
177
+ * the message: its path as the studio names it, from the block down to the selection, with the
178
+ * ids the agent's tools take. A record is the one the selection shows, a field the selection's
179
+ * own, of the record when a record is selected.
180
+ */
181
+ const editorSelectionSchema = z.object({
182
+ path: z.array(z.string().max(512)).min(1).max(16),
183
+ blockId: z.string().max(64).optional(),
184
+ itemId: z.string().max(64).optional(),
185
+ recordId: z.string().max(64).optional(),
186
+ fieldName: z.string().max(256).optional()
187
+ });
188
+ /**
189
+ * Where an editor was in the studio as they sent a message, in the Locale they viewed: a Page,
190
+ * by its path after the Locale's prefix and its title, with its id for a curated page and its
191
+ * Layout's for a layout page, and what they had selected on it, or the Canvas with no page
192
+ * selected. A hint for the agent, never what the message is about.
193
+ */
194
+ const editorWhereSchema = z.discriminatedUnion("on", [z.object({
195
+ on: z.literal("page"),
196
+ locale: z.string().max(64),
197
+ path: z.string().max(2048),
198
+ title: z.string().max(512),
199
+ pageId: z.string().max(64).optional(),
200
+ layoutId: z.string().max(256).optional(),
201
+ selection: editorSelectionSchema.optional()
202
+ }), z.object({
203
+ on: z.literal("canvas"),
204
+ locale: z.string().max(64)
205
+ })]);
125
206
  /** Studio → thread. */
126
207
  const threadClientMessageSchema = z.discriminatedUnion("type", [
127
208
  z.object({
128
209
  type: z.literal("send"),
129
210
  operationId: z.string().min(1),
130
- text: z.string().min(1)
211
+ text: z.string().min(1),
212
+ where: editorWhereSchema.optional()
131
213
  }),
132
214
  z.object({ type: z.literal("stop") }),
133
215
  z.object({ type: z.literal("resync") })
134
216
  ]);
135
217
  /**
136
218
  * The code a thread closes its sockets with once it ended with its draft, and for good: it's
137
- * deleted, so reconnecting reaches nothing. A development draft, which stays open, gets a new
138
- * thread the next time the studio connects.
219
+ * deleted, so reconnecting reaches nothing.
139
220
  */
140
221
  const THREAD_ENDED_CLOSE_CODE = 4e3;
141
222
  /** The text of a message's content: its text parts, joined. */
142
223
  const textOf = (content) => typeof content === "string" ? content : content.flatMap((part) => part.type === "text" ? [part.text] : []).join("").trim();
224
+ /**
225
+ * What an editor wrote, of their message entry's content: its first text part. The rest, where
226
+ * they were in the studio, only the model reads.
227
+ */
228
+ const editorTextOf = (content) => typeof content === "string" ? content : (content.find((part) => part.type === "text")?.text ?? "").trim();
143
229
  /** What an assistant message is to the thread: a final answer, a tool round, or neither. */
144
230
  function assistantStep(id, message) {
145
231
  if (message.content.some((part) => part.type === "toolCall")) return {
@@ -184,6 +270,14 @@ function threadStepOf(entry) {
184
270
  };
185
271
  }
186
272
  if (entry.kind === "camox.draft-ended") return void 0;
273
+ if (entry.kind === "camox.push") {
274
+ const push = pushDataSchema.safeParse(entry.data).data;
275
+ return push && {
276
+ id,
277
+ type: "push",
278
+ ...push
279
+ };
280
+ }
187
281
  const message = entry.model?.find((candidate) => candidate.role !== "system");
188
282
  if (!message) return void 0;
189
283
  if (message.role === "user") {
@@ -193,7 +287,7 @@ function threadStepOf(entry) {
193
287
  id,
194
288
  type: "editor",
195
289
  sender,
196
- text: textOf(message.content),
290
+ text: editorTextOf(message.content),
197
291
  ...handedOffTo && { handedOffTo }
198
292
  };
199
293
  }
@@ -237,7 +331,7 @@ function threadStepOf(entry) {
237
331
  * run is over, or a later message started another run after its last model call came back
238
332
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
239
333
  */
240
- function threadViewOf({ steps, running, toolCalls = [], callingTools = false, drafts = {} }) {
334
+ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, drafts = {} }) {
241
335
  const messages = [];
242
336
  let waiting;
243
337
  let unfinished = false;
@@ -338,20 +432,50 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, dr
338
432
  case "build":
339
433
  status = step.status;
340
434
  break;
435
+ case "push":
436
+ messages.push({
437
+ id: step.id,
438
+ from: "push",
439
+ sender: step.pusher,
440
+ subjects: step.subjects,
441
+ outcome: step.outcome
442
+ });
443
+ break;
341
444
  case "tools":
342
445
  unfinished = false;
343
446
  break;
344
447
  case "unfinished": unfinished = true;
345
448
  }
346
449
  if (!running) fail();
347
- const usingTools = running && (callingTools || toolCalls.length > 0);
450
+ if (!running) return {
451
+ messages,
452
+ running
453
+ };
348
454
  return {
349
455
  messages,
350
456
  running,
351
- ...running && status && { status },
352
- ...usingTools && { usingTools }
457
+ activity: activityOf({
458
+ status,
459
+ toolCalls,
460
+ callingTools,
461
+ changing
462
+ })
353
463
  };
354
464
  }
465
+ /** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
466
+ function activityOf({ status, toolCalls, callingTools, changing }) {
467
+ const calling = (name) => toolCalls.some((call) => call.name === name);
468
+ if (status === "building" || calling("build")) return "building";
469
+ if (status === "fixing") return "fixing";
470
+ if (calling("startDraft")) return "startingDraft";
471
+ if (changing) return "changing";
472
+ if (callingTools || toolCalls.length > 0) return "exploring";
473
+ return "thinking";
474
+ }
475
+ /** Whether a tool call changes the draft. */
476
+ const changes = (name) => CHANGING_TOOLS.has(name);
477
+ /** Whether a block of the model's message is a call of a tool that changes the draft. */
478
+ const callsChangingTool = (block) => block.type === "toolCall" && changes(block.name);
355
479
  const append = (state, entry) => {
356
480
  const step = entry && threadStepOf(entry);
357
481
  if (!step || state.steps.some((known) => known.id === step.id)) return state;
@@ -363,13 +487,21 @@ const append = (state, entry) => {
363
487
  /** Folds one of Pi's agent events into the thread's state. */
364
488
  function reduceThreadState(state, event) {
365
489
  switch (event.type) {
366
- case "snapshot": return {
367
- ...state,
368
- steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
369
- running: event.run !== void 0,
370
- toolCalls: event.tools.flatMap((tool) => tool.status === "done" ? [] : [tool.callId]),
371
- callingTools: !!event.generation?.message?.content.some((part) => part.type === "toolCall")
372
- };
490
+ case "snapshot": {
491
+ const toolCalls = event.tools.flatMap((tool) => tool.status === "done" ? [] : [{
492
+ callId: tool.callId,
493
+ name: tool.name
494
+ }]);
495
+ const generated = event.generation?.message?.content ?? [];
496
+ return {
497
+ ...state,
498
+ steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
499
+ running: event.run !== void 0,
500
+ toolCalls,
501
+ callingTools: generated.some((part) => part.type === "toolCall"),
502
+ changing: generated.some(callsChangingTool) || toolCalls.some((call) => changes(call.name)) || event.nestedTools.some((nested) => changes(nested.name) && toolCalls.some((call) => nested.parentCallId === call.callId || nested.parentCallId.startsWith(`${call.callId}/`)))
503
+ };
504
+ }
373
505
  case "run_start": return {
374
506
  ...state,
375
507
  running: true
@@ -378,25 +510,41 @@ function reduceThreadState(state, event) {
378
510
  ...state,
379
511
  running: false,
380
512
  toolCalls: [],
381
- callingTools: false
513
+ callingTools: false,
514
+ changing: false
382
515
  };
383
516
  case "message_start": return event.message.role === "assistant" ? {
384
517
  ...state,
385
- callingTools: false
518
+ callingTools: false,
519
+ changing: false
386
520
  } : state;
387
- case "message_update": return event.changes.some((change) => change.type === "toolcall_start") ? {
388
- ...state,
389
- callingTools: true
390
- } : state;
391
- case "tool_execution_start":
392
- if (event.parentToolCallId !== void 0) return state;
521
+ case "message_update": {
522
+ const calls = event.changes.flatMap((change) => change.type === "toolcall_start" || change.type === "block" ? [change.block] : change.type === "message" ? change.message.content : []);
523
+ if (calls.length === 0) return state;
393
524
  return {
394
525
  ...state,
395
- toolCalls: [...state.toolCalls ?? [], event.toolCallId]
526
+ callingTools: state.callingTools || calls.some((block) => block.type === "toolCall"),
527
+ changing: state.changing || calls.some(callsChangingTool)
528
+ };
529
+ }
530
+ case "tool_execution_start": {
531
+ const changing = state.changing || changes(event.toolName);
532
+ if (event.parentToolCallId !== void 0) return {
533
+ ...state,
534
+ changing
396
535
  };
536
+ return {
537
+ ...state,
538
+ changing,
539
+ toolCalls: [...state.toolCalls ?? [], {
540
+ callId: event.toolCallId,
541
+ name: event.toolName
542
+ }]
543
+ };
544
+ }
397
545
  case "tool_execution_end": return append({
398
546
  ...state,
399
- toolCalls: state.toolCalls?.filter((callId) => callId !== event.toolCallId)
547
+ toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId)
400
548
  }, event.entry);
401
549
  case "message_end":
402
550
  case "entry_appended": return append(state, event.entry);
@@ -418,4 +566,4 @@ function reduceThreadFrame(state, frame) {
418
566
  }
419
567
 
420
568
  //#endregion
421
- export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, isSender, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
569
+ export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camox/api-contract",
3
- "version": "0.58.1",
3
+ "version": "0.60.2",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "src",
package/src/drafts.ts CHANGED
@@ -31,25 +31,10 @@ export const PUBLISHING_DRAFT_VAR = "CAMOX_PUBLISHING_DRAFT";
31
31
  */
32
32
  export const DRAFT_CREATE_HEADER = "x-camox-draft-create";
33
33
 
34
- /**
35
- * The `x-camox-draft` value naming the caller's own development draft in the project a request
36
- * acts on (ADR 0006, ADR 0011). Dev mode and the CLI always send it.
37
- */
38
- export const DEVELOPMENT_DRAFT = "development";
39
-
40
- /**
41
- * oRPC error code for a request naming the development draft before dev mode created it: the
42
- * first dev-mode sync after login does, so the site has to run locally once first.
43
- */
44
- export const NO_DEVELOPMENT_DRAFT_ERROR = "NO_DEVELOPMENT_DRAFT";
45
-
46
34
  /** URL search param carrying the current draft's id in the studio and the rendered site. */
47
35
  export const DRAFT_SEARCH_PARAM = "camox-draft";
48
36
 
49
- /** A regular draft is a named set of edits; a development draft backs a dev server. */
50
- export type DraftKind = "regular" | "development";
51
-
52
- /** How a regular draft ended. */
37
+ /** How a draft ended. */
53
38
  export type DraftOutcome = "published" | "deleted";
54
39
 
55
40
  /** Who ended a draft, how and when, from its ended-drafts record (ADR 0009, ADR 0011). */
@@ -140,7 +125,7 @@ export const LIVE_READ_ONLY_ERROR = "LIVE_READ_ONLY";
140
125
  // for one on Live. Every procedure is listed, so a new one fails the type check until it's
141
126
  // classified and can't be forgotten. Procedures that only fill a generated value (ADR 0012),
142
127
  // like `generateSummary`, write no content. A synced block's shared content is written through
143
- // `blocks` and `repeatableItems`. Releases and dev-mode sync aren't listed.
128
+ // `blocks` and `repeatableItems`. Releases aren't listed.
144
129
  const BLOCK_WRITES: Record<keyof Router["blocks"], boolean> = {
145
130
  get: false,
146
131
  getPageMarkdown: false,
@@ -223,7 +208,7 @@ const LOCALE_WRITES: Record<keyof Router["locales"], boolean> = {
223
208
  addBack: true,
224
209
  };
225
210
 
226
- // `definitions` is absent: its sync is a release or a dev sync, never a studio content write.
211
+ // `definitions` is absent: its sync is a release, never a studio content write.
227
212
  const CONTENT_WRITES: Partial<Record<keyof Router, Record<string, boolean>>> = {
228
213
  blocks: BLOCK_WRITES,
229
214
  locales: LOCALE_WRITES,
package/src/projects.ts CHANGED
@@ -42,8 +42,12 @@ export const projectSlugSchema = /* @__PURE__ */ z
42
42
  "Use lowercase letters, digits and hyphens, without a hyphen at either end",
43
43
  );
44
44
 
45
- /** The address a project's website is served at: its Project Slug's `camox.site` subdomain. */
46
- export const projectWebsiteUrl = (slug: string) => `https://${slug}.camox.site`;
45
+ /**
46
+ * The address a project's website is served at: its Custom Domain when it has one, else its
47
+ * Project Slug's `camox.site` subdomain, which then redirects to it (ADR 0028).
48
+ */
49
+ export const projectWebsiteUrl = (project: { slug: string; customDomain: string | null }) =>
50
+ `https://${project.customDomain ?? `${project.slug}.camox.site`}`;
47
51
 
48
52
  /** Why a slug can't be given to a new project. */
49
53
  export type UnavailableSlugReason = "invalid" | "reserved" | "taken";