@relayflows/sdk 2.0.10 → 2.0.11
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/agent-relay-receipt.d.ts +24 -0
- package/dist/agent-relay-receipt.d.ts.map +1 -0
- package/dist/agent-relay-receipt.js +66 -0
- package/dist/agent-relay-receipt.js.map +1 -0
- package/dist/agent-relay-state.d.ts +18 -0
- package/dist/agent-relay-state.d.ts.map +1 -0
- package/dist/agent-relay-state.js +58 -0
- package/dist/agent-relay-state.js.map +1 -0
- package/dist/agent-relay-transport.d.ts +17 -70
- package/dist/agent-relay-transport.d.ts.map +1 -1
- package/dist/agent-relay-transport.js +177 -112
- package/dist/agent-relay-transport.js.map +1 -1
- package/dist/worker-cli.d.ts +5 -2
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +38 -42
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +8 -2
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/agent-relay-receipt.ts +120 -0
- package/src/agent-relay-state.ts +73 -0
- package/src/agent-relay-transport.ts +242 -156
- package/src/worker-cli.ts +44 -50
- package/src/worker.ts +8 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
2
|
+
export declare const nonempty: (value: unknown) => value is string;
|
|
3
|
+
export interface RelayTaskExecution {
|
|
4
|
+
execution_id: string;
|
|
5
|
+
run_id: string;
|
|
6
|
+
step_id: string;
|
|
7
|
+
dispatch_id: string;
|
|
8
|
+
deadline: string;
|
|
9
|
+
worker_generation?: string;
|
|
10
|
+
accepted_at?: string;
|
|
11
|
+
accounting?: Record<string, number>;
|
|
12
|
+
}
|
|
13
|
+
export interface RelayTaskReceipt {
|
|
14
|
+
invocation_id: string;
|
|
15
|
+
action_name: "task.run";
|
|
16
|
+
status: "pending" | "dispatched" | "running" | "completed" | "failed";
|
|
17
|
+
task_execution: RelayTaskExecution;
|
|
18
|
+
output: unknown;
|
|
19
|
+
error: string | null;
|
|
20
|
+
completed_at: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** GET is authoritative. A spawn/POST acknowledgment is never a task result. */
|
|
23
|
+
export declare function readTaskReceipt(value: unknown, invocationId: string, input: Record<string, unknown>, previous?: RelayTaskReceipt): RelayTaskReceipt;
|
|
24
|
+
//# sourceMappingURL=agent-relay-receipt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-relay-receipt.d.ts","sourceRoot":"","sources":["../src/agent-relay-receipt.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CACH,CAAC;AACvE,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MACJ,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AACD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtE,cAAc,EAAE,kBAAkB,CAAC;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,gBAAgB,CAsFlB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { canonicalize } from "./canonical.js";
|
|
2
|
+
export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
export const nonempty = (value) => typeof value === "string" && value.length > 0;
|
|
4
|
+
/** GET is authoritative. A spawn/POST acknowledgment is never a task result. */
|
|
5
|
+
export function readTaskReceipt(value, invocationId, input, previous) {
|
|
6
|
+
const context = input.task_context;
|
|
7
|
+
if (!isRecord(value) ||
|
|
8
|
+
value.invocation_id !== invocationId ||
|
|
9
|
+
value.action_name !== "task.run" ||
|
|
10
|
+
canonicalize(value.input) !== canonicalize(input) ||
|
|
11
|
+
!isRecord(value.task_execution)) {
|
|
12
|
+
throw new Error("Relay task receipt has mismatched invocation or input");
|
|
13
|
+
}
|
|
14
|
+
const execution = value.task_execution;
|
|
15
|
+
const validDate = (date) => nonempty(date) && Number.isFinite(Date.parse(date));
|
|
16
|
+
if (!nonempty(execution.execution_id) ||
|
|
17
|
+
!validDate(execution.deadline) ||
|
|
18
|
+
["run_id", "step_id", "dispatch_id"].some((key) => execution[key] !== context[key]) ||
|
|
19
|
+
!["pending", "dispatched", "running", "completed", "failed"].includes(String(value.status))) {
|
|
20
|
+
throw new Error("Relay task receipt has invalid execution correlation");
|
|
21
|
+
}
|
|
22
|
+
const accepted = nonempty(execution.worker_generation) && validDate(execution.accepted_at);
|
|
23
|
+
if ((value.status === "running" || value.status === "completed") &&
|
|
24
|
+
!accepted) {
|
|
25
|
+
throw new Error("Relay task receipt is missing durable acceptance");
|
|
26
|
+
}
|
|
27
|
+
if ((execution.worker_generation !== undefined ||
|
|
28
|
+
execution.accepted_at !== undefined) &&
|
|
29
|
+
!accepted) {
|
|
30
|
+
throw new Error("Relay task receipt has incomplete durable acceptance");
|
|
31
|
+
}
|
|
32
|
+
if (previous !== undefined &&
|
|
33
|
+
(previous.task_execution.execution_id !== execution.execution_id ||
|
|
34
|
+
previous.task_execution.deadline !== execution.deadline ||
|
|
35
|
+
(previous.task_execution.worker_generation !== undefined &&
|
|
36
|
+
(previous.task_execution.worker_generation !==
|
|
37
|
+
execution.worker_generation ||
|
|
38
|
+
previous.task_execution.accepted_at !== execution.accepted_at)))) {
|
|
39
|
+
throw new Error("Relay task receipt changed its execution, accepted generation/time, or deadline");
|
|
40
|
+
}
|
|
41
|
+
if (execution.accounting !== undefined &&
|
|
42
|
+
(!isRecord(execution.accounting) ||
|
|
43
|
+
Object.values(execution.accounting).some((n) => typeof n !== "number" || !Number.isFinite(n) || n < 0))) {
|
|
44
|
+
throw new Error("Relay task receipt has invalid accounting");
|
|
45
|
+
}
|
|
46
|
+
if (value.status === "completed" &&
|
|
47
|
+
(!Object.hasOwn(value, "output") ||
|
|
48
|
+
value.error !== null ||
|
|
49
|
+
!validDate(value.completed_at))) {
|
|
50
|
+
throw new Error("Relay task receipt has no authoritative final output");
|
|
51
|
+
}
|
|
52
|
+
if (value.status === "failed" &&
|
|
53
|
+
(!nonempty(value.error) || !validDate(value.completed_at))) {
|
|
54
|
+
throw new Error("Relay task failure receipt has no reason");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
invocation_id: invocationId,
|
|
58
|
+
action_name: "task.run",
|
|
59
|
+
status: value.status,
|
|
60
|
+
task_execution: execution,
|
|
61
|
+
output: value.output,
|
|
62
|
+
error: value.error,
|
|
63
|
+
completed_at: value.completed_at,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=agent-relay-receipt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-relay-receipt.js","sourceRoot":"","sources":["../src/agent-relay-receipt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface RelayTaskClaim {
|
|
2
|
+
version: 1;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
callerId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
invocationId: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
stepId: string;
|
|
9
|
+
idempotencyKey: string;
|
|
10
|
+
input: Record<string, unknown>;
|
|
11
|
+
startedAt: number;
|
|
12
|
+
}
|
|
13
|
+
/** Exclusive creation pins the request before POST; no last-writer overwrite. */
|
|
14
|
+
export declare function claimRelayTask(dataDir: string, claim: RelayTaskClaim): Promise<{
|
|
15
|
+
claim: RelayTaskClaim;
|
|
16
|
+
created: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=agent-relay-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-relay-state.d.ts","sourceRoot":"","sources":["../src/agent-relay-state.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAkDtD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { canonicalize } from "./canonical.js";
|
|
5
|
+
/** Exclusive creation pins the request before POST; no last-writer overwrite. */
|
|
6
|
+
export async function claimRelayTask(dataDir, claim) {
|
|
7
|
+
const directory = join(dataDir, "relay-tasks");
|
|
8
|
+
await mkdir(directory, { recursive: true });
|
|
9
|
+
const parent = await open(dataDir, "r");
|
|
10
|
+
try {
|
|
11
|
+
await parent.sync();
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
await parent.close();
|
|
15
|
+
}
|
|
16
|
+
const digest = createHash("sha256")
|
|
17
|
+
.update(canonicalize([claim.runId, claim.stepId, claim.idempotencyKey]))
|
|
18
|
+
.digest("hex");
|
|
19
|
+
const path = join(directory, `${digest}.json`);
|
|
20
|
+
let file;
|
|
21
|
+
try {
|
|
22
|
+
file = await open(path, "wx", 0o600);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "EEXIST")
|
|
26
|
+
throw error;
|
|
27
|
+
const prior = JSON.parse(await readFile(path, "utf8"));
|
|
28
|
+
if (typeof prior !== "object" ||
|
|
29
|
+
prior === null ||
|
|
30
|
+
!("startedAt" in prior) ||
|
|
31
|
+
typeof prior.startedAt !== "number" ||
|
|
32
|
+
!Number.isSafeInteger(prior.startedAt) ||
|
|
33
|
+
prior.startedAt < 0 ||
|
|
34
|
+
canonicalize(prior) !==
|
|
35
|
+
canonicalize({ ...claim, startedAt: prior.startedAt })) {
|
|
36
|
+
throw new Error("Relay task dispatch conflicts with its durable caller, endpoint, or input");
|
|
37
|
+
}
|
|
38
|
+
return { claim: prior, created: false };
|
|
39
|
+
}
|
|
40
|
+
// A crash during creation leaves a partial claim that fails closed on read.
|
|
41
|
+
// Never remove an uncertain claim and retry under a different identity.
|
|
42
|
+
try {
|
|
43
|
+
await file.writeFile(canonicalize(claim));
|
|
44
|
+
await file.sync();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await file.close();
|
|
48
|
+
}
|
|
49
|
+
const dir = await open(directory, "r");
|
|
50
|
+
try {
|
|
51
|
+
await dir.sync();
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await dir.close();
|
|
55
|
+
}
|
|
56
|
+
return { claim, created: true };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=agent-relay-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-relay-state.js","sourceRoot":"","sources":["../src/agent-relay-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAe9C,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAe,EACf,KAAqB;IAErB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC/C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;SAChC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;SACvE,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;QACpE,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAChE,IACE,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,CAAC,CAAC,WAAW,IAAI,KAAK,CAAC;YACvB,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;YACnC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC;YACtC,KAAK,CAAC,SAAS,GAAG,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC;gBACjB,YAAY,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,EACxD,CAAC;YACD,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAuB,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5D,CAAC;IACD,4EAA4E;IAC5E,wEAAwE;IACxE,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -1,84 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* in worker-cli.ts. That is unobservable — you cannot DM the spawned agent
|
|
6
|
-
* mid-flight, and every ecosystem tool that wants to steer or watch it
|
|
7
|
-
* ends up shelling out to the `agent-relay` CLI, which is fragile.
|
|
8
|
-
*
|
|
9
|
-
* This module speaks to agent-relay via structured HTTP: the same call
|
|
10
|
-
* shape the `mcp__agent-relay__spawn` MCP tool posts. The SDK becomes a
|
|
11
|
-
* first-class agent-relay participant, registered under a derived agent
|
|
12
|
-
* name a caller can DM against.
|
|
13
|
-
*
|
|
14
|
-
* Scope: spawn + close. Streaming inbox and observation are follow-ups.
|
|
15
|
-
*/
|
|
16
|
-
export type AgentTransport = 'direct' | 'relay';
|
|
17
|
-
export interface AgentRelaySpawnRequest {
|
|
18
|
-
/** Registered agent name in the workspace. */
|
|
19
|
-
name: string;
|
|
20
|
-
/** CLI to launch. */
|
|
21
|
-
cli: 'claude' | 'codex' | 'gemini' | 'aider' | 'goose' | 'grok' | 'opencode';
|
|
22
|
-
/** Initial task instructions. */
|
|
1
|
+
import { type RelayTaskReceipt } from "./agent-relay-receipt.js";
|
|
2
|
+
export type AgentTransport = "direct" | "relay";
|
|
3
|
+
export interface AgentRelayTaskRequest {
|
|
4
|
+
cli: string;
|
|
23
5
|
task: string;
|
|
24
|
-
/** Optional model powering the worker. */
|
|
25
6
|
model?: string;
|
|
26
|
-
/** Optional working directory for the spawned worker process. */
|
|
27
7
|
worker_cwd?: string;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
project?: string;
|
|
36
|
-
/** Declared workstream for workforce reporting. */
|
|
37
|
-
workstream?: string;
|
|
8
|
+
result_schema?: unknown;
|
|
9
|
+
runId: string;
|
|
10
|
+
stepId: string;
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
dataDir: string;
|
|
13
|
+
/** Engine task contract ceiling; bounded independently of the renewing lease. */
|
|
14
|
+
timeoutMs?: number;
|
|
38
15
|
}
|
|
39
16
|
export interface AgentRelayEnv {
|
|
40
|
-
/** `RELAY_BASE_URL` env; defaults to https://cast.agentrelay.com. */
|
|
41
17
|
baseUrl?: string;
|
|
42
|
-
/** `RELAY_API_KEY` env (rk_live_...). Required. */
|
|
43
|
-
apiKey?: string;
|
|
44
|
-
/** `RELAY_DEFAULT_WORKSPACE` env; workspace scoping for the spawn. */
|
|
45
|
-
workspaceId?: string;
|
|
46
|
-
/** Optional bearer token for the individual agent identity, if pre-registered. */
|
|
47
18
|
agentToken?: string;
|
|
48
19
|
}
|
|
49
|
-
export interface AgentSpawnHandle {
|
|
50
|
-
/** Registered agent name in the workspace — DM this to steer. */
|
|
51
|
-
readonly registeredName: string;
|
|
52
|
-
/** Invocation id returned by relay; use for status polling. */
|
|
53
|
-
readonly invocationId: string;
|
|
54
|
-
/** Best-effort deregistration/notification. */
|
|
55
|
-
close(): Promise<void>;
|
|
56
|
-
}
|
|
57
20
|
export declare class AgentRelayTransportError extends Error {
|
|
58
21
|
readonly cause?: unknown | undefined;
|
|
59
22
|
constructor(message: string, cause?: unknown | undefined);
|
|
60
23
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* unauthenticated and the direct-spawn fallback is a separate decision.
|
|
65
|
-
*/
|
|
66
|
-
export declare function readAgentRelayEnv(env?: NodeJS.ProcessEnv): Required<Pick<AgentRelayEnv, 'baseUrl' | 'apiKey'>> & AgentRelayEnv;
|
|
67
|
-
/**
|
|
68
|
-
* Spawn a worker via agent-relay HTTP. The endpoint mirrors what the
|
|
69
|
-
* `mcp__agent-relay__spawn` MCP tool wraps — a POST that requests a fleet
|
|
70
|
-
* node dispatch. Returns a handle keyed on the registered agent name.
|
|
71
|
-
*
|
|
72
|
-
* The `fetch` argument is injected so tests can mock the transport without
|
|
73
|
-
* hitting the network.
|
|
74
|
-
*/
|
|
75
|
-
export declare function agentRelaySpawn(request: AgentRelaySpawnRequest, env?: AgentRelayEnv & {
|
|
24
|
+
export declare function readAgentRelayEnv(env?: NodeJS.ProcessEnv): Required<AgentRelayEnv>;
|
|
25
|
+
/** Exact Relaycast #436 HTTP contract. Only terminal GET receipts can return. */
|
|
26
|
+
export declare function runAgentRelayTask(request: AgentRelayTaskRequest, options?: AgentRelayEnv & {
|
|
76
27
|
fetch?: typeof fetch;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* result is stable across replays of the same step so DMs can be addressed
|
|
81
|
-
* even during retries.
|
|
82
|
-
*/
|
|
83
|
-
export declare function deriveAgentName(runId: string, stepId: string): string;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
pollMs?: number;
|
|
30
|
+
}): Promise<RelayTaskReceipt>;
|
|
84
31
|
//# sourceMappingURL=agent-relay-transport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-relay-transport.d.ts","sourceRoot":"","sources":["../src/agent-relay-transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-relay-transport.d.ts","sourceRoot":"","sources":["../src/agent-relay-transport.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC;AAChD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,qBAAa,wBAAyB,SAAQ,KAAK;aAG/B,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AACD,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,QAAQ,CAAC,aAAa,CAAC,CAUzB;AAED,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,GAAE,aAAa,GAAG;IACvB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,GACL,OAAO,CAAC,gBAAgB,CAAC,CAmN3B"}
|
|
@@ -1,134 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* ends up shelling out to the `agent-relay` CLI, which is fragile.
|
|
8
|
-
*
|
|
9
|
-
* This module speaks to agent-relay via structured HTTP: the same call
|
|
10
|
-
* shape the `mcp__agent-relay__spawn` MCP tool posts. The SDK becomes a
|
|
11
|
-
* first-class agent-relay participant, registered under a derived agent
|
|
12
|
-
* name a caller can DM against.
|
|
13
|
-
*
|
|
14
|
-
* Scope: spawn + close. Streaming inbox and observation are follow-ups.
|
|
15
|
-
*/
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import { claimRelayTask } from "./agent-relay-state.js";
|
|
5
|
+
import { canonicalize } from "./canonical.js";
|
|
6
|
+
import { isRecord, nonempty, readTaskReceipt, } from "./agent-relay-receipt.js";
|
|
16
7
|
export class AgentRelayTransportError extends Error {
|
|
17
8
|
cause;
|
|
18
9
|
constructor(message, cause) {
|
|
19
10
|
super(message);
|
|
20
11
|
this.cause = cause;
|
|
21
|
-
this.name =
|
|
12
|
+
this.name = "AgentRelayTransportError";
|
|
22
13
|
}
|
|
23
14
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Read the same env the relay MCP already consumes. Explicit override wins.
|
|
26
|
-
* Missing RELAY_API_KEY refuses immediately — the transport cannot proceed
|
|
27
|
-
* unauthenticated and the direct-spawn fallback is a separate decision.
|
|
28
|
-
*/
|
|
29
15
|
export function readAgentRelayEnv(env = process.env) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
throw new AgentRelayTransportError('agent-relay transport requires RELAY_API_KEY in the environment; falling back to direct transport is the caller\'s responsibility.');
|
|
34
|
-
}
|
|
16
|
+
const agentToken = env.RELAY_AGENT_TOKEN?.trim();
|
|
17
|
+
if (!agentToken)
|
|
18
|
+
throw new AgentRelayTransportError("Relay task transport requires a pre-provisioned RELAY_AGENT_TOKEN.");
|
|
35
19
|
return {
|
|
36
|
-
baseUrl,
|
|
37
|
-
|
|
38
|
-
workspaceId: env['RELAY_DEFAULT_WORKSPACE']?.trim() || undefined,
|
|
39
|
-
agentToken: env['RELAY_AGENT_TOKEN']?.trim() || undefined,
|
|
20
|
+
baseUrl: env.RELAY_BASE_URL?.trim() || "https://cast.agentrelay.com",
|
|
21
|
+
agentToken,
|
|
40
22
|
};
|
|
41
23
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* hitting the network.
|
|
49
|
-
*/
|
|
50
|
-
export async function agentRelaySpawn(request, env = {}) {
|
|
51
|
-
// Only fall back to process.env if the caller didn't supply the required
|
|
52
|
-
// fields directly. Tests pass { apiKey, fetch } inline; they shouldn't need
|
|
53
|
-
// to also set RELAY_API_KEY in the runner env.
|
|
54
|
-
const fromEnv = env.apiKey
|
|
55
|
-
? { baseUrl: env.baseUrl || 'https://cast.agentrelay.com' }
|
|
56
|
-
: readAgentRelayEnv();
|
|
57
|
-
const resolved = { ...fromEnv, ...env };
|
|
58
|
-
const url = new URL('/api/v1/agents/spawn', resolved.baseUrl).toString();
|
|
59
|
-
const doFetch = env.fetch ?? globalThis.fetch;
|
|
60
|
-
if (typeof doFetch !== 'function') {
|
|
61
|
-
throw new AgentRelayTransportError('global fetch is unavailable; provide { fetch } explicitly.');
|
|
62
|
-
}
|
|
63
|
-
const headers = {
|
|
64
|
-
'content-type': 'application/json',
|
|
65
|
-
'authorization': `Bearer ${resolved.apiKey}`,
|
|
66
|
-
};
|
|
67
|
-
if (resolved.workspaceId !== undefined)
|
|
68
|
-
headers['x-relay-workspace'] = resolved.workspaceId;
|
|
69
|
-
if (resolved.agentToken !== undefined)
|
|
70
|
-
headers['x-relay-agent-token'] = resolved.agentToken;
|
|
71
|
-
let response;
|
|
72
|
-
try {
|
|
73
|
-
response = await doFetch(url, {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers,
|
|
76
|
-
body: JSON.stringify(request),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch (cause) {
|
|
80
|
-
throw new AgentRelayTransportError(`agent-relay spawn network error at ${url}: ${cause.message ?? String(cause)}`, cause);
|
|
81
|
-
}
|
|
82
|
-
if (response.status < 200 || response.status >= 300) {
|
|
83
|
-
let body;
|
|
84
|
-
try {
|
|
85
|
-
body = (await response.text()).slice(0, 512);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
body = '<no body>';
|
|
24
|
+
/** Exact Relaycast #436 HTTP contract. Only terminal GET receipts can return. */
|
|
25
|
+
export async function runAgentRelayTask(request, options = {}) {
|
|
26
|
+
const env = options.agentToken
|
|
27
|
+
? {
|
|
28
|
+
baseUrl: options.baseUrl || "https://cast.agentrelay.com",
|
|
29
|
+
agentToken: options.agentToken,
|
|
89
30
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
31
|
+
: { ...readAgentRelayEnv(), ...options };
|
|
32
|
+
const base = new URL(env.baseUrl);
|
|
33
|
+
if (base.username ||
|
|
34
|
+
base.password ||
|
|
35
|
+
base.search ||
|
|
36
|
+
base.hash ||
|
|
37
|
+
base.pathname !== "/" ||
|
|
38
|
+
!["https:", "http:"].includes(base.protocol))
|
|
39
|
+
throw new AgentRelayTransportError("Invalid Relay task base URL");
|
|
40
|
+
const loopback = base.hostname === "[::1]" ||
|
|
41
|
+
(isIP(base.hostname) === 4 && base.hostname.startsWith("127."));
|
|
42
|
+
if (base.protocol !== "https:" && !loopback) {
|
|
43
|
+
throw new AgentRelayTransportError("Relay task credentials require HTTPS outside literal loopback addresses");
|
|
95
44
|
}
|
|
96
|
-
|
|
97
|
-
|
|
45
|
+
const baseUrl = base.origin;
|
|
46
|
+
const timeoutMs = request.timeoutMs ?? 86_400_000;
|
|
47
|
+
if (!Number.isSafeInteger(timeoutMs) ||
|
|
48
|
+
timeoutMs < 1 ||
|
|
49
|
+
timeoutMs > 86_400_000 ||
|
|
50
|
+
![
|
|
51
|
+
request.runId,
|
|
52
|
+
request.stepId,
|
|
53
|
+
request.idempotencyKey,
|
|
54
|
+
request.dataDir,
|
|
55
|
+
request.cli,
|
|
56
|
+
].every(nonempty)) {
|
|
57
|
+
throw new AgentRelayTransportError("Relay task requires durable dispatch identity and a valid deadline");
|
|
98
58
|
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
59
|
+
const pollMs = options.pollMs ?? 1000;
|
|
60
|
+
if (!Number.isFinite(pollMs) || pollMs < 1)
|
|
61
|
+
throw new AgentRelayTransportError("Invalid Relay task polling interval");
|
|
62
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
63
|
+
const outer = options.signal ?? new AbortController().signal;
|
|
64
|
+
let deadline = Date.now() + timeoutMs + 30_000;
|
|
65
|
+
let missingDeadline = Date.now() + 30_000;
|
|
66
|
+
async function http(path, body) {
|
|
67
|
+
for (;;) {
|
|
68
|
+
outer.throwIfAborted();
|
|
69
|
+
if (Date.now() >= deadline)
|
|
70
|
+
throw new AgentRelayTransportError("Relay task status unavailable before its reconciliation deadline");
|
|
71
|
+
const bounded = AbortSignal.any([
|
|
72
|
+
outer,
|
|
73
|
+
AbortSignal.timeout(Math.min(15_000, Math.max(1, deadline - Date.now()))),
|
|
74
|
+
]);
|
|
75
|
+
let response;
|
|
76
|
+
let value;
|
|
111
77
|
try {
|
|
112
|
-
await doFetch(
|
|
113
|
-
method:
|
|
114
|
-
|
|
115
|
-
|
|
78
|
+
response = await doFetch(new URL(path, baseUrl), {
|
|
79
|
+
method: body === undefined ? "GET" : "POST",
|
|
80
|
+
redirect: "error",
|
|
81
|
+
signal: bounded,
|
|
82
|
+
headers: {
|
|
83
|
+
authorization: `Bearer ${env.agentToken}`,
|
|
84
|
+
"content-type": "application/json",
|
|
85
|
+
...(body === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { "Idempotency-Key": request.idempotencyKey }),
|
|
88
|
+
},
|
|
89
|
+
...(body === undefined ? {} : { body: canonicalize(body) }),
|
|
116
90
|
});
|
|
91
|
+
if (response.status === 404 &&
|
|
92
|
+
body === undefined &&
|
|
93
|
+
path.includes("/invocations/")) {
|
|
94
|
+
if (Date.now() >= missingDeadline)
|
|
95
|
+
throw new AgentRelayTransportError("Relay task dispatch remains unconfirmed; no repeat POST is permitted");
|
|
96
|
+
}
|
|
97
|
+
else if (!response.ok &&
|
|
98
|
+
response.status !== 429 &&
|
|
99
|
+
response.status < 500) {
|
|
100
|
+
// Never copy untrusted response bodies, URLs, or tokens into diagnostics.
|
|
101
|
+
throw new AgentRelayTransportError(`Relay task request refused with HTTP ${response.status}`);
|
|
102
|
+
}
|
|
103
|
+
if (response.ok)
|
|
104
|
+
value = await response.json();
|
|
105
|
+
if (body !== undefined && !response.ok)
|
|
106
|
+
return {}; // ambiguous POST: GET only below
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
outer.throwIfAborted();
|
|
110
|
+
if (error instanceof AgentRelayTransportError)
|
|
111
|
+
throw error;
|
|
112
|
+
if (body !== undefined)
|
|
113
|
+
return {}; // response loss or invalid JSON never causes another POST
|
|
114
|
+
if (response?.ok && !bounded.aborted)
|
|
115
|
+
throw new AgentRelayTransportError("Relay task response was not valid JSON");
|
|
117
116
|
}
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
outer.throwIfAborted();
|
|
118
|
+
if (response?.ok && value !== undefined) {
|
|
119
|
+
if (!isRecord(value) || value.ok !== true || !isRecord(value.data))
|
|
120
|
+
throw new AgentRelayTransportError("Relay task response has an invalid data envelope");
|
|
121
|
+
return value.data;
|
|
120
122
|
}
|
|
123
|
+
await delay(pollMs, undefined, { signal: outer });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Resolving the agent is read-only. Pin identity before any invocation so a
|
|
127
|
+
// restarted runner cannot create a second task using another caller's key.
|
|
128
|
+
const agent = await http("/v1/agent");
|
|
129
|
+
if (!nonempty(agent.id))
|
|
130
|
+
throw new AgentRelayTransportError("Relay task caller identity is missing");
|
|
131
|
+
if (!nonempty(agent.workspace_id))
|
|
132
|
+
throw new AgentRelayTransportError("Relay task workspace identity is missing");
|
|
133
|
+
// Pinned action-invoke-v1 identity contract from Relaycast #436.
|
|
134
|
+
const invocationId = "inv_idem_" +
|
|
135
|
+
createHash("sha256")
|
|
136
|
+
.update([
|
|
137
|
+
"action-invoke-v1",
|
|
138
|
+
agent.workspace_id,
|
|
139
|
+
agent.id,
|
|
140
|
+
"task.run",
|
|
141
|
+
request.idempotencyKey,
|
|
142
|
+
].join("\0"))
|
|
143
|
+
.digest("hex");
|
|
144
|
+
const input = {
|
|
145
|
+
cli: request.cli,
|
|
146
|
+
task: request.task,
|
|
147
|
+
...(request.model === undefined ? {} : { model: request.model }),
|
|
148
|
+
...(request.worker_cwd === undefined
|
|
149
|
+
? {}
|
|
150
|
+
: { worker_cwd: request.worker_cwd }),
|
|
151
|
+
...(request.result_schema === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { result_schema: request.result_schema }),
|
|
154
|
+
task_context: {
|
|
155
|
+
run_id: request.runId,
|
|
156
|
+
step_id: request.stepId,
|
|
157
|
+
dispatch_id: request.idempotencyKey,
|
|
158
|
+
timeout_ms: timeoutMs,
|
|
121
159
|
},
|
|
122
160
|
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
161
|
+
const { claim, created } = await claimRelayTask(request.dataDir, {
|
|
162
|
+
version: 1,
|
|
163
|
+
baseUrl,
|
|
164
|
+
callerId: agent.id,
|
|
165
|
+
workspaceId: agent.workspace_id,
|
|
166
|
+
invocationId,
|
|
167
|
+
runId: request.runId,
|
|
168
|
+
stepId: request.stepId,
|
|
169
|
+
idempotencyKey: request.idempotencyKey,
|
|
170
|
+
input,
|
|
171
|
+
startedAt: Date.now(),
|
|
172
|
+
});
|
|
173
|
+
// A restarted runner may recover a terminal receipt after the task deadline.
|
|
174
|
+
// Give that read one bounded window; never reopen or extend the remote task.
|
|
175
|
+
deadline = Math.max(claim.startedAt + timeoutMs + 30_000, Date.now() + 15_000);
|
|
176
|
+
missingDeadline = claim.startedAt + 30_000;
|
|
177
|
+
if (created) {
|
|
178
|
+
const ack = await http("/v1/actions/task.run/invoke", { input });
|
|
179
|
+
if (Object.keys(ack).length &&
|
|
180
|
+
(ack.invocation_id !== invocationId ||
|
|
181
|
+
ack.action_name !== "task.run" ||
|
|
182
|
+
canonicalize(ack.input) !== canonicalize(input))) {
|
|
183
|
+
throw new AgentRelayTransportError("Relay task acknowledgment has mismatched invocation or input");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let previous;
|
|
187
|
+
for (;;) {
|
|
188
|
+
const value = await http(`/v1/actions/task.run/invocations/${encodeURIComponent(invocationId)}`);
|
|
189
|
+
if (value.caller_id !== agent.id)
|
|
190
|
+
throw new AgentRelayTransportError("Relay task receipt belongs to another caller");
|
|
191
|
+
const receipt = readTaskReceipt(value, invocationId, input, previous);
|
|
192
|
+
outer.throwIfAborted();
|
|
193
|
+
if (receipt.status === "completed" || receipt.status === "failed")
|
|
194
|
+
return receipt;
|
|
195
|
+
previous = receipt;
|
|
196
|
+
await delay(pollMs, undefined, { signal: outer });
|
|
197
|
+
}
|
|
133
198
|
}
|
|
134
199
|
//# sourceMappingURL=agent-relay-transport.js.map
|