convex-durable-agents 0.2.3 → 0.2.4
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/README.md +81 -7
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +23 -4
- package/dist/client/api.js.map +1 -1
- package/dist/client/handler.d.ts +20 -0
- package/dist/client/handler.d.ts.map +1 -1
- package/dist/client/handler.js +239 -117
- package/dist/client/handler.js.map +1 -1
- package/dist/client/streamer.js +1 -1
- package/dist/client/streamer.js.map +1 -1
- package/dist/client/tools.d.ts +17 -0
- package/dist/client/tools.d.ts.map +1 -1
- package/dist/client/tools.js +16 -0
- package/dist/client/tools.js.map +1 -1
- package/dist/client/types.d.ts +75 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +11 -0
- package/dist/client/types.js.map +1 -1
- package/dist/component/_generated/component.d.ts +89 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/agent.d.ts.map +1 -1
- package/dist/component/agent.js +21 -2
- package/dist/component/agent.js.map +1 -1
- package/dist/component/schema.d.ts +70 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +21 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.js +2 -2
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +92 -2
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +83 -2
- package/dist/component/threads.js.map +1 -1
- package/dist/component/tool_calls.d.ts +54 -3
- package/dist/component/tool_calls.d.ts.map +1 -1
- package/dist/component/tool_calls.js +345 -35
- package/dist/component/tool_calls.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +69 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +404 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/streaming.d.ts +4 -0
- package/dist/utils/streaming.d.ts.map +1 -0
- package/dist/utils/streaming.js +4 -0
- package/dist/utils/streaming.js.map +1 -0
- package/package.json +1 -1
- package/src/client/api.ts +24 -4
- package/src/client/handler.ts +308 -133
- package/src/client/streamer.ts +1 -1
- package/src/client/tools.ts +43 -1
- package/src/client/types.ts +60 -0
- package/src/component/_generated/component.ts +103 -0
- package/src/component/agent.ts +24 -2
- package/src/component/schema.ts +22 -0
- package/src/component/streams.ts +2 -2
- package/src/component/threads.ts +92 -3
- package/src/component/tool_calls.ts +421 -44
- package/src/utils/retry.ts +528 -0
- package/src/{streaming.ts → utils/streaming.ts} +2 -3
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/streaming.d.ts +0 -3
- package/dist/streaming.d.ts.map +0 -1
- package/dist/streaming.js +0 -4
- package/dist/streaming.js.map +0 -1
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{logger.js → utils/logger.js} +0 -0
- /package/src/{logger.ts → utils/logger.ts} +0 -0
package/src/client/types.ts
CHANGED
|
@@ -33,6 +33,17 @@ export type PaginationResult<T> = {
|
|
|
33
33
|
|
|
34
34
|
export type ThreadStatus = "streaming" | "awaiting_tool_results" | "completed" | "failed" | "stopped";
|
|
35
35
|
|
|
36
|
+
export type RetryState = {
|
|
37
|
+
scope: "stream";
|
|
38
|
+
attempt: number;
|
|
39
|
+
maxAttempts: number;
|
|
40
|
+
nextRetryAt: number;
|
|
41
|
+
error: string;
|
|
42
|
+
kind?: string;
|
|
43
|
+
retryable: boolean;
|
|
44
|
+
requiresExplicitHandling: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
36
47
|
export type ThreadDoc = {
|
|
37
48
|
_id: string;
|
|
38
49
|
_creationTime: number;
|
|
@@ -40,6 +51,7 @@ export type ThreadDoc = {
|
|
|
40
51
|
stopSignal: boolean;
|
|
41
52
|
streamId?: string | null;
|
|
42
53
|
streamFnHandle: string;
|
|
54
|
+
retryState?: RetryState;
|
|
43
55
|
};
|
|
44
56
|
|
|
45
57
|
const vThreadStatus = v.union(
|
|
@@ -59,6 +71,19 @@ export const _vClientThreadDoc = v.object({
|
|
|
59
71
|
streamFnHandle: v.string(),
|
|
60
72
|
workpoolEnqueueAction: v.optional(v.string()),
|
|
61
73
|
toolExecutionWorkpoolEnqueueAction: v.optional(v.string()),
|
|
74
|
+
retryState: v.optional(
|
|
75
|
+
v.object({
|
|
76
|
+
scope: v.literal("stream"),
|
|
77
|
+
attempt: v.number(),
|
|
78
|
+
maxAttempts: v.number(),
|
|
79
|
+
nextRetryAt: v.number(),
|
|
80
|
+
error: v.string(),
|
|
81
|
+
kind: v.optional(v.string()),
|
|
82
|
+
retryable: v.boolean(),
|
|
83
|
+
requiresExplicitHandling: v.boolean(),
|
|
84
|
+
retryFnId: v.optional(v.string()),
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
62
87
|
});
|
|
63
88
|
|
|
64
89
|
export type MessageDoc = UIMessage<any> & {
|
|
@@ -77,12 +102,47 @@ export function messageDocToUIMessage(message: MessageDoc): UIMessage {
|
|
|
77
102
|
};
|
|
78
103
|
}
|
|
79
104
|
|
|
105
|
+
export type RetryBackoffConfig =
|
|
106
|
+
| {
|
|
107
|
+
strategy?: "fixed";
|
|
108
|
+
delayMs: number;
|
|
109
|
+
jitter?: boolean;
|
|
110
|
+
}
|
|
111
|
+
| {
|
|
112
|
+
strategy: "exponential";
|
|
113
|
+
initialDelayMs: number;
|
|
114
|
+
multiplier?: number;
|
|
115
|
+
maxDelayMs?: number;
|
|
116
|
+
jitter?: boolean;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type SyncToolRetryOptions = {
|
|
120
|
+
/**
|
|
121
|
+
* Opt-in to retry for this sync tool.
|
|
122
|
+
*/
|
|
123
|
+
enabled: true;
|
|
124
|
+
/**
|
|
125
|
+
* Maximum execution attempts including the initial attempt.
|
|
126
|
+
*/
|
|
127
|
+
maxAttempts?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Retry backoff policy.
|
|
130
|
+
*/
|
|
131
|
+
backoff?: RetryBackoffConfig;
|
|
132
|
+
/**
|
|
133
|
+
* Optional function to classify whether an error is retryable.
|
|
134
|
+
* Receives { threadId, toolCallId, toolName, args, error, attempt, maxAttempts }.
|
|
135
|
+
*/
|
|
136
|
+
shouldRetryError?: FunctionReference<"action", "internal" | "public">;
|
|
137
|
+
};
|
|
138
|
+
|
|
80
139
|
// Sync durable tool definition - handler returns the result directly
|
|
81
140
|
export type SyncTool<INPUT = unknown, OUTPUT = unknown> = {
|
|
82
141
|
type: "sync";
|
|
83
142
|
description: string;
|
|
84
143
|
parameters: unknown; // JSON Schema
|
|
85
144
|
handler: FunctionReference<"action", "internal" | "public">;
|
|
145
|
+
retry?: SyncToolRetryOptions;
|
|
86
146
|
_inputType?: INPUT;
|
|
87
147
|
_outputType?: OUTPUT;
|
|
88
148
|
};
|
|
@@ -163,6 +163,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
163
163
|
>;
|
|
164
164
|
};
|
|
165
165
|
threads: {
|
|
166
|
+
clearRetryState: FunctionReference<
|
|
167
|
+
"mutation",
|
|
168
|
+
"internal",
|
|
169
|
+
{ threadId: string },
|
|
170
|
+
null,
|
|
171
|
+
Name
|
|
172
|
+
>;
|
|
166
173
|
clearStreamId: FunctionReference<
|
|
167
174
|
"mutation",
|
|
168
175
|
"internal",
|
|
@@ -182,6 +189,17 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
182
189
|
{
|
|
183
190
|
_creationTime: number;
|
|
184
191
|
_id: string;
|
|
192
|
+
retryState?: {
|
|
193
|
+
attempt: number;
|
|
194
|
+
error: string;
|
|
195
|
+
kind?: string;
|
|
196
|
+
maxAttempts: number;
|
|
197
|
+
nextRetryAt: number;
|
|
198
|
+
requiresExplicitHandling: boolean;
|
|
199
|
+
retryFnId?: string;
|
|
200
|
+
retryable: boolean;
|
|
201
|
+
scope: "stream";
|
|
202
|
+
};
|
|
185
203
|
status:
|
|
186
204
|
| "streaming"
|
|
187
205
|
| "awaiting_tool_results"
|
|
@@ -220,6 +238,17 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
220
238
|
{
|
|
221
239
|
_creationTime: number;
|
|
222
240
|
_id: string;
|
|
241
|
+
retryState?: {
|
|
242
|
+
attempt: number;
|
|
243
|
+
error: string;
|
|
244
|
+
kind?: string;
|
|
245
|
+
maxAttempts: number;
|
|
246
|
+
nextRetryAt: number;
|
|
247
|
+
requiresExplicitHandling: boolean;
|
|
248
|
+
retryFnId?: string;
|
|
249
|
+
retryable: boolean;
|
|
250
|
+
scope: "stream";
|
|
251
|
+
};
|
|
223
252
|
status:
|
|
224
253
|
| "streaming"
|
|
225
254
|
| "awaiting_tool_results"
|
|
@@ -241,6 +270,17 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
241
270
|
Array<{
|
|
242
271
|
_creationTime: number;
|
|
243
272
|
_id: string;
|
|
273
|
+
retryState?: {
|
|
274
|
+
attempt: number;
|
|
275
|
+
error: string;
|
|
276
|
+
kind?: string;
|
|
277
|
+
maxAttempts: number;
|
|
278
|
+
nextRetryAt: number;
|
|
279
|
+
requiresExplicitHandling: boolean;
|
|
280
|
+
retryFnId?: string;
|
|
281
|
+
retryable: boolean;
|
|
282
|
+
scope: "stream";
|
|
283
|
+
};
|
|
244
284
|
status:
|
|
245
285
|
| "streaming"
|
|
246
286
|
| "awaiting_tool_results"
|
|
@@ -276,6 +316,23 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
276
316
|
null,
|
|
277
317
|
Name
|
|
278
318
|
>;
|
|
319
|
+
scheduleRetry: FunctionReference<
|
|
320
|
+
"mutation",
|
|
321
|
+
"internal",
|
|
322
|
+
{
|
|
323
|
+
attempt: number;
|
|
324
|
+
error: string;
|
|
325
|
+
kind?: string;
|
|
326
|
+
maxAttempts: number;
|
|
327
|
+
nextRetryAt: number;
|
|
328
|
+
requiresExplicitHandling: boolean;
|
|
329
|
+
retryable: boolean;
|
|
330
|
+
scope: "stream";
|
|
331
|
+
threadId: string;
|
|
332
|
+
},
|
|
333
|
+
null,
|
|
334
|
+
Name
|
|
335
|
+
>;
|
|
279
336
|
setStatus: FunctionReference<
|
|
280
337
|
"mutation",
|
|
281
338
|
"internal",
|
|
@@ -321,7 +378,9 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
321
378
|
{
|
|
322
379
|
args: any;
|
|
323
380
|
callback?: string;
|
|
381
|
+
handler?: string;
|
|
324
382
|
msgId: string;
|
|
383
|
+
retry?: any;
|
|
325
384
|
saveDelta: boolean;
|
|
326
385
|
threadId: string;
|
|
327
386
|
toolCallId: string;
|
|
@@ -331,9 +390,18 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
331
390
|
_creationTime: number;
|
|
332
391
|
_id: string;
|
|
333
392
|
args: any;
|
|
393
|
+
callbackAttempt?: number;
|
|
394
|
+
callbackLastError?: string;
|
|
334
395
|
error?: string;
|
|
396
|
+
executionAttempt?: number;
|
|
397
|
+
executionLastError?: string;
|
|
398
|
+
executionMaxAttempts?: number;
|
|
399
|
+
executionRetryPolicy?: any;
|
|
400
|
+
handler?: string;
|
|
335
401
|
msgId: string;
|
|
402
|
+
nextRetryAt?: number;
|
|
336
403
|
result?: any;
|
|
404
|
+
status: "pending" | "completed" | "failed";
|
|
337
405
|
threadId: string;
|
|
338
406
|
toolCallId: string;
|
|
339
407
|
toolName: string;
|
|
@@ -348,9 +416,18 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
348
416
|
_creationTime: number;
|
|
349
417
|
_id: string;
|
|
350
418
|
args: any;
|
|
419
|
+
callbackAttempt?: number;
|
|
420
|
+
callbackLastError?: string;
|
|
351
421
|
error?: string;
|
|
422
|
+
executionAttempt?: number;
|
|
423
|
+
executionLastError?: string;
|
|
424
|
+
executionMaxAttempts?: number;
|
|
425
|
+
executionRetryPolicy?: any;
|
|
426
|
+
handler?: string;
|
|
352
427
|
msgId: string;
|
|
428
|
+
nextRetryAt?: number;
|
|
353
429
|
result?: any;
|
|
430
|
+
status: "pending" | "completed" | "failed";
|
|
354
431
|
threadId: string;
|
|
355
432
|
toolCallId: string;
|
|
356
433
|
toolName: string;
|
|
@@ -365,9 +442,18 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
365
442
|
_creationTime: number;
|
|
366
443
|
_id: string;
|
|
367
444
|
args: any;
|
|
445
|
+
callbackAttempt?: number;
|
|
446
|
+
callbackLastError?: string;
|
|
368
447
|
error?: string;
|
|
448
|
+
executionAttempt?: number;
|
|
449
|
+
executionLastError?: string;
|
|
450
|
+
executionMaxAttempts?: number;
|
|
451
|
+
executionRetryPolicy?: any;
|
|
452
|
+
handler?: string;
|
|
369
453
|
msgId: string;
|
|
454
|
+
nextRetryAt?: number;
|
|
370
455
|
result?: any;
|
|
456
|
+
status: "pending" | "completed" | "failed";
|
|
371
457
|
threadId: string;
|
|
372
458
|
toolCallId: string;
|
|
373
459
|
toolName: string;
|
|
@@ -382,15 +468,31 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
382
468
|
_creationTime: number;
|
|
383
469
|
_id: string;
|
|
384
470
|
args: any;
|
|
471
|
+
callbackAttempt?: number;
|
|
472
|
+
callbackLastError?: string;
|
|
385
473
|
error?: string;
|
|
474
|
+
executionAttempt?: number;
|
|
475
|
+
executionLastError?: string;
|
|
476
|
+
executionMaxAttempts?: number;
|
|
477
|
+
executionRetryPolicy?: any;
|
|
478
|
+
handler?: string;
|
|
386
479
|
msgId: string;
|
|
480
|
+
nextRetryAt?: number;
|
|
387
481
|
result?: any;
|
|
482
|
+
status: "pending" | "completed" | "failed";
|
|
388
483
|
threadId: string;
|
|
389
484
|
toolCallId: string;
|
|
390
485
|
toolName: string;
|
|
391
486
|
}>,
|
|
392
487
|
Name
|
|
393
488
|
>;
|
|
489
|
+
resumePendingSyncToolExecutions: FunctionReference<
|
|
490
|
+
"mutation",
|
|
491
|
+
"internal",
|
|
492
|
+
{ limit?: number },
|
|
493
|
+
number,
|
|
494
|
+
Name
|
|
495
|
+
>;
|
|
394
496
|
scheduleAsyncToolCall: FunctionReference<
|
|
395
497
|
"mutation",
|
|
396
498
|
"internal",
|
|
@@ -413,6 +515,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
413
515
|
args: any;
|
|
414
516
|
handler: string;
|
|
415
517
|
msgId: string;
|
|
518
|
+
retry?: any;
|
|
416
519
|
saveDelta: boolean;
|
|
417
520
|
threadId: string;
|
|
418
521
|
toolCallId: string;
|
package/src/component/agent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FunctionHandle, FunctionReference, GenericDataModel, GenericMutationCtx } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
|
-
import { Logger } from "../logger.js";
|
|
4
|
-
import { STREAM_LIVENESS_THRESHOLD_MS } from "../streaming.js";
|
|
3
|
+
import { Logger } from "../utils/logger.js";
|
|
4
|
+
import { STREAM_LIVENESS_THRESHOLD_MS } from "../utils/streaming.js";
|
|
5
5
|
import { api, internal } from "./_generated/api.js";
|
|
6
6
|
import { action, mutation } from "./_generated/server.js";
|
|
7
7
|
import { cancelStream, isAlive } from "./streams.js";
|
|
@@ -69,6 +69,7 @@ export const continueStream = mutation({
|
|
|
69
69
|
status: "stopped",
|
|
70
70
|
activeStream: null,
|
|
71
71
|
continue: false,
|
|
72
|
+
retryState: undefined,
|
|
72
73
|
});
|
|
73
74
|
if (thread.onStatusChangeHandle && previousStatus !== "stopped") {
|
|
74
75
|
await ctx.runMutation(thread.onStatusChangeHandle as FunctionHandle<"mutation">, {
|
|
@@ -93,6 +94,27 @@ export const continueStream = mutation({
|
|
|
93
94
|
return null;
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
if (thread.retryState?.scope === "stream") {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
if (thread.retryState.nextRetryAt > now) {
|
|
100
|
+
const delayMs = Math.max(0, thread.retryState.nextRetryAt - now);
|
|
101
|
+
logger.debug(
|
|
102
|
+
`Retry pending for thread=${args.threadId}; nextRetryAt=${thread.retryState.nextRetryAt}, now=${now}`,
|
|
103
|
+
);
|
|
104
|
+
const retryFnId = await ctx.scheduler.runAfter(delayMs, api.agent.continueStream, {
|
|
105
|
+
threadId: args.threadId,
|
|
106
|
+
});
|
|
107
|
+
await ctx.db.patch(thread._id, {
|
|
108
|
+
retryState: {
|
|
109
|
+
...thread.retryState,
|
|
110
|
+
retryFnId,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
logger.debug(`Retry window reached for thread=${args.threadId}; continuing with persisted retryState`);
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
// Check for pending tool calls
|
|
97
119
|
const pendingToolCalls = await ctx.runQuery(api.tool_calls.listPending, {
|
|
98
120
|
threadId: args.threadId,
|
package/src/component/schema.ts
CHANGED
|
@@ -10,6 +10,18 @@ export const vThreadStatus = v.union(
|
|
|
10
10
|
v.literal("stopped"),
|
|
11
11
|
);
|
|
12
12
|
|
|
13
|
+
export const vRetryState = v.object({
|
|
14
|
+
scope: v.literal("stream"),
|
|
15
|
+
attempt: v.number(),
|
|
16
|
+
maxAttempts: v.number(),
|
|
17
|
+
nextRetryAt: v.number(),
|
|
18
|
+
error: v.string(),
|
|
19
|
+
kind: v.optional(v.string()),
|
|
20
|
+
retryable: v.boolean(),
|
|
21
|
+
requiresExplicitHandling: v.boolean(),
|
|
22
|
+
retryFnId: v.optional(v.id("_scheduled_functions")),
|
|
23
|
+
});
|
|
24
|
+
|
|
13
25
|
// AI SDK message content - supports both string and array of parts
|
|
14
26
|
export const vMessageContent = v.union(
|
|
15
27
|
v.string(),
|
|
@@ -70,6 +82,8 @@ const schema = defineSchema({
|
|
|
70
82
|
onStatusChangeHandle: v.optional(v.string()),
|
|
71
83
|
// Monotonically increasing sequence number for streams of this thread
|
|
72
84
|
seq: v.number(),
|
|
85
|
+
// Pending retry metadata for stream retries.
|
|
86
|
+
retryState: v.optional(vRetryState),
|
|
73
87
|
}).index("by_status", ["status"]),
|
|
74
88
|
|
|
75
89
|
// AI SDK compatible message storage
|
|
@@ -95,6 +109,13 @@ const schema = defineSchema({
|
|
|
95
109
|
callback: v.optional(v.string()),
|
|
96
110
|
callbackAttempt: v.optional(v.number()),
|
|
97
111
|
callbackLastError: v.optional(v.string()),
|
|
112
|
+
executionAttempt: v.optional(v.number()),
|
|
113
|
+
executionMaxAttempts: v.optional(v.number()),
|
|
114
|
+
executionLastError: v.optional(v.string()),
|
|
115
|
+
executionRetryPolicy: v.optional(v.any()),
|
|
116
|
+
nextRetryAt: v.optional(v.number()),
|
|
117
|
+
executionRetryFnId: v.optional(v.id("_scheduled_functions")),
|
|
118
|
+
handler: v.optional(v.string()),
|
|
98
119
|
timeoutMs: v.optional(v.union(v.number(), v.null())),
|
|
99
120
|
expiresAt: v.optional(v.union(v.number(), v.null())),
|
|
100
121
|
timeoutFnId: v.optional(v.id("_scheduled_functions")),
|
|
@@ -105,6 +126,7 @@ const schema = defineSchema({
|
|
|
105
126
|
saveDelta: v.optional(v.boolean()),
|
|
106
127
|
})
|
|
107
128
|
.index("by_thread", ["threadId"])
|
|
129
|
+
.index("by_status_only", ["status"])
|
|
108
130
|
.index("by_status", ["threadId", "status"])
|
|
109
131
|
.index("by_thread_tool_call_id", ["threadId", "toolCallId"])
|
|
110
132
|
.index("by_tool_call_id", ["toolCallId"]),
|
package/src/component/streams.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UIMessageChunk } from "ai";
|
|
2
2
|
import { type Infer, v } from "convex/values";
|
|
3
|
-
import { Logger } from "../logger.js";
|
|
4
|
-
import { STREAM_LIVENESS_THRESHOLD_MS } from "../streaming.js";
|
|
3
|
+
import { Logger } from "../utils/logger.js";
|
|
4
|
+
import { STREAM_LIVENESS_THRESHOLD_MS } from "../utils/streaming.js";
|
|
5
5
|
import { api, internal } from "./_generated/api";
|
|
6
6
|
import type { Doc, Id } from "./_generated/dataModel";
|
|
7
7
|
import { internalMutation, type MutationCtx, mutation, query } from "./_generated/server";
|
package/src/component/threads.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { FunctionHandle } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
|
-
import { Logger } from "../logger.js";
|
|
3
|
+
import { Logger } from "../utils/logger.js";
|
|
4
|
+
import { api } from "./_generated/api.js";
|
|
4
5
|
import type { Doc, Id } from "./_generated/dataModel.js";
|
|
5
|
-
import { internalQuery, mutation, query } from "./_generated/server.js";
|
|
6
|
-
import { vThreadStatus } from "./schema.js";
|
|
6
|
+
import { internalQuery, type MutationCtx, mutation, query } from "./_generated/server.js";
|
|
7
|
+
import { vRetryState, vThreadStatus } from "./schema.js";
|
|
7
8
|
|
|
8
9
|
const logger = new Logger("threads");
|
|
9
10
|
const FINALIZER_MISMATCH_ALERT_WINDOW_MS = 5 * 60 * 1000;
|
|
@@ -64,6 +65,7 @@ export type ThreadDoc = {
|
|
|
64
65
|
streamFnHandle: string;
|
|
65
66
|
workpoolEnqueueAction?: string;
|
|
66
67
|
toolExecutionWorkpoolEnqueueAction?: string;
|
|
68
|
+
retryState?: Doc<"threads">["retryState"];
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
function publicThread(thread: Doc<"threads">): ThreadDoc {
|
|
@@ -76,6 +78,7 @@ function publicThread(thread: Doc<"threads">): ThreadDoc {
|
|
|
76
78
|
streamFnHandle: thread.streamFnHandle,
|
|
77
79
|
workpoolEnqueueAction: thread.workpoolEnqueueAction,
|
|
78
80
|
toolExecutionWorkpoolEnqueueAction: thread.toolExecutionWorkpoolEnqueueAction,
|
|
81
|
+
retryState: thread.retryState,
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -89,6 +92,7 @@ export const vThreadDoc = v.object({
|
|
|
89
92
|
streamFnHandle: v.string(),
|
|
90
93
|
workpoolEnqueueAction: v.optional(v.string()),
|
|
91
94
|
toolExecutionWorkpoolEnqueueAction: v.optional(v.string()),
|
|
95
|
+
retryState: v.optional(vRetryState),
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
export const vThreadDocWithStreamFnHandle = v.object({
|
|
@@ -104,8 +108,19 @@ export const vThreadDocWithStreamFnHandle = v.object({
|
|
|
104
108
|
activeStream: v.optional(v.union(v.id("streams"), v.null())),
|
|
105
109
|
continue: v.optional(v.boolean()),
|
|
106
110
|
seq: v.number(),
|
|
111
|
+
retryState: v.optional(vRetryState),
|
|
107
112
|
});
|
|
108
113
|
|
|
114
|
+
async function cancelRetryFnIfPending(ctx: MutationCtx, thread: Doc<"threads">): Promise<void> {
|
|
115
|
+
if (!thread.retryState?.retryFnId) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const retryFn = await ctx.db.system.get(thread.retryState.retryFnId);
|
|
119
|
+
if (retryFn?.state.kind === "pending") {
|
|
120
|
+
await ctx.scheduler.cancel(thread.retryState.retryFnId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
109
124
|
export const create = mutation({
|
|
110
125
|
args: {
|
|
111
126
|
streamFnHandle: v.string(),
|
|
@@ -149,6 +164,74 @@ export const getWithStreamFnHandle = internalQuery({
|
|
|
149
164
|
},
|
|
150
165
|
});
|
|
151
166
|
|
|
167
|
+
export const scheduleRetry = mutation({
|
|
168
|
+
args: {
|
|
169
|
+
threadId: v.id("threads"),
|
|
170
|
+
scope: v.literal("stream"),
|
|
171
|
+
attempt: v.number(),
|
|
172
|
+
maxAttempts: v.number(),
|
|
173
|
+
nextRetryAt: v.number(),
|
|
174
|
+
error: v.string(),
|
|
175
|
+
kind: v.optional(v.string()),
|
|
176
|
+
retryable: v.boolean(),
|
|
177
|
+
requiresExplicitHandling: v.boolean(),
|
|
178
|
+
},
|
|
179
|
+
returns: v.null(),
|
|
180
|
+
handler: async (ctx, args) => {
|
|
181
|
+
const thread = await ctx.db.get(args.threadId);
|
|
182
|
+
if (!thread) {
|
|
183
|
+
throw new Error(`Thread ${args.threadId} not found`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await cancelRetryFnIfPending(ctx, thread);
|
|
187
|
+
|
|
188
|
+
if (thread.stopSignal) {
|
|
189
|
+
await ctx.db.patch(args.threadId, {
|
|
190
|
+
retryState: undefined,
|
|
191
|
+
});
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const delayMs = Math.max(0, args.nextRetryAt - Date.now());
|
|
196
|
+
const retryFnId = await ctx.scheduler.runAfter(delayMs, api.agent.continueStream, {
|
|
197
|
+
threadId: args.threadId,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await ctx.db.patch(args.threadId, {
|
|
201
|
+
retryState: {
|
|
202
|
+
scope: args.scope,
|
|
203
|
+
attempt: args.attempt,
|
|
204
|
+
maxAttempts: args.maxAttempts,
|
|
205
|
+
nextRetryAt: args.nextRetryAt,
|
|
206
|
+
error: args.error,
|
|
207
|
+
kind: args.kind,
|
|
208
|
+
retryable: args.retryable,
|
|
209
|
+
requiresExplicitHandling: args.requiresExplicitHandling,
|
|
210
|
+
retryFnId,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
return null;
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export const clearRetryState = mutation({
|
|
218
|
+
args: {
|
|
219
|
+
threadId: v.id("threads"),
|
|
220
|
+
},
|
|
221
|
+
returns: v.null(),
|
|
222
|
+
handler: async (ctx, args) => {
|
|
223
|
+
const thread = await ctx.db.get(args.threadId);
|
|
224
|
+
if (!thread) {
|
|
225
|
+
throw new Error(`Thread ${args.threadId} not found`);
|
|
226
|
+
}
|
|
227
|
+
await cancelRetryFnIfPending(ctx, thread);
|
|
228
|
+
await ctx.db.patch(args.threadId, {
|
|
229
|
+
retryState: undefined,
|
|
230
|
+
});
|
|
231
|
+
return null;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
152
235
|
export const resume = mutation({
|
|
153
236
|
args: {
|
|
154
237
|
threadId: v.id("threads"),
|
|
@@ -344,6 +427,11 @@ export const setStopSignal = mutation({
|
|
|
344
427
|
if (!thread) {
|
|
345
428
|
throw new Error(`Thread ${args.threadId} not found`);
|
|
346
429
|
}
|
|
430
|
+
if (args.stopSignal) {
|
|
431
|
+
await cancelRetryFnIfPending(ctx, thread);
|
|
432
|
+
await ctx.db.patch(args.threadId, { stopSignal: args.stopSignal, retryState: undefined });
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
347
435
|
await ctx.db.patch(args.threadId, { stopSignal: args.stopSignal });
|
|
348
436
|
return null;
|
|
349
437
|
},
|
|
@@ -383,6 +471,7 @@ export const remove = mutation({
|
|
|
383
471
|
if (!thread) {
|
|
384
472
|
throw new Error(`Thread ${args.threadId} not found`);
|
|
385
473
|
}
|
|
474
|
+
await cancelRetryFnIfPending(ctx, thread);
|
|
386
475
|
// Delete all messages for this thread
|
|
387
476
|
const messages = await ctx.db
|
|
388
477
|
.query("messages")
|