@linkedclaw/consumer-runtime 0.9.1 → 0.9.2
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/index.d.ts +17 -4
- package/dist/index.js +64 -18
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,18 @@ interface HireParams {
|
|
|
6
6
|
maxMessages?: number;
|
|
7
7
|
referredBy?: string;
|
|
8
8
|
autoActivate?: boolean;
|
|
9
|
-
/** Override the default relay URL
|
|
9
|
+
/** Override the default relay URL (defaults to the cloud relay /ws endpoint). */
|
|
10
10
|
relayUrl?: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Try /acp (OpenClaw sub-agent bridge) before /ws. Default false — mirrors
|
|
13
|
+
* `SkillConfig.try_acp = False` on the provider side (upstream commit
|
|
14
|
+
* 2dbc36c). Native providers register on /ws and are reachable there; only
|
|
15
|
+
* opt in when the target is an ACP-routed agent (OpenClaw sub-agent). When
|
|
16
|
+
* opted in, falls back to /ws if /acp connect fails or the recipient is
|
|
17
|
+
* absent from /acp's connection table.
|
|
18
|
+
*/
|
|
19
|
+
tryAcp?: boolean;
|
|
20
|
+
/** API key + agent ID for the IDENTIFY frame. */
|
|
12
21
|
apiKey: string;
|
|
13
22
|
agentId: string;
|
|
14
23
|
}
|
|
@@ -28,10 +37,14 @@ declare class RequesterFlows {
|
|
|
28
37
|
sort?: string;
|
|
29
38
|
}): Promise<Agent[]>;
|
|
30
39
|
/**
|
|
31
|
-
* Open a session.
|
|
32
|
-
*
|
|
40
|
+
* Open a session. HTTP create → WS handshake (SESSION_CREATE/ACCEPT) → done.
|
|
41
|
+
*
|
|
42
|
+
* Default transport is /ws — native providers register there
|
|
43
|
+
* (`SkillConfig.try_acp=False` upstream default). Set `tryAcp: true` to try
|
|
44
|
+
* /acp first; on opt-in, falls back to /ws when the recipient isn't on /acp.
|
|
33
45
|
*/
|
|
34
46
|
hire(params: HireParams): Promise<HireResult>;
|
|
47
|
+
private attemptHandshake;
|
|
35
48
|
send(sessionId: string, payload: Record<string, unknown> | string, seq: number): Promise<{
|
|
36
49
|
accepted?: boolean;
|
|
37
50
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,18 @@ import {
|
|
|
4
4
|
} from "@linkedclaw/consumer";
|
|
5
5
|
import { MessageType } from "@linkedclaw/provider";
|
|
6
6
|
import WebSocket from "ws";
|
|
7
|
-
var
|
|
7
|
+
var ACP_CONNECT_TIMEOUT_MS = 2e3;
|
|
8
|
+
var SESSION_ACCEPT_TIMEOUT_MS = 3e4;
|
|
8
9
|
var SessionRejectedError = class extends Error {
|
|
9
10
|
constructor(reason) {
|
|
10
11
|
super(`session rejected: ${reason}`);
|
|
11
12
|
}
|
|
12
13
|
};
|
|
14
|
+
var TransportMissError = class extends Error {
|
|
15
|
+
constructor(reason) {
|
|
16
|
+
super(`transport miss: ${reason}`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
13
19
|
var RequesterFlows = class {
|
|
14
20
|
constructor(client) {
|
|
15
21
|
this.client = client;
|
|
@@ -19,8 +25,11 @@ var RequesterFlows = class {
|
|
|
19
25
|
return this.client.discover({ capability, ...extra });
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
|
-
* Open a session.
|
|
23
|
-
*
|
|
28
|
+
* Open a session. HTTP create → WS handshake (SESSION_CREATE/ACCEPT) → done.
|
|
29
|
+
*
|
|
30
|
+
* Default transport is /ws — native providers register there
|
|
31
|
+
* (`SkillConfig.try_acp=False` upstream default). Set `tryAcp: true` to try
|
|
32
|
+
* /acp first; on opt-in, falls back to /ws when the recipient isn't on /acp.
|
|
24
33
|
*/
|
|
25
34
|
async hire(params) {
|
|
26
35
|
const session = await this.client.createSession({
|
|
@@ -31,22 +40,57 @@ var RequesterFlows = class {
|
|
|
31
40
|
});
|
|
32
41
|
if (params.autoActivate === false) return { session, activated: false };
|
|
33
42
|
const relayUrl = params.relayUrl ?? DEFAULT_RELAY_URL;
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
try {
|
|
44
|
+
if (params.tryAcp) {
|
|
45
|
+
const acpUrl = relayUrl.replace(/\/ws$/, "/acp");
|
|
46
|
+
try {
|
|
47
|
+
await this.attemptHandshake(acpUrl, session.session_id, params, ACP_CONNECT_TIMEOUT_MS);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (!(err instanceof TransportMissError)) throw err;
|
|
50
|
+
await this.attemptHandshake(relayUrl, session.session_id, params, SESSION_ACCEPT_TIMEOUT_MS);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
await this.attemptHandshake(relayUrl, session.session_id, params, SESSION_ACCEPT_TIMEOUT_MS);
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
await this.client.endSession(session.session_id, {}).catch(() => {
|
|
57
|
+
});
|
|
58
|
+
if (err instanceof TransportMissError) {
|
|
59
|
+
throw new SessionRejectedError(`agent unreachable`);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
return { session, activated: true };
|
|
64
|
+
}
|
|
65
|
+
async attemptHandshake(url, sessionId, params, connectTimeoutMs) {
|
|
66
|
+
const ws = new WebSocket(url);
|
|
36
67
|
try {
|
|
37
68
|
await new Promise((resolve, reject) => {
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
const timer = setTimeout(
|
|
70
|
+
() => reject(new TransportMissError("connect timeout")),
|
|
71
|
+
connectTimeoutMs
|
|
72
|
+
);
|
|
73
|
+
ws.once("open", () => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve();
|
|
76
|
+
});
|
|
77
|
+
ws.once("error", (err) => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
reject(new TransportMissError(`connect failed: ${err.message}`));
|
|
80
|
+
});
|
|
40
81
|
});
|
|
41
82
|
ws.send(JSON.stringify({ type: MessageType.IDENTIFY, agent_id: params.agentId, token: params.apiKey }));
|
|
42
83
|
ws.send(JSON.stringify({
|
|
43
84
|
type: MessageType.SESSION_CREATE,
|
|
44
|
-
session_id:
|
|
85
|
+
session_id: sessionId,
|
|
45
86
|
recipient: params.providerAgentId,
|
|
46
87
|
capability: params.capability
|
|
47
88
|
}));
|
|
48
89
|
const reply = await new Promise((resolve, reject) => {
|
|
49
|
-
const timer = setTimeout(
|
|
90
|
+
const timer = setTimeout(
|
|
91
|
+
() => reject(new Error("SESSION_ACCEPT timeout")),
|
|
92
|
+
SESSION_ACCEPT_TIMEOUT_MS
|
|
93
|
+
);
|
|
50
94
|
ws.once("message", (data) => {
|
|
51
95
|
clearTimeout(timer);
|
|
52
96
|
resolve(JSON.parse(data.toString()));
|
|
@@ -56,17 +100,19 @@ var RequesterFlows = class {
|
|
|
56
100
|
reject(err);
|
|
57
101
|
});
|
|
58
102
|
});
|
|
59
|
-
if (reply.type === MessageType.ERROR)
|
|
103
|
+
if (reply.type === MessageType.ERROR) {
|
|
104
|
+
const errMsg = reply.error ?? "relay error";
|
|
105
|
+
if (errMsg.includes("not connected")) throw new TransportMissError(errMsg);
|
|
106
|
+
throw new SessionRejectedError(errMsg);
|
|
107
|
+
}
|
|
60
108
|
if (reply.type === MessageType.SESSION_REJECT) throw new SessionRejectedError(reply.reason ?? "rejected");
|
|
61
|
-
if (reply.type !== MessageType.SESSION_ACCEPT) throw new Error(`unexpected
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
109
|
+
if (reply.type !== MessageType.SESSION_ACCEPT) throw new Error(`unexpected reply: ${reply.type}`);
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
ws.close();
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
67
115
|
}
|
|
68
|
-
ws.close();
|
|
69
|
-
return { session, activated: true };
|
|
70
116
|
}
|
|
71
117
|
send(sessionId, payload, seq) {
|
|
72
118
|
const normalized = typeof payload === "string" ? { text: payload } : payload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkedclaw/consumer-runtime",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Runtime helpers (RequesterFlows, ACP) for LinkedClaw consumer agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"ws": "^8.18.0",
|
|
25
25
|
"zod": "^3.23.0",
|
|
26
|
-
"@linkedclaw/
|
|
27
|
-
"@linkedclaw/
|
|
26
|
+
"@linkedclaw/consumer": "^0.9.1",
|
|
27
|
+
"@linkedclaw/provider": "^0.9.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@biomejs/biome": "^1.9.4",
|