@agrentingai/paperclip-adapter 0.2.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/README.md +306 -0
- package/dist/server/index.cjs +1406 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +895 -0
- package/dist/server/index.d.ts +895 -0
- package/dist/server/index.js +1336 -0
- package/dist/server/index.js.map +1 -0
- package/dist/ui/index.cjs +125 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +36 -0
- package/dist/ui/index.d.ts +36 -0
- package/dist/ui/index.js +98 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +65 -0
- package/server/src/adapter.test.ts +497 -0
- package/server/src/adapter.ts +1044 -0
- package/server/src/balance-monitor.test.ts +147 -0
- package/server/src/balance-monitor.ts +118 -0
- package/server/src/client.test.ts +949 -0
- package/server/src/client.ts +550 -0
- package/server/src/comment-sync.test.ts +25 -0
- package/server/src/comment-sync.ts +71 -0
- package/server/src/crypto.test.ts +62 -0
- package/server/src/crypto.ts +25 -0
- package/server/src/index.ts +67 -0
- package/server/src/polling.test.ts +208 -0
- package/server/src/polling.ts +183 -0
- package/server/src/types.ts +244 -0
- package/server/src/webhook-handler.test.ts +379 -0
- package/server/src/webhook-handler.ts +292 -0
- package/ui/src/adapter.ts +131 -0
- package/ui/src/index.ts +2 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cryptographic utilities for the Agrenting adapter.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Verify an HMAC-SHA256 signature against a raw request body.
|
|
7
|
+
* Uses constant-time comparison to prevent timing attacks.
|
|
8
|
+
*/
|
|
9
|
+
export async function verifyWebhookSignature(
|
|
10
|
+
rawBody: string,
|
|
11
|
+
signature: string,
|
|
12
|
+
secret: string
|
|
13
|
+
): Promise<boolean> {
|
|
14
|
+
const crypto = await import("crypto");
|
|
15
|
+
const expected = crypto
|
|
16
|
+
.createHmac("sha256", secret)
|
|
17
|
+
.update(rawBody)
|
|
18
|
+
.digest("base64");
|
|
19
|
+
const sigBuf = Buffer.from(signature);
|
|
20
|
+
const expectedBuf = Buffer.from(expected);
|
|
21
|
+
if (sigBuf.byteLength !== expectedBuf.byteLength) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return crypto.timingSafeEqual(sigBuf, expectedBuf);
|
|
25
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export { createServerAdapter } from "./adapter.js";
|
|
2
|
+
export { AgrentingClient } from "./client.js";
|
|
3
|
+
export {
|
|
4
|
+
createWebhookHandler,
|
|
5
|
+
registerTaskMapping,
|
|
6
|
+
unregisterTaskMapping,
|
|
7
|
+
getActiveTaskMappings,
|
|
8
|
+
} from "./webhook-handler.js";
|
|
9
|
+
export type {
|
|
10
|
+
AgrentingWebhookPayload,
|
|
11
|
+
PaperclipApiClient,
|
|
12
|
+
WebhookHandlerOptions,
|
|
13
|
+
} from "./webhook-handler.js";
|
|
14
|
+
export {
|
|
15
|
+
formatAgentResponse,
|
|
16
|
+
forwardCommentToAgrenting,
|
|
17
|
+
processIncomingMessage,
|
|
18
|
+
} from "./comment-sync.js";
|
|
19
|
+
export { pollTaskUntilDone, getWebhookGracePeriodMs, POLL_INTERVALS_MS, MAX_POLLS, getBackoffMs } from "./polling.js";
|
|
20
|
+
export type { PollOptions, PollResult } from "./polling.js";
|
|
21
|
+
export {
|
|
22
|
+
checkBalance,
|
|
23
|
+
canSubmitTask,
|
|
24
|
+
formatLowBalanceComment,
|
|
25
|
+
formatInsufficientBalanceComment,
|
|
26
|
+
} from "./balance-monitor.js";
|
|
27
|
+
export type { BalanceInfo, BalanceCheckOptions } from "./balance-monitor.js";
|
|
28
|
+
export { verifyWebhookSignature } from "./crypto.js";
|
|
29
|
+
export {
|
|
30
|
+
registerWebhook,
|
|
31
|
+
deregisterWebhook,
|
|
32
|
+
hireAgent,
|
|
33
|
+
getAgentProfile,
|
|
34
|
+
sendMessageToTask,
|
|
35
|
+
getTaskMessages,
|
|
36
|
+
reassignTask,
|
|
37
|
+
listCapabilities,
|
|
38
|
+
sendMessageToHiring,
|
|
39
|
+
getHiringMessages,
|
|
40
|
+
retryHiring,
|
|
41
|
+
getHiring,
|
|
42
|
+
listHirings,
|
|
43
|
+
autoSelectAgent,
|
|
44
|
+
executeWithRetry,
|
|
45
|
+
} from "./adapter.js";
|
|
46
|
+
export type {
|
|
47
|
+
AgrentingAdapterConfig,
|
|
48
|
+
AgrentingExecutionResult,
|
|
49
|
+
AgrentingTaskStatus,
|
|
50
|
+
AgrentingTask,
|
|
51
|
+
AgentInfo,
|
|
52
|
+
AgentProfile,
|
|
53
|
+
HireAgentResult,
|
|
54
|
+
SendMessageOptions,
|
|
55
|
+
SendMessageResult,
|
|
56
|
+
ReassignTaskResult,
|
|
57
|
+
PaymentInfo,
|
|
58
|
+
TransactionInfo,
|
|
59
|
+
DiscoverAgentsOptions,
|
|
60
|
+
CreateTaskPaymentOptions,
|
|
61
|
+
Hiring,
|
|
62
|
+
TaskMessage,
|
|
63
|
+
HiringMessage,
|
|
64
|
+
Capability,
|
|
65
|
+
AutoSelectOptions,
|
|
66
|
+
RetryHiringOptions,
|
|
67
|
+
} from "./types.js";
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
pollTaskUntilDone,
|
|
4
|
+
getBackoffMs,
|
|
5
|
+
getWebhookGracePeriodMs,
|
|
6
|
+
POLL_INTERVALS_MS,
|
|
7
|
+
MAX_POLLS,
|
|
8
|
+
} from "./polling.js";
|
|
9
|
+
|
|
10
|
+
// Mock the client so we don't hit the real API
|
|
11
|
+
vi.mock("./client.js", () => {
|
|
12
|
+
return {
|
|
13
|
+
AgrentingClient: vi.fn().mockImplementation(function() {
|
|
14
|
+
return {
|
|
15
|
+
getTask: vi.fn().mockResolvedValue({
|
|
16
|
+
id: "task-1",
|
|
17
|
+
status: "completed",
|
|
18
|
+
output: "done",
|
|
19
|
+
client_agent_id: "c1",
|
|
20
|
+
provider_agent_id: "p1",
|
|
21
|
+
capability: "test",
|
|
22
|
+
input: "hello",
|
|
23
|
+
created_at: new Date().toISOString(),
|
|
24
|
+
updated_at: new Date().toISOString(),
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const mockConfig = {
|
|
32
|
+
agrentingUrl: "https://api.agrenting.com",
|
|
33
|
+
apiKey: "test-key",
|
|
34
|
+
agentDid: "did:agrenting:test",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// getBackoffMs
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe("getBackoffMs", () => {
|
|
46
|
+
it("returns 10s for attempt 0", () => {
|
|
47
|
+
expect(getBackoffMs(0)).toBe(10_000);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns 30s for attempt 1", () => {
|
|
51
|
+
expect(getBackoffMs(1)).toBe(30_000);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns 60s for attempt 2", () => {
|
|
55
|
+
expect(getBackoffMs(2)).toBe(60_000);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns 120s for attempt 3", () => {
|
|
59
|
+
expect(getBackoffMs(3)).toBe(120_000);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("caps at 120s for higher attempts", () => {
|
|
63
|
+
expect(getBackoffMs(10)).toBe(120_000);
|
|
64
|
+
expect(getBackoffMs(100)).toBe(120_000);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// POLL_INTERVALS_MS / MAX_POLLS constants
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe("constants", () => {
|
|
73
|
+
it("has 4 poll intervals with exponential backoff", () => {
|
|
74
|
+
expect(POLL_INTERVALS_MS).toEqual([10_000, 30_000, 60_000, 120_000]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("MAX_POLLS is 10", () => {
|
|
78
|
+
expect(MAX_POLLS).toBe(10);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// getWebhookGracePeriodMs
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
describe("getWebhookGracePeriodMs", () => {
|
|
87
|
+
it("returns 60s by default (10% of 600s, capped at 60s)", () => {
|
|
88
|
+
expect(getWebhookGracePeriodMs(mockConfig)).toBe(60_000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns 10% of timeout for short timeouts", () => {
|
|
92
|
+
const config = { ...mockConfig, timeoutSec: 30 };
|
|
93
|
+
// 30s * 1000 * 0.1 = 3000ms, min(60000, 3000) = 3000
|
|
94
|
+
expect(getWebhookGracePeriodMs(config)).toBe(3_000);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("caps at 60s even for long timeouts", () => {
|
|
98
|
+
const config = { ...mockConfig, timeoutSec: 3600 };
|
|
99
|
+
// 3600s * 1000 * 0.1 = 360000, min(60000, 360000) = 60000
|
|
100
|
+
expect(getWebhookGracePeriodMs(config)).toBe(60_000);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// pollTaskUntilDone
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
describe("pollTaskUntilDone", () => {
|
|
109
|
+
it("returns completed task on first poll", { timeout: 30_000 }, async () => {
|
|
110
|
+
const result = await pollTaskUntilDone({
|
|
111
|
+
config: mockConfig,
|
|
112
|
+
taskId: "task-1",
|
|
113
|
+
deadline: Date.now() + 600_000,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.result.success).toBe(true);
|
|
117
|
+
expect(result.result.output).toBe("done");
|
|
118
|
+
expect(result.result.taskId).toBe("task-1");
|
|
119
|
+
expect(result.viaPolling).toBe(true);
|
|
120
|
+
expect(result.pollCount).toBeGreaterThanOrEqual(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns timeout when deadline is in the past", async () => {
|
|
124
|
+
const result = await pollTaskUntilDone({
|
|
125
|
+
config: mockConfig,
|
|
126
|
+
taskId: "task-1",
|
|
127
|
+
deadline: Date.now() - 1000, // Already past
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.result.success).toBe(false);
|
|
131
|
+
expect(result.result.error).toContain("timed out");
|
|
132
|
+
expect(result.pollCount).toBe(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("fires onStatusUpdate callback on each poll", { timeout: 30_000 }, async () => {
|
|
136
|
+
const onStatusUpdate = vi.fn();
|
|
137
|
+
|
|
138
|
+
await pollTaskUntilDone({
|
|
139
|
+
config: mockConfig,
|
|
140
|
+
taskId: "task-1",
|
|
141
|
+
deadline: Date.now() + 600_000,
|
|
142
|
+
onStatusUpdate,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(onStatusUpdate).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(onStatusUpdate).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({ status: "completed" }),
|
|
148
|
+
1
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("reports failure when task fails", { timeout: 30_000 }, async () => {
|
|
153
|
+
const { AgrentingClient } = await import("./client.js");
|
|
154
|
+
const MockClient = vi.mocked(AgrentingClient);
|
|
155
|
+
MockClient.mockImplementationOnce(function() {
|
|
156
|
+
return {
|
|
157
|
+
getTask: vi.fn().mockResolvedValue({
|
|
158
|
+
id: "fail-task",
|
|
159
|
+
status: "failed",
|
|
160
|
+
error_reason: "Agent crashed",
|
|
161
|
+
client_agent_id: "c1",
|
|
162
|
+
provider_agent_id: "p1",
|
|
163
|
+
capability: "test",
|
|
164
|
+
input: "hello",
|
|
165
|
+
created_at: new Date().toISOString(),
|
|
166
|
+
updated_at: new Date().toISOString(),
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = await pollTaskUntilDone({
|
|
172
|
+
config: mockConfig,
|
|
173
|
+
taskId: "fail-task",
|
|
174
|
+
deadline: Date.now() + 600_000,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(result.result.success).toBe(false);
|
|
178
|
+
expect(result.result.error).toBe("Agent crashed");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("reports cancellation when task is cancelled", { timeout: 30_000 }, async () => {
|
|
182
|
+
const { AgrentingClient } = await import("./client.js");
|
|
183
|
+
const MockClient = vi.mocked(AgrentingClient);
|
|
184
|
+
MockClient.mockImplementationOnce(function() {
|
|
185
|
+
return {
|
|
186
|
+
getTask: vi.fn().mockResolvedValue({
|
|
187
|
+
id: "cancel-task",
|
|
188
|
+
status: "cancelled",
|
|
189
|
+
client_agent_id: "c1",
|
|
190
|
+
provider_agent_id: "p1",
|
|
191
|
+
capability: "test",
|
|
192
|
+
input: "hello",
|
|
193
|
+
created_at: new Date().toISOString(),
|
|
194
|
+
updated_at: new Date().toISOString(),
|
|
195
|
+
}),
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const result = await pollTaskUntilDone({
|
|
200
|
+
config: mockConfig,
|
|
201
|
+
taskId: "cancel-task",
|
|
202
|
+
deadline: Date.now() + 600_000,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(result.result.success).toBe(false);
|
|
206
|
+
expect(result.result.error).toBe("Task was cancelled");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback polling logic for Agrenting task status.
|
|
3
|
+
*
|
|
4
|
+
* Used when webhooks are delayed or fail to deliver.
|
|
5
|
+
* Polls with exponential backoff: 10s → 30s → 60s → 120s (max 10 polls).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AgrentingClient } from "./client.js";
|
|
9
|
+
import type {
|
|
10
|
+
AgrentingAdapterConfig,
|
|
11
|
+
AgrentingExecutionResult,
|
|
12
|
+
AgrentingTask,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
|
|
15
|
+
/** Poll intervals in milliseconds (exponential backoff) — shared across polling modes */
|
|
16
|
+
export const POLL_INTERVALS_MS = [10_000, 30_000, 60_000, 120_000];
|
|
17
|
+
export const MAX_POLLS = 10;
|
|
18
|
+
|
|
19
|
+
/** Compute the backoff duration for a given poll attempt (0-indexed). */
|
|
20
|
+
export function getBackoffMs(attempt: number): number {
|
|
21
|
+
const index = Math.min(attempt, POLL_INTERVALS_MS.length - 1);
|
|
22
|
+
return POLL_INTERVALS_MS[index];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PollOptions {
|
|
26
|
+
config: AgrentingAdapterConfig;
|
|
27
|
+
taskId: string;
|
|
28
|
+
/** Deadline in ms (Date.now() + timeoutMs) */
|
|
29
|
+
deadline: number;
|
|
30
|
+
/** Optional callback fired on each poll cycle with the current task state */
|
|
31
|
+
onStatusUpdate?: (task: AgrentingTask, pollAttempt: number) => void;
|
|
32
|
+
/** Starting poll attempt (useful for resuming after a webhook delay) */
|
|
33
|
+
startAttempt?: number;
|
|
34
|
+
/** AbortSignal to cancel polling early (e.g., when a webhook resolves first) */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PollResult {
|
|
39
|
+
/** Final execution result */
|
|
40
|
+
result: AgrentingExecutionResult;
|
|
41
|
+
/** Number of polls performed */
|
|
42
|
+
pollCount: number;
|
|
43
|
+
/** Whether the result came from polling (true) or was already resolved */
|
|
44
|
+
viaPolling: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Poll the Agrenting task API with exponential backoff until the task
|
|
49
|
+
* reaches a terminal state or the deadline is reached.
|
|
50
|
+
*/
|
|
51
|
+
export async function pollTaskUntilDone(
|
|
52
|
+
options: PollOptions
|
|
53
|
+
): Promise<PollResult> {
|
|
54
|
+
const client = new AgrentingClient(options.config);
|
|
55
|
+
let pollAttempt = options.startAttempt ?? 0;
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
|
|
58
|
+
while (pollAttempt < MAX_POLLS) {
|
|
59
|
+
// Check if polling was aborted (e.g., webhook resolved first)
|
|
60
|
+
if (options.signal?.aborted) {
|
|
61
|
+
return {
|
|
62
|
+
result: {
|
|
63
|
+
success: false,
|
|
64
|
+
error: "Polling aborted",
|
|
65
|
+
taskId: options.taskId,
|
|
66
|
+
durationMs: Date.now() - startTime,
|
|
67
|
+
},
|
|
68
|
+
pollCount: pollAttempt,
|
|
69
|
+
viaPolling: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (now >= options.deadline) {
|
|
75
|
+
return {
|
|
76
|
+
result: {
|
|
77
|
+
success: false,
|
|
78
|
+
error: `Task timed out after ${options.config.timeoutSec ?? 600}s`,
|
|
79
|
+
taskId: options.taskId,
|
|
80
|
+
durationMs: now - startTime,
|
|
81
|
+
},
|
|
82
|
+
pollCount: pollAttempt,
|
|
83
|
+
viaPolling: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Wait for the next poll interval (respects abort signal).
|
|
88
|
+
// Skip the wait on the first iteration so the first poll is immediate.
|
|
89
|
+
if (pollAttempt > 0) {
|
|
90
|
+
const waitMs = getBackoffMs(pollAttempt);
|
|
91
|
+
await new Promise<void>((resolve) => {
|
|
92
|
+
const timer = setTimeout(resolve, waitMs);
|
|
93
|
+
if (options.signal) {
|
|
94
|
+
options.signal.addEventListener("abort", () => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
resolve();
|
|
97
|
+
}, { once: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const task = await client.getTask(options.taskId);
|
|
103
|
+
pollAttempt++;
|
|
104
|
+
|
|
105
|
+
options.onStatusUpdate?.(task, pollAttempt);
|
|
106
|
+
|
|
107
|
+
if (task.status === "completed") {
|
|
108
|
+
return {
|
|
109
|
+
result: {
|
|
110
|
+
success: true,
|
|
111
|
+
output: task.output,
|
|
112
|
+
taskId: options.taskId,
|
|
113
|
+
durationMs: Date.now() - startTime,
|
|
114
|
+
},
|
|
115
|
+
pollCount: pollAttempt,
|
|
116
|
+
viaPolling: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (task.status === "failed") {
|
|
121
|
+
return {
|
|
122
|
+
result: {
|
|
123
|
+
success: false,
|
|
124
|
+
error: task.error_reason ?? "Task failed with no reason provided",
|
|
125
|
+
taskId: options.taskId,
|
|
126
|
+
durationMs: Date.now() - startTime,
|
|
127
|
+
},
|
|
128
|
+
pollCount: pollAttempt,
|
|
129
|
+
viaPolling: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (task.status === "cancelled") {
|
|
134
|
+
return {
|
|
135
|
+
result: {
|
|
136
|
+
success: false,
|
|
137
|
+
error: "Task was cancelled",
|
|
138
|
+
taskId: options.taskId,
|
|
139
|
+
durationMs: Date.now() - startTime,
|
|
140
|
+
},
|
|
141
|
+
pollCount: pollAttempt,
|
|
142
|
+
viaPolling: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Max polls reached without terminal state — do one final check
|
|
148
|
+
const finalTask = await client.getTask(options.taskId);
|
|
149
|
+
if (finalTask.status === "completed") {
|
|
150
|
+
return {
|
|
151
|
+
result: {
|
|
152
|
+
success: true,
|
|
153
|
+
output: finalTask.output,
|
|
154
|
+
taskId: options.taskId,
|
|
155
|
+
durationMs: Date.now() - startTime,
|
|
156
|
+
},
|
|
157
|
+
pollCount: pollAttempt + 1,
|
|
158
|
+
viaPolling: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
result: {
|
|
164
|
+
success: false,
|
|
165
|
+
error: `Task did not complete after ${pollAttempt} polls (max ${MAX_POLLS})`,
|
|
166
|
+
taskId: options.taskId,
|
|
167
|
+
durationMs: Date.now() - startTime,
|
|
168
|
+
},
|
|
169
|
+
pollCount: pollAttempt,
|
|
170
|
+
viaPolling: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calculate when to activate fallback polling.
|
|
176
|
+
* Returns the number of ms to wait before switching from webhook
|
|
177
|
+
* wait mode to polling mode.
|
|
178
|
+
*/
|
|
179
|
+
export function getWebhookGracePeriodMs(config: AgrentingAdapterConfig): number {
|
|
180
|
+
// Default: 60 seconds. If the timeout is very short, use a fraction of it.
|
|
181
|
+
const timeoutMs = (config.timeoutSec ?? 600) * 1000;
|
|
182
|
+
return Math.min(60_000, timeoutMs * 0.1);
|
|
183
|
+
}
|