veryfront 0.1.232 → 0.1.234

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.
@@ -4,6 +4,34 @@ import { z } from "zod";
4
4
 
5
5
  const AGENT_RUN_API_TIMEOUT_MS = 15_000;
6
6
 
7
+ function createTimedAbortSignal(timeoutMs: number, abortSignal?: AbortSignal) {
8
+ const controller = new AbortController();
9
+ let abortedByCaller = false;
10
+ const timeout = dntShim.setTimeout(() => {
11
+ controller.abort();
12
+ }, timeoutMs);
13
+
14
+ const onAbort = () => {
15
+ abortedByCaller = true;
16
+ controller.abort();
17
+ };
18
+
19
+ if (abortSignal?.aborted) {
20
+ onAbort();
21
+ } else {
22
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
23
+ }
24
+
25
+ return {
26
+ signal: controller.signal,
27
+ wasAbortedByCaller: () => abortedByCaller,
28
+ cleanup: () => {
29
+ clearTimeout(timeout);
30
+ abortSignal?.removeEventListener("abort", onAbort);
31
+ },
32
+ };
33
+ }
34
+
7
35
  export const ConversationRunTargetsSchema = z.object({
8
36
  sourceTargetKind: z.enum(["project", "preview_branch"]).nullable(),
9
37
  runtimeTargetKind: z.enum(["production", "preview_branch"]).nullable(),
@@ -37,6 +65,15 @@ export function resolveConversationRunTargets(input: {
37
65
  );
38
66
  }
39
67
 
68
+ export const ConversationRunStatusSchema = z.enum([
69
+ "pending",
70
+ "running",
71
+ "waiting_for_tool",
72
+ "completed",
73
+ "failed",
74
+ "cancelled",
75
+ ]);
76
+
40
77
  export const ConversationRunProjectionSchema = z
41
78
  .object({
42
79
  runId: z.string().min(1).optional(),
@@ -49,7 +86,7 @@ export const ConversationRunProjectionSchema = z
49
86
  latest_event_id: z.number().int().nonnegative().optional(),
50
87
  latestExternalEventSequence: z.number().int().nonnegative().optional(),
51
88
  latest_external_event_sequence: z.number().int().nonnegative().optional(),
52
- status: z.enum(["pending", "running", "waiting_for_tool", "completed", "failed", "cancelled"]),
89
+ status: ConversationRunStatusSchema,
53
90
  })
54
91
  .passthrough()
55
92
  .transform((data) => {
@@ -112,6 +149,59 @@ export const CompleteConversationRunResponseSchema = z
112
149
  })
113
150
  .passthrough();
114
151
 
152
+ const AppendConversationRunEventsCamelRunSchema = z
153
+ .object({
154
+ runId: z.string().min(1),
155
+ conversationId: z.string().uuid(),
156
+ latestEventId: z.number().int().nonnegative(),
157
+ latestExternalEventSequence: z.number().int().nonnegative(),
158
+ })
159
+ .passthrough();
160
+
161
+ const AppendConversationRunEventsSnakeRunSchema = z
162
+ .object({
163
+ run_id: z.string().min(1),
164
+ conversation_id: z.string().uuid(),
165
+ latest_event_id: z.number().int().nonnegative(),
166
+ latest_external_event_sequence: z.number().int().nonnegative(),
167
+ })
168
+ .passthrough();
169
+
170
+ export const AppendConversationRunEventsResponseSchema = z.union([
171
+ z.object({
172
+ latestEventId: z.number().int().nonnegative(),
173
+ latestExternalEventSequence: z.number().int().nonnegative(),
174
+ appendedCount: z.number().int().nonnegative(),
175
+ run: AppendConversationRunEventsCamelRunSchema,
176
+ }),
177
+ z.object({
178
+ latest_event_id: z.number().int().nonnegative(),
179
+ latest_external_event_sequence: z.number().int().nonnegative(),
180
+ appended_count: z.number().int().nonnegative(),
181
+ run: AppendConversationRunEventsSnakeRunSchema,
182
+ }).transform((data) => ({
183
+ latestEventId: data.latest_event_id,
184
+ latestExternalEventSequence: data.latest_external_event_sequence,
185
+ appendedCount: data.appended_count,
186
+ run: {
187
+ ...data.run,
188
+ runId: data.run.run_id,
189
+ conversationId: data.run.conversation_id,
190
+ latestEventId: data.run.latest_event_id,
191
+ latestExternalEventSequence: data.run.latest_external_event_sequence,
192
+ },
193
+ })),
194
+ ]);
195
+
196
+ export type AppendConversationRunEventsResponse = z.infer<
197
+ typeof AppendConversationRunEventsResponseSchema
198
+ >;
199
+
200
+ const ConversationRunErrorSchema = z.object({
201
+ detail: z.string().min(1).optional(),
202
+ error: z.string().min(1).optional(),
203
+ });
204
+
115
205
  export interface ConversationAgentRunUsage {
116
206
  inputTokens: number;
117
207
  outputTokens: number;
@@ -141,6 +231,71 @@ export interface FinalizeConversationAgentRunInput {
141
231
  terminalErrorMessage?: string | null;
142
232
  }
143
233
 
234
+ export class AppendConversationRunEventsError extends Error {
235
+ readonly status: number;
236
+ readonly detail: string | null;
237
+
238
+ constructor(input: {
239
+ status: number;
240
+ detail?: string | null;
241
+ statusText?: string;
242
+ }) {
243
+ const detail = input.detail?.trim() || input.statusText || `HTTP ${input.status}`;
244
+ super(`Append conversation run events failed (${input.status}): ${detail}`);
245
+ this.name = "AppendConversationRunEventsError";
246
+ this.status = input.status;
247
+ this.detail = input.detail?.trim() || null;
248
+ }
249
+ }
250
+
251
+ export function parseAppendConversationRunEventsErrorBody(bodyText: string): string | null {
252
+ if (!bodyText) {
253
+ return null;
254
+ }
255
+
256
+ try {
257
+ const parsed = ConversationRunErrorSchema.safeParse(JSON.parse(bodyText));
258
+ if (parsed.success) {
259
+ return parsed.data.detail ?? parsed.data.error ?? null;
260
+ }
261
+ } catch {
262
+ return bodyText;
263
+ }
264
+
265
+ return bodyText;
266
+ }
267
+
268
+ export function isIgnorableConversationRunAppendError(
269
+ error: unknown,
270
+ ): error is AppendConversationRunEventsError {
271
+ if (!(error instanceof AppendConversationRunEventsError)) {
272
+ return false;
273
+ }
274
+
275
+ if (error.status === 404) {
276
+ return true;
277
+ }
278
+
279
+ if (error.status !== 400) {
280
+ return false;
281
+ }
282
+
283
+ return (
284
+ error.detail === "Cannot append external events to a terminal run" ||
285
+ error.detail === "Cannot append external events while the run is waiting for a tool result"
286
+ );
287
+ }
288
+
289
+ export function isCursorMismatchConversationRunAppendError(
290
+ error: unknown,
291
+ ): error is AppendConversationRunEventsError {
292
+ return (
293
+ error instanceof AppendConversationRunEventsError &&
294
+ error.status === 400 &&
295
+ error.detail === "External run event cursor mismatch"
296
+ );
297
+ }
298
+
144
299
  async function controlPlaneJson<T>(input: {
145
300
  authToken: string;
146
301
  url: string;
@@ -148,11 +303,13 @@ async function controlPlaneJson<T>(input: {
148
303
  body?: unknown;
149
304
  responseSchema: z.ZodSchema<T>;
150
305
  operation: string;
306
+ abortSignal?: AbortSignal;
151
307
  }): Promise<T> {
152
- const controller = new AbortController();
153
- const timeout = dntShim.setTimeout(() => {
154
- controller.abort();
155
- }, AGENT_RUN_API_TIMEOUT_MS);
308
+ if (input.abortSignal?.aborted) {
309
+ throw new DOMException("This operation was aborted", "AbortError");
310
+ }
311
+
312
+ const timedAbort = createTimedAbortSignal(AGENT_RUN_API_TIMEOUT_MS, input.abortSignal);
156
313
 
157
314
  let response: Response;
158
315
  try {
@@ -163,15 +320,19 @@ async function controlPlaneJson<T>(input: {
163
320
  "Content-Type": "application/json",
164
321
  },
165
322
  ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),
166
- signal: controller.signal,
323
+ signal: timedAbort.signal,
167
324
  });
168
325
  } catch (error) {
169
- if (error instanceof DOMException && error.name === "AbortError") {
326
+ if (
327
+ error instanceof DOMException &&
328
+ error.name === "AbortError" &&
329
+ !timedAbort.wasAbortedByCaller()
330
+ ) {
170
331
  throw new Error(`${input.operation} timed out after ${AGENT_RUN_API_TIMEOUT_MS}ms`);
171
332
  }
172
333
  throw error;
173
334
  } finally {
174
- clearTimeout(timeout);
335
+ timedAbort.cleanup();
175
336
  }
176
337
 
177
338
  if (!response.ok) {
@@ -184,6 +345,90 @@ async function controlPlaneJson<T>(input: {
184
345
  return input.responseSchema.parse(await response.json());
185
346
  }
186
347
 
348
+ export async function getConversationRun(input: {
349
+ authToken: string;
350
+ apiUrl: string;
351
+ conversationId: string;
352
+ runId: string;
353
+ abortSignal?: AbortSignal;
354
+ }): Promise<ConversationRunProjection> {
355
+ return controlPlaneJson({
356
+ authToken: input.authToken,
357
+ url: `${input.apiUrl}/conversations/${input.conversationId}/runs/${input.runId}`,
358
+ responseSchema: ConversationRunProjectionSchema,
359
+ operation: "Read conversation durable run projection",
360
+ abortSignal: input.abortSignal,
361
+ });
362
+ }
363
+
364
+ export async function appendConversationRunEvents(input: {
365
+ authToken: string;
366
+ apiUrl: string;
367
+ conversationId: string;
368
+ runId: string;
369
+ expectedPreviousEventId?: number;
370
+ expectedPreviousExternalEventSequence?: number;
371
+ events: unknown[];
372
+ abortSignal?: AbortSignal;
373
+ }): Promise<AppendConversationRunEventsResponse> {
374
+ if (input.abortSignal?.aborted) {
375
+ throw new DOMException("This operation was aborted", "AbortError");
376
+ }
377
+
378
+ const timedAbort = createTimedAbortSignal(AGENT_RUN_API_TIMEOUT_MS, input.abortSignal);
379
+
380
+ let response: Response;
381
+ try {
382
+ response = await fetch(
383
+ `${input.apiUrl}/conversations/${input.conversationId}/runs/${input.runId}/events`,
384
+ {
385
+ method: "POST",
386
+ headers: {
387
+ Authorization: `Bearer ${input.authToken}`,
388
+ "Content-Type": "application/json",
389
+ },
390
+ body: JSON.stringify({
391
+ ...(input.expectedPreviousEventId !== undefined
392
+ ? { expected_previous_event_id: input.expectedPreviousEventId }
393
+ : {}),
394
+ ...(input.expectedPreviousExternalEventSequence !== undefined
395
+ ? {
396
+ expected_previous_external_event_sequence:
397
+ input.expectedPreviousExternalEventSequence,
398
+ }
399
+ : {}),
400
+ events: input.events,
401
+ }),
402
+ signal: timedAbort.signal,
403
+ },
404
+ );
405
+ } catch (error) {
406
+ if (
407
+ error instanceof DOMException &&
408
+ error.name === "AbortError" &&
409
+ !timedAbort.wasAbortedByCaller()
410
+ ) {
411
+ throw new Error(
412
+ `Append conversation run events timed out after ${AGENT_RUN_API_TIMEOUT_MS}ms`,
413
+ );
414
+ }
415
+ throw error;
416
+ } finally {
417
+ timedAbort.cleanup();
418
+ }
419
+
420
+ if (!response.ok) {
421
+ const body = await response.text().catch(() => "");
422
+ throw new AppendConversationRunEventsError({
423
+ status: response.status,
424
+ detail: parseAppendConversationRunEventsErrorBody(body),
425
+ statusText: response.statusText,
426
+ });
427
+ }
428
+
429
+ return AppendConversationRunEventsResponseSchema.parse(await response.json());
430
+ }
431
+
187
432
  export async function createConversationAgentRun(
188
433
  input: CreateConversationAgentRunInput,
189
434
  ): Promise<ConversationRunProjection> {
@@ -222,11 +467,11 @@ export async function createConversationAgentRun(
222
467
  operation: "Create canonical durable run",
223
468
  });
224
469
 
225
- return controlPlaneJson({
470
+ return getConversationRun({
226
471
  authToken: input.authToken,
227
- url: `${input.apiUrl}/conversations/${input.conversationId}/runs/${runId}`,
228
- responseSchema: ConversationRunProjectionSchema,
229
- operation: "Read conversation durable run projection",
472
+ apiUrl: input.apiUrl,
473
+ conversationId: input.conversationId,
474
+ runId,
230
475
  });
231
476
  }
232
477
 
@@ -181,16 +181,51 @@ export {
181
181
  type CreateAgUiBrowserResponseStreamInput,
182
182
  } from "./ag-ui-browser-response-stream.js";
183
183
  export {
184
+ bootstrapConversationAgentRun,
185
+ type BootstrapConversationAgentRunResult,
186
+ type ConversationMessageRecord,
187
+ ConversationMessageRecordSchema,
188
+ type ConversationRecord,
189
+ ConversationRecordSchema,
190
+ createConversationMessage,
191
+ createConversationRecord,
192
+ ensureConversationProjectLink,
193
+ fetchConversationRecord,
194
+ } from "./conversation-bootstrap.js";
195
+ export {
196
+ appendConversationRunEvents,
197
+ AppendConversationRunEventsError,
198
+ type AppendConversationRunEventsResponse,
199
+ AppendConversationRunEventsResponseSchema,
184
200
  CompleteConversationRunResponseSchema,
185
201
  type ConversationAgentRunUsage,
186
202
  type ConversationRunProjection,
187
203
  ConversationRunProjectionSchema,
204
+ ConversationRunStatusSchema,
188
205
  type ConversationRunTargets,
189
206
  ConversationRunTargetsSchema,
190
207
  createConversationAgentRun,
191
208
  finalizeConversationAgentRun,
209
+ getConversationRun,
210
+ isCursorMismatchConversationRunAppendError,
211
+ isIgnorableConversationRunAppendError,
212
+ parseAppendConversationRunEventsErrorBody,
192
213
  resolveConversationRunTargets,
193
214
  } from "./durable.js";
215
+ export {
216
+ buildInvokeAgentChildRunLifecycleCustomEvent,
217
+ buildInvokeAgentChildRunProgressEvents,
218
+ buildInvokeAgentChildRunStateDelta,
219
+ type InvokeAgentChildRunLifecycleCustomEvent,
220
+ InvokeAgentChildRunLifecycleCustomEventSchema,
221
+ type InvokeAgentChildRunLifecycleValue,
222
+ InvokeAgentChildRunLifecycleValueSchema,
223
+ type InvokeAgentChildRunProgressEvent,
224
+ type InvokeAgentChildRunProgressInput,
225
+ type InvokeAgentChildRunStateDelta,
226
+ InvokeAgentChildRunStateDeltaSchema,
227
+ publishInvokeAgentChildRunProgress,
228
+ } from "./invoke-agent-child-runs.js";
194
229
  export {
195
230
  type HostedChildLifecycleAdapter,
196
231
  type HostedChildLifecycleRunnerOptions,
@@ -0,0 +1,170 @@
1
+ import "../../_dnt.polyfills.js";
2
+ import { z } from "zod";
3
+ import { appendConversationRunEvents, isIgnorableConversationRunAppendError } from "./durable.js";
4
+
5
+ const AG_UI_CUSTOM_EVENT_TYPE = "CUSTOM";
6
+
7
+ export const InvokeAgentChildRunLifecycleValueSchema = z.object({
8
+ toolCallId: z.string().min(1),
9
+ childConversationId: z.string().uuid(),
10
+ childRunId: z.string().min(1),
11
+ childMessageId: z.string().uuid(),
12
+ childAgentId: z.string().min(1),
13
+ description: z.string().min(1).optional(),
14
+ status: z.enum(["pending", "running", "waiting_for_tool", "completed", "failed", "cancelled"]),
15
+ sourceTargetKind: z.enum(["project", "production", "environment", "preview_branch"]).nullable()
16
+ .optional(),
17
+ runtimeTargetKind: z.enum(["production", "environment", "preview_branch"]).nullable().optional(),
18
+ targetEnvironmentId: z.string().uuid().nullable().optional(),
19
+ targetBranchId: z.string().uuid().nullable().optional(),
20
+ });
21
+
22
+ export type InvokeAgentChildRunLifecycleValue = z.infer<
23
+ typeof InvokeAgentChildRunLifecycleValueSchema
24
+ >;
25
+
26
+ export const InvokeAgentChildRunStateDeltaSchema = z.object({
27
+ type: z.literal("STATE_DELTA"),
28
+ delta: z.array(
29
+ z.object({
30
+ op: z.enum(["add", "replace"]),
31
+ path: z.string().min(1),
32
+ value: InvokeAgentChildRunLifecycleValueSchema,
33
+ }),
34
+ ),
35
+ });
36
+
37
+ export type InvokeAgentChildRunStateDelta = z.infer<typeof InvokeAgentChildRunStateDeltaSchema>;
38
+
39
+ export const InvokeAgentChildRunLifecycleCustomEventSchema = z.object({
40
+ type: z.literal(AG_UI_CUSTOM_EVENT_TYPE),
41
+ name: z.literal("veryfront.invoke_agent.lifecycle"),
42
+ value: InvokeAgentChildRunLifecycleValueSchema,
43
+ });
44
+
45
+ export type InvokeAgentChildRunLifecycleCustomEvent = z.infer<
46
+ typeof InvokeAgentChildRunLifecycleCustomEventSchema
47
+ >;
48
+
49
+ export type InvokeAgentChildRunProgressInput = {
50
+ toolCallId: string;
51
+ childConversationId: string;
52
+ childRunId: string;
53
+ childMessageId: string;
54
+ childAgentId: string;
55
+ description?: string;
56
+ status: "pending" | "running" | "waiting_for_tool" | "completed" | "failed" | "cancelled";
57
+ sourceTargetKind?: "project" | "production" | "environment" | "preview_branch" | null;
58
+ runtimeTargetKind?: "production" | "environment" | "preview_branch" | null;
59
+ targetEnvironmentId?: string | null;
60
+ targetBranchId?: string | null;
61
+ };
62
+
63
+ export type InvokeAgentChildRunProgressEvent =
64
+ | InvokeAgentChildRunStateDelta
65
+ | InvokeAgentChildRunLifecycleCustomEvent;
66
+
67
+ function buildInvokeAgentChildRunLifecycleValue(
68
+ input: InvokeAgentChildRunProgressInput,
69
+ ): InvokeAgentChildRunLifecycleValue {
70
+ return InvokeAgentChildRunLifecycleValueSchema.parse({
71
+ toolCallId: input.toolCallId,
72
+ childConversationId: input.childConversationId,
73
+ childRunId: input.childRunId,
74
+ childMessageId: input.childMessageId,
75
+ childAgentId: input.childAgentId,
76
+ ...(input.description ? { description: input.description } : {}),
77
+ status: input.status,
78
+ ...(input.sourceTargetKind !== undefined ? { sourceTargetKind: input.sourceTargetKind } : {}),
79
+ ...(input.runtimeTargetKind !== undefined
80
+ ? { runtimeTargetKind: input.runtimeTargetKind }
81
+ : {}),
82
+ ...(input.targetEnvironmentId !== undefined
83
+ ? { targetEnvironmentId: input.targetEnvironmentId }
84
+ : {}),
85
+ ...(input.targetBranchId !== undefined ? { targetBranchId: input.targetBranchId } : {}),
86
+ });
87
+ }
88
+
89
+ export function buildInvokeAgentChildRunStateDelta(
90
+ input: InvokeAgentChildRunProgressInput,
91
+ ): InvokeAgentChildRunStateDelta {
92
+ const escapedToolCallId = input.toolCallId.replaceAll("~", "~0").replaceAll("/", "~1");
93
+ return InvokeAgentChildRunStateDeltaSchema.parse({
94
+ type: "STATE_DELTA",
95
+ delta: [
96
+ {
97
+ op: input.status === "pending" ? "add" : "replace",
98
+ path: `/invokeAgentChildRuns/${escapedToolCallId}`,
99
+ value: buildInvokeAgentChildRunLifecycleValue(input),
100
+ },
101
+ ],
102
+ });
103
+ }
104
+
105
+ export function buildInvokeAgentChildRunLifecycleCustomEvent(
106
+ input: InvokeAgentChildRunProgressInput,
107
+ ): InvokeAgentChildRunLifecycleCustomEvent {
108
+ return InvokeAgentChildRunLifecycleCustomEventSchema.parse({
109
+ type: AG_UI_CUSTOM_EVENT_TYPE,
110
+ name: "veryfront.invoke_agent.lifecycle",
111
+ value: buildInvokeAgentChildRunLifecycleValue(input),
112
+ });
113
+ }
114
+
115
+ export function buildInvokeAgentChildRunProgressEvents(
116
+ input: InvokeAgentChildRunProgressInput,
117
+ ): readonly [InvokeAgentChildRunStateDelta, InvokeAgentChildRunLifecycleCustomEvent] {
118
+ return [
119
+ buildInvokeAgentChildRunStateDelta(input),
120
+ buildInvokeAgentChildRunLifecycleCustomEvent(input),
121
+ ] as const;
122
+ }
123
+
124
+ export async function publishInvokeAgentChildRunProgress(input: {
125
+ authToken: string;
126
+ apiUrl: string;
127
+ conversationId: string;
128
+ runId: string;
129
+ expectedPreviousEventId?: number;
130
+ expectedPreviousExternalEventSequence?: number;
131
+ toolCallId: string;
132
+ childAgentId: string;
133
+ childConversationId: string;
134
+ childRunId: string;
135
+ childMessageId: string;
136
+ description?: string;
137
+ status: "pending" | "running" | "waiting_for_tool" | "completed" | "failed" | "cancelled";
138
+ sourceTargetKind?: "project" | "production" | "environment" | "preview_branch" | null;
139
+ runtimeTargetKind?: "production" | "environment" | "preview_branch" | null;
140
+ targetEnvironmentId?: string | null;
141
+ targetBranchId?: string | null;
142
+ publishParentRunEvents?: (events: InvokeAgentChildRunProgressEvent[]) => Promise<void> | void;
143
+ abortSignal?: AbortSignal;
144
+ }): Promise<void> {
145
+ const events = [...buildInvokeAgentChildRunProgressEvents(input)];
146
+
147
+ if (input.publishParentRunEvents) {
148
+ await input.publishParentRunEvents(events);
149
+ return;
150
+ }
151
+
152
+ try {
153
+ await appendConversationRunEvents({
154
+ authToken: input.authToken,
155
+ apiUrl: input.apiUrl,
156
+ conversationId: input.conversationId,
157
+ runId: input.runId,
158
+ expectedPreviousEventId: input.expectedPreviousEventId,
159
+ expectedPreviousExternalEventSequence: input.expectedPreviousExternalEventSequence,
160
+ events,
161
+ abortSignal: input.abortSignal,
162
+ });
163
+ } catch (error) {
164
+ if (isIgnorableConversationRunAppendError(error)) {
165
+ return;
166
+ }
167
+
168
+ throw error;
169
+ }
170
+ }
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.232";
3
+ export const VERSION = "0.1.234";