robotrock 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.d.ts +4 -3
- package/dist/ai/index.js +1461 -27
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/trigger.d.ts +3 -2
- package/dist/ai/trigger.js +1449 -13
- package/dist/ai/trigger.js.map +1 -1
- package/dist/ai/workflow.d.ts +30 -3
- package/dist/ai/workflow.js +1422 -13
- package/dist/ai/workflow.js.map +1 -1
- package/dist/{client-CLcHdjOz.d.ts → client-D-XEBOWd.d.ts} +7 -2
- package/dist/index.d.ts +5 -36
- package/dist/index.js +525 -36
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +146 -458
- package/dist/schemas/index.js +156 -15
- package/dist/schemas/index.js.map +1 -1
- package/dist/tool-approval-bridge-BKDY5NAY.d.ts +247 -0
- package/dist/trigger/index.d.ts +1 -1
- package/dist/trigger/index.js +560 -10
- package/dist/trigger/index.js.map +1 -1
- package/dist/trigger-CsOLTjsH.d.ts +79 -0
- package/dist/workflow/index.d.ts +2 -2
- package/dist/workflow/index.js +608 -16
- package/dist/workflow/index.js.map +1 -1
- package/package.json +9 -8
- package/dist/chunk-7VBBX2HA.js +0 -613
- package/dist/chunk-7VBBX2HA.js.map +0 -1
- package/dist/chunk-D2FBSEZK.js +0 -67
- package/dist/chunk-D2FBSEZK.js.map +0 -1
- package/dist/chunk-TAWMIQAM.js +0 -177
- package/dist/chunk-TAWMIQAM.js.map +0 -1
- package/dist/chunk-ZCMXX7IG.js +0 -374
- package/dist/chunk-ZCMXX7IG.js.map +0 -1
- package/dist/workflow-CFcRMNhy.d.ts +0 -393
package/dist/index.js
CHANGED
|
@@ -1,47 +1,533 @@
|
|
|
1
|
+
// src/schemas/index.ts
|
|
2
|
+
import { z } from "zod";
|
|
1
3
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
4
|
+
isPublicHttpUrl,
|
|
5
|
+
PUBLIC_HTTP_URL_ERROR
|
|
6
|
+
} from "@robotrock/core/utils";
|
|
7
|
+
import { validateContextPublicUrls } from "@robotrock/core/schemas";
|
|
8
|
+
var safeUrlSchema = z.string().refine((url) => isPublicHttpUrl(url), {
|
|
9
|
+
message: PUBLIC_HTTP_URL_ERROR
|
|
10
|
+
});
|
|
11
|
+
var jsonSchema7Schema = z.custom(
|
|
12
|
+
(val) => typeof val === "object" && val !== null,
|
|
13
|
+
{ message: "Must be a valid JSON Schema object" }
|
|
14
|
+
);
|
|
15
|
+
var uiSchemaSchema = z.custom((val) => typeof val === "object" && val !== null, {
|
|
16
|
+
message: "Must be a valid UiSchema object"
|
|
17
|
+
});
|
|
18
|
+
var webhookHandlerSchema = z.object({
|
|
19
|
+
type: z.literal("webhook"),
|
|
20
|
+
url: safeUrlSchema,
|
|
21
|
+
headers: z.record(z.string(), z.string())
|
|
22
|
+
});
|
|
23
|
+
var triggerHandlerSchema = webhookHandlerSchema.extend({
|
|
24
|
+
type: z.literal("trigger"),
|
|
25
|
+
tokenId: z.string().min(1)
|
|
26
|
+
});
|
|
27
|
+
var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
|
|
28
|
+
var taskActionSchema = z.object({
|
|
29
|
+
id: z.string().min(1),
|
|
30
|
+
title: z.string().min(1),
|
|
31
|
+
description: z.string().optional(),
|
|
32
|
+
schema: jsonSchema7Schema.optional(),
|
|
33
|
+
ui: uiSchemaSchema.optional(),
|
|
34
|
+
data: z.record(z.string(), z.unknown()).optional(),
|
|
35
|
+
handlers: z.array(handlerSchema).min(1).optional()
|
|
36
|
+
});
|
|
37
|
+
var uiFieldSchemaSchema = z.object({
|
|
38
|
+
"ui:widget": z.string().optional(),
|
|
39
|
+
"ui:title": z.string().optional(),
|
|
40
|
+
"ui:description": z.string().optional(),
|
|
41
|
+
"ui:options": z.record(z.string(), z.unknown()).optional(),
|
|
42
|
+
items: z.lazy(() => z.record(z.string(), uiFieldSchemaSchema)).optional()
|
|
43
|
+
}).passthrough();
|
|
44
|
+
var contextUiSchema = z.record(z.string(), uiFieldSchemaSchema).optional();
|
|
45
|
+
var contextDataSchema = z.object({
|
|
46
|
+
data: z.record(z.string(), z.unknown()),
|
|
47
|
+
ui: contextUiSchema
|
|
48
|
+
}).optional();
|
|
49
|
+
var taskContextObjectSchema = z.object({
|
|
50
|
+
app: z.string().min(1).optional(),
|
|
51
|
+
type: z.string().min(1),
|
|
52
|
+
name: z.string().min(1),
|
|
53
|
+
description: z.string().optional(),
|
|
54
|
+
validUntil: z.string().optional(),
|
|
55
|
+
context: contextDataSchema,
|
|
56
|
+
version: z.literal(2).optional(),
|
|
57
|
+
actions: z.array(taskActionSchema).min(1, "At least one action is required")
|
|
58
|
+
});
|
|
59
|
+
function refineContextPublicUrls(data, ctx) {
|
|
60
|
+
const error = validateContextPublicUrls(data.context);
|
|
61
|
+
if (error) {
|
|
62
|
+
ctx.addIssue({
|
|
63
|
+
code: z.ZodIssueCode.custom,
|
|
64
|
+
message: error,
|
|
65
|
+
path: ["context"]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var taskContextSchema = taskContextObjectSchema.superRefine(
|
|
70
|
+
refineContextPublicUrls
|
|
71
|
+
);
|
|
72
|
+
var assignToSchema = z.object({
|
|
73
|
+
users: z.array(z.string().email()).optional(),
|
|
74
|
+
groups: z.array(z.string().min(1)).optional()
|
|
75
|
+
}).refine(
|
|
76
|
+
(data) => {
|
|
77
|
+
const groups = data.groups ?? [];
|
|
78
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
84
|
+
);
|
|
85
|
+
var threadUpdateMessageSchema = z.string().min(1).max(500);
|
|
86
|
+
var threadUpdateStatuses = [
|
|
87
|
+
"info",
|
|
88
|
+
"queued",
|
|
89
|
+
"running",
|
|
90
|
+
"waiting",
|
|
91
|
+
"succeeded",
|
|
92
|
+
"failed",
|
|
93
|
+
"cancelled"
|
|
94
|
+
];
|
|
95
|
+
var threadUpdateStatusSchema = z.enum(threadUpdateStatuses);
|
|
96
|
+
var DEFAULT_THREAD_UPDATE_STATUS = "info";
|
|
97
|
+
var threadUpdateInputSchema = z.object({
|
|
98
|
+
message: threadUpdateMessageSchema,
|
|
99
|
+
status: threadUpdateStatusSchema.optional()
|
|
100
|
+
});
|
|
101
|
+
var taskPriorities = ["low", "normal", "high", "urgent"];
|
|
102
|
+
var taskPrioritySchema = z.enum(taskPriorities);
|
|
103
|
+
var DEFAULT_TASK_PRIORITY = "normal";
|
|
104
|
+
var LOWEST_TASK_PRIORITY = "low";
|
|
105
|
+
var TASK_PRIORITY_RANK = {
|
|
106
|
+
low: 1,
|
|
107
|
+
normal: 2,
|
|
108
|
+
high: 3,
|
|
109
|
+
urgent: 4
|
|
110
|
+
};
|
|
111
|
+
var nonNegativeInt = z.number().int().nonnegative();
|
|
112
|
+
var agentCostTokensSchema = z.object({
|
|
113
|
+
input: nonNegativeInt.optional(),
|
|
114
|
+
output: nonNegativeInt.optional(),
|
|
115
|
+
total: nonNegativeInt.optional()
|
|
116
|
+
});
|
|
117
|
+
var agentCostSchema = z.object({
|
|
118
|
+
tokens: agentCostTokensSchema.optional(),
|
|
119
|
+
eur: z.number().nonnegative().optional(),
|
|
120
|
+
usd: z.number().nonnegative().optional()
|
|
121
|
+
});
|
|
122
|
+
var AGENT_INFO_MAX_KEYS = 32;
|
|
123
|
+
var agentTelemetrySchema = z.object({
|
|
124
|
+
version: z.string().min(1).optional(),
|
|
125
|
+
toolCallCount: nonNegativeInt.optional(),
|
|
126
|
+
cost: agentCostSchema.optional(),
|
|
127
|
+
info: z.record(z.string(), z.unknown()).optional()
|
|
128
|
+
}).refine(
|
|
129
|
+
(data) => {
|
|
130
|
+
const keys = data.info ? Object.keys(data.info) : [];
|
|
131
|
+
return keys.length <= AGENT_INFO_MAX_KEYS;
|
|
132
|
+
},
|
|
133
|
+
{ message: `agent.info may contain at most ${AGENT_INFO_MAX_KEYS} keys` }
|
|
134
|
+
);
|
|
135
|
+
var createTaskBodySchema = taskContextObjectSchema.extend({
|
|
136
|
+
assignTo: assignToSchema.optional(),
|
|
137
|
+
/**
|
|
138
|
+
* Groups related tasks together. When omitted, the server generates one and
|
|
139
|
+
* returns it so the caller can reuse it on later tasks in the same thread.
|
|
140
|
+
*/
|
|
141
|
+
threadId: z.string().min(1).optional(),
|
|
142
|
+
/**
|
|
143
|
+
* Optional thread priority. When set, applies to the whole thread and
|
|
144
|
+
* overwrites any previous priority. Omit on later tasks to leave unchanged.
|
|
145
|
+
*/
|
|
146
|
+
priority: taskPrioritySchema.optional(),
|
|
147
|
+
/**
|
|
148
|
+
* Optional initial status update logged against the task's thread. Shows in
|
|
149
|
+
* the inbox status bar and the thread update log.
|
|
150
|
+
*/
|
|
151
|
+
update: threadUpdateInputSchema.optional(),
|
|
152
|
+
agent: agentTelemetrySchema.optional()
|
|
153
|
+
}).superRefine(refineContextPublicUrls);
|
|
154
|
+
var threadUpdateBodySchema = threadUpdateInputSchema;
|
|
155
|
+
|
|
156
|
+
// src/approval-result.ts
|
|
157
|
+
var TaskTimeoutError = class extends Error {
|
|
158
|
+
constructor(message) {
|
|
159
|
+
super(message);
|
|
160
|
+
this.name = "TaskTimeoutError";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var TaskExpiredError = class extends Error {
|
|
164
|
+
constructor(message) {
|
|
165
|
+
super(message);
|
|
166
|
+
this.name = "TaskExpiredError";
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function toDiscriminatedApprovalResult(actions, task) {
|
|
170
|
+
void actions;
|
|
171
|
+
if (!task.handled) {
|
|
172
|
+
throw new Error("Task has no handled result");
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
actionId: task.handled.action.id,
|
|
176
|
+
data: task.handled.action.data,
|
|
177
|
+
handledBy: task.handled.handledBy,
|
|
178
|
+
handledAt: new Date(task.handledAt ?? Date.now()),
|
|
179
|
+
taskId: task.id
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/client.ts
|
|
184
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
185
|
+
var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
186
|
+
function sleep(ms) {
|
|
187
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
188
|
+
}
|
|
189
|
+
function parseValidUntilMs(value) {
|
|
190
|
+
if (value === void 0) {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
if (value instanceof Date) {
|
|
194
|
+
const ms = value.getTime();
|
|
195
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
196
|
+
}
|
|
197
|
+
if (typeof value === "number") {
|
|
198
|
+
return Number.isFinite(value) ? value : void 0;
|
|
199
|
+
}
|
|
200
|
+
const parsed = Date.parse(value);
|
|
201
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
202
|
+
}
|
|
203
|
+
function serializeValidUntil(value) {
|
|
204
|
+
if (value instanceof Date) {
|
|
205
|
+
const ms = value.getTime();
|
|
206
|
+
if (Number.isNaN(ms)) {
|
|
207
|
+
throw new RobotRockError("Invalid validUntil: Date is invalid", 400);
|
|
208
|
+
}
|
|
209
|
+
return value.toISOString();
|
|
210
|
+
}
|
|
211
|
+
if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
|
|
212
|
+
return new Date(value).toISOString();
|
|
213
|
+
}
|
|
214
|
+
throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
|
|
215
|
+
}
|
|
216
|
+
var RobotRockError = class extends Error {
|
|
217
|
+
constructor(message, statusCode, response) {
|
|
218
|
+
super(message);
|
|
219
|
+
this.statusCode = statusCode;
|
|
220
|
+
this.response = response;
|
|
221
|
+
this.name = "RobotRockError";
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var RobotRock = class {
|
|
225
|
+
apiKey;
|
|
226
|
+
baseUrl;
|
|
227
|
+
app;
|
|
228
|
+
version;
|
|
229
|
+
webhook;
|
|
230
|
+
polling;
|
|
231
|
+
constructor(config) {
|
|
232
|
+
if (config.webhook && config.polling) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
"RobotRock client cannot configure both webhook and polling. Use webhook for callbacks or polling to block until handled."
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const apiKey = config.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
238
|
+
if (!apiKey) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey when creating the client."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
this.apiKey = apiKey;
|
|
244
|
+
const rawBase = config.baseUrl ?? "https://api.robotrock.io/v1";
|
|
245
|
+
this.baseUrl = rawBase.replace(/\/+$/, "");
|
|
246
|
+
this.app = config.app;
|
|
247
|
+
this.version = config.version ?? 2;
|
|
248
|
+
this.webhook = config.webhook;
|
|
249
|
+
this.polling = config.polling ?? {};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Create a task via POST /v1 without waiting for a human response.
|
|
253
|
+
*/
|
|
254
|
+
async createTask(task) {
|
|
255
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
256
|
+
webhook: this.webhook,
|
|
257
|
+
app: this.app,
|
|
258
|
+
version: this.version
|
|
259
|
+
});
|
|
260
|
+
const bodyPayload = {
|
|
261
|
+
...normalizedTask,
|
|
262
|
+
...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
|
|
263
|
+
...task.threadId !== void 0 ? { threadId: task.threadId } : {},
|
|
264
|
+
...task.priority !== void 0 ? { priority: task.priority } : {},
|
|
265
|
+
...task.update !== void 0 ? { update: task.update } : {},
|
|
266
|
+
...task.agent !== void 0 ? { agent: task.agent } : {}
|
|
267
|
+
};
|
|
268
|
+
const validation = createTaskBodySchema.safeParse(bodyPayload);
|
|
269
|
+
if (!validation.success) {
|
|
270
|
+
throw new RobotRockError(
|
|
271
|
+
`Invalid task: ${validation.error.issues[0]?.message}`,
|
|
272
|
+
400,
|
|
273
|
+
validation.error.issues
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const headers = {
|
|
277
|
+
"Content-Type": "application/json",
|
|
278
|
+
"X-Api-Key": this.apiKey
|
|
279
|
+
};
|
|
280
|
+
if (task.idempotencyKey) {
|
|
281
|
+
headers["Idempotency-Key"] = task.idempotencyKey;
|
|
282
|
+
}
|
|
283
|
+
const response = await fetch(`${this.baseUrl}/`, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers,
|
|
286
|
+
body: JSON.stringify(validation.data)
|
|
287
|
+
});
|
|
288
|
+
const data = await parseResponseBody(response);
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
throw new RobotRockError(
|
|
291
|
+
getErrorMessage(data, "Failed to create task"),
|
|
292
|
+
response.status,
|
|
293
|
+
data
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return data.task;
|
|
297
|
+
}
|
|
298
|
+
async sendToHuman(task) {
|
|
299
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
300
|
+
webhook: this.webhook,
|
|
301
|
+
app: this.app,
|
|
302
|
+
version: this.version
|
|
303
|
+
});
|
|
304
|
+
const createdTaskTask = await this.createTask(task);
|
|
305
|
+
const hasHandlers = normalizedTask.actions.some(
|
|
306
|
+
(action) => Array.isArray(action.handlers) && action.handlers.length > 0
|
|
307
|
+
);
|
|
308
|
+
if (hasHandlers) {
|
|
309
|
+
return {
|
|
310
|
+
mode: "created",
|
|
311
|
+
task: createdTaskTask
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const timeoutMs = this.polling.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
315
|
+
const pollIntervalMs = this.polling.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
316
|
+
const pollingDeadline = Date.now() + timeoutMs;
|
|
317
|
+
const validUntilMs = parseValidUntilMs(createdTaskTask.validUntil);
|
|
318
|
+
const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
|
|
319
|
+
const taskId = createdTaskTask.taskId;
|
|
320
|
+
while (Date.now() < deadline) {
|
|
321
|
+
const existing = await this.getTask(taskId);
|
|
322
|
+
if (existing?.status === "handled" && existing.handled) {
|
|
323
|
+
return {
|
|
324
|
+
mode: "handled",
|
|
325
|
+
task: createdTaskTask,
|
|
326
|
+
...toDiscriminatedApprovalResult(
|
|
327
|
+
normalizedTask.actions,
|
|
328
|
+
existing
|
|
329
|
+
)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (existing?.status === "expired" || existing && Date.now() >= existing.validUntil) {
|
|
333
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
334
|
+
}
|
|
335
|
+
const remainingMs = deadline - Date.now();
|
|
336
|
+
await sleep(Math.min(pollIntervalMs, Math.max(0, remainingMs)));
|
|
337
|
+
}
|
|
338
|
+
if (validUntilMs !== void 0 && Date.now() >= validUntilMs) {
|
|
339
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
340
|
+
}
|
|
341
|
+
throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
|
|
345
|
+
*/
|
|
346
|
+
async getTask(taskId) {
|
|
347
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
|
|
348
|
+
method: "GET",
|
|
349
|
+
headers: {
|
|
350
|
+
"X-Api-Key": this.apiKey
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
if (response.status === 404) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
const data = await parseResponseBody(response);
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
throw new RobotRockError(
|
|
359
|
+
getErrorMessage(data, "Failed to get task"),
|
|
360
|
+
response.status,
|
|
361
|
+
data
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
return data;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Log a status update against a thread. The update shows in the inbox status
|
|
368
|
+
* bar and thread update log for every task in the thread.
|
|
369
|
+
*/
|
|
370
|
+
async sendUpdate({ threadId, message, status }) {
|
|
371
|
+
if (!threadId) {
|
|
372
|
+
throw new RobotRockError("threadId is required to send an update", 400);
|
|
373
|
+
}
|
|
374
|
+
const validation = threadUpdateBodySchema.safeParse({ message, status });
|
|
375
|
+
if (!validation.success) {
|
|
376
|
+
throw new RobotRockError(
|
|
377
|
+
`Invalid update: ${validation.error.issues[0]?.message}`,
|
|
378
|
+
400,
|
|
379
|
+
validation.error.issues
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const response = await fetch(
|
|
383
|
+
`${this.baseUrl}/threads/${encodeURIComponent(threadId)}/updates`,
|
|
384
|
+
{
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: {
|
|
387
|
+
"Content-Type": "application/json",
|
|
388
|
+
"X-Api-Key": this.apiKey
|
|
389
|
+
},
|
|
390
|
+
body: JSON.stringify(validation.data)
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
const data = await parseResponseBody(response);
|
|
394
|
+
if (!response.ok) {
|
|
395
|
+
throw new RobotRockError(
|
|
396
|
+
getErrorMessage(data, "Failed to send update"),
|
|
397
|
+
response.status,
|
|
398
|
+
data
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return data.update;
|
|
402
|
+
}
|
|
403
|
+
async cancelTask(taskId) {
|
|
404
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
|
|
405
|
+
method: "POST",
|
|
406
|
+
headers: {
|
|
407
|
+
"X-Api-Key": this.apiKey
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
if (!response.ok) {
|
|
411
|
+
const data = await parseResponseBody(response);
|
|
412
|
+
throw new RobotRockError(
|
|
413
|
+
getErrorMessage(data, "Failed to cancel task"),
|
|
414
|
+
response.status,
|
|
415
|
+
data
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
function createClient(config) {
|
|
421
|
+
return new RobotRock(config);
|
|
422
|
+
}
|
|
423
|
+
function attachWebhookToActions(actions, webhook) {
|
|
424
|
+
return actions.map((action) => ({
|
|
425
|
+
...action,
|
|
426
|
+
handlers: webhookToHandlers(webhook)
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
function webhookToHandlers(webhook) {
|
|
430
|
+
return [
|
|
431
|
+
{
|
|
432
|
+
type: "webhook",
|
|
433
|
+
url: webhook.url,
|
|
434
|
+
headers: webhook.headers ?? {}
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
function normalizeSendToHumanInput(task, clientDefaults) {
|
|
439
|
+
const {
|
|
440
|
+
actions,
|
|
441
|
+
idempotencyKey: _idempotencyKey,
|
|
442
|
+
assignTo: _assignTo,
|
|
443
|
+
threadId: _threadId,
|
|
444
|
+
priority: _priority,
|
|
445
|
+
update: _update,
|
|
446
|
+
validUntil,
|
|
447
|
+
app: taskApp,
|
|
448
|
+
...rest
|
|
449
|
+
} = task;
|
|
450
|
+
const webhook = clientDefaults.webhook;
|
|
451
|
+
const normalizedActions = webhook ? attachWebhookToActions(actions, webhook) : actions;
|
|
452
|
+
const app = taskApp ?? clientDefaults.app;
|
|
453
|
+
return {
|
|
454
|
+
...rest,
|
|
455
|
+
version: clientDefaults.version,
|
|
456
|
+
...app ? { app } : {},
|
|
457
|
+
...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
|
|
458
|
+
actions: normalizedActions
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async function parseResponseBody(response) {
|
|
462
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
463
|
+
const bodyText = await response.text();
|
|
464
|
+
if (!bodyText) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
if (contentType.toLowerCase().includes("application/json")) {
|
|
468
|
+
try {
|
|
469
|
+
return JSON.parse(bodyText);
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
return JSON.parse(bodyText);
|
|
475
|
+
} catch {
|
|
476
|
+
return bodyText;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function getErrorMessage(data, fallback) {
|
|
480
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
481
|
+
const maybeMessage = data.message;
|
|
482
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim()) {
|
|
483
|
+
return maybeMessage;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (typeof data === "string" && data.trim()) {
|
|
487
|
+
const compact = data.replace(/\s+/g, " ").trim();
|
|
488
|
+
const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
|
|
489
|
+
return `${fallback}. Server returned non-JSON response: ${snippet}`;
|
|
490
|
+
}
|
|
491
|
+
return fallback;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/env.ts
|
|
495
|
+
var DEFAULT_BASE_URL = "https://api.robotrock.io/v1";
|
|
496
|
+
function resolveRobotRockConfig(overrides) {
|
|
497
|
+
const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
498
|
+
if (!apiKey) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
"RobotRock API key is required. Set ROBOTROCK_API_KEY or pass apiKey when creating the client."
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const baseUrl = overrides?.baseUrl ?? process.env.ROBOTROCK_BASE_URL ?? process.env.ROBOTROCK_API_URL ?? DEFAULT_BASE_URL;
|
|
504
|
+
const app = overrides?.app ?? process.env.ROBOTROCK_APP;
|
|
505
|
+
return app ? { apiKey, baseUrl, app } : { apiKey, baseUrl };
|
|
506
|
+
}
|
|
507
|
+
function resolveRobotRockClient(client, configOverrides) {
|
|
508
|
+
if (client) {
|
|
509
|
+
return client;
|
|
510
|
+
}
|
|
511
|
+
return createClient(resolveRobotRockConfig(configOverrides));
|
|
512
|
+
}
|
|
27
513
|
|
|
28
514
|
// src/webhook.ts
|
|
29
515
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
30
|
-
import { z } from "zod";
|
|
516
|
+
import { z as z2 } from "zod";
|
|
31
517
|
var ROBOTROCK_SIGNATURE_HEADER = "x-robotrock-signature";
|
|
32
|
-
var robotRockWebhookPayloadBodySchema =
|
|
33
|
-
taskId:
|
|
34
|
-
action:
|
|
35
|
-
id:
|
|
36
|
-
title:
|
|
37
|
-
data:
|
|
518
|
+
var robotRockWebhookPayloadBodySchema = z2.object({
|
|
519
|
+
taskId: z2.string().min(1),
|
|
520
|
+
action: z2.object({
|
|
521
|
+
id: z2.string().min(1),
|
|
522
|
+
title: z2.string().min(1),
|
|
523
|
+
data: z2.unknown()
|
|
38
524
|
}),
|
|
39
|
-
handledBy:
|
|
40
|
-
handledAt:
|
|
41
|
-
handlerType:
|
|
525
|
+
handledBy: z2.string().min(1).optional(),
|
|
526
|
+
handledAt: z2.string().min(1),
|
|
527
|
+
handlerType: z2.string().min(1)
|
|
42
528
|
});
|
|
43
529
|
var robotRockWebhookPayloadSchema = robotRockWebhookPayloadBodySchema.extend({
|
|
44
|
-
headers:
|
|
530
|
+
headers: z2.record(z2.string(), z2.string())
|
|
45
531
|
});
|
|
46
532
|
var RobotRockWebhookError = class extends Error {
|
|
47
533
|
constructor(message, code, details) {
|
|
@@ -140,6 +626,9 @@ export {
|
|
|
140
626
|
TASK_PRIORITY_RANK,
|
|
141
627
|
TaskExpiredError,
|
|
142
628
|
TaskTimeoutError,
|
|
629
|
+
agentCostSchema,
|
|
630
|
+
agentCostTokensSchema,
|
|
631
|
+
agentTelemetrySchema,
|
|
143
632
|
assignToSchema,
|
|
144
633
|
attachWebhookToActions,
|
|
145
634
|
createClient,
|