@annals/agent-bridge 0.1.3 → 0.1.5

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.
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/openclaw-config.ts
4
+ import { readFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+
8
+ // src/utils/logger.ts
9
+ var RESET = "\x1B[0m";
10
+ var RED = "\x1B[31m";
11
+ var GREEN = "\x1B[32m";
12
+ var YELLOW = "\x1B[33m";
13
+ var BLUE = "\x1B[34m";
14
+ var GRAY = "\x1B[90m";
15
+ var BOLD = "\x1B[1m";
16
+ function timestamp() {
17
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
18
+ }
19
+ var log = {
20
+ info(msg, ...args) {
21
+ console.log(`${GRAY}${timestamp()}${RESET} ${BLUE}INFO${RESET} ${msg}`, ...args);
22
+ },
23
+ success(msg, ...args) {
24
+ console.log(`${GRAY}${timestamp()}${RESET} ${GREEN}OK${RESET} ${msg}`, ...args);
25
+ },
26
+ warn(msg, ...args) {
27
+ console.warn(`${GRAY}${timestamp()}${RESET} ${YELLOW}WARN${RESET} ${msg}`, ...args);
28
+ },
29
+ error(msg, ...args) {
30
+ console.error(`${GRAY}${timestamp()}${RESET} ${RED}ERROR${RESET} ${msg}`, ...args);
31
+ },
32
+ debug(msg, ...args) {
33
+ if (process.env.DEBUG) {
34
+ console.log(`${GRAY}${timestamp()} DEBUG ${msg}${RESET}`, ...args);
35
+ }
36
+ },
37
+ banner(text) {
38
+ console.log(`
39
+ ${BOLD}${text}${RESET}
40
+ `);
41
+ }
42
+ };
43
+
44
+ // src/utils/openclaw-config.ts
45
+ var OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
46
+ function readOpenClawConfig(configPath) {
47
+ const path = configPath || OPENCLAW_CONFIG_PATH;
48
+ try {
49
+ const raw = readFileSync(path, "utf-8");
50
+ return JSON.parse(raw);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function isChatCompletionsEnabled(configPath) {
56
+ const config = readOpenClawConfig(configPath);
57
+ if (!config) return false;
58
+ try {
59
+ const enabled = config?.gateway?.http?.endpoints?.chatCompletions?.enabled;
60
+ return enabled === true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function readOpenClawToken(configPath) {
66
+ const path = configPath || OPENCLAW_CONFIG_PATH;
67
+ try {
68
+ const raw = readFileSync(path, "utf-8");
69
+ const config = JSON.parse(raw);
70
+ const token = config?.gateway?.auth?.token;
71
+ if (typeof token === "string" && token.length > 0) {
72
+ return token;
73
+ }
74
+ log.warn("OpenClaw config found but gateway.auth.token is missing or empty");
75
+ return null;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ export {
82
+ log,
83
+ readOpenClawConfig,
84
+ isChatCompletionsEnabled,
85
+ readOpenClawToken
86
+ };
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ log,
4
+ readOpenClawToken
5
+ } from "./chunk-W24WCWEC.js";
2
6
 
3
7
  // src/index.ts
4
8
  import { program } from "commander";
@@ -49,44 +53,6 @@ function hasToken() {
49
53
  import { EventEmitter } from "events";
50
54
  import WebSocket from "ws";
51
55
  import { BRIDGE_PROTOCOL_VERSION } from "@annals/bridge-protocol";
52
-
53
- // src/utils/logger.ts
54
- var RESET = "\x1B[0m";
55
- var RED = "\x1B[31m";
56
- var GREEN = "\x1B[32m";
57
- var YELLOW = "\x1B[33m";
58
- var BLUE = "\x1B[34m";
59
- var GRAY = "\x1B[90m";
60
- var BOLD = "\x1B[1m";
61
- function timestamp() {
62
- return (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
63
- }
64
- var log = {
65
- info(msg, ...args) {
66
- console.log(`${GRAY}${timestamp()}${RESET} ${BLUE}INFO${RESET} ${msg}`, ...args);
67
- },
68
- success(msg, ...args) {
69
- console.log(`${GRAY}${timestamp()}${RESET} ${GREEN}OK${RESET} ${msg}`, ...args);
70
- },
71
- warn(msg, ...args) {
72
- console.warn(`${GRAY}${timestamp()}${RESET} ${YELLOW}WARN${RESET} ${msg}`, ...args);
73
- },
74
- error(msg, ...args) {
75
- console.error(`${GRAY}${timestamp()}${RESET} ${RED}ERROR${RESET} ${msg}`, ...args);
76
- },
77
- debug(msg, ...args) {
78
- if (process.env.DEBUG) {
79
- console.log(`${GRAY}${timestamp()} DEBUG ${msg}${RESET}`, ...args);
80
- }
81
- },
82
- banner(text) {
83
- console.log(`
84
- ${BOLD}${text}${RESET}
85
- `);
86
- }
87
- };
88
-
89
- // src/platform/ws-client.ts
90
56
  var HEARTBEAT_INTERVAL = 3e4;
91
57
  var INITIAL_RECONNECT_DELAY = 1e3;
92
58
  var MAX_RECONNECT_DELAY = 3e4;
@@ -399,39 +365,34 @@ var BridgeManager = class {
399
365
  }
400
366
  };
401
367
 
402
- // src/adapters/openclaw.ts
403
- import WebSocket2 from "ws";
404
- import { randomUUID } from "crypto";
405
-
406
368
  // src/adapters/base.ts
407
369
  var AgentAdapter = class {
408
370
  };
409
371
 
410
372
  // src/adapters/openclaw.ts
411
- var DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
373
+ var DEFAULT_GATEWAY_URL = "http://127.0.0.1:18789";
374
+ function normalizeUrl(url) {
375
+ if (url.startsWith("wss://")) return url.replace("wss://", "https://");
376
+ if (url.startsWith("ws://")) return url.replace("ws://", "http://");
377
+ return url;
378
+ }
412
379
  var OpenClawSession = class {
413
380
  constructor(sessionId, config) {
414
381
  this.config = config;
415
- this.gatewayUrl = config.gatewayUrl || DEFAULT_GATEWAY_URL;
416
- this.gatewayToken = config.gatewayToken || "";
417
- this.sessionKey = `bridge:${sessionId}`;
418
- }
382
+ this.baseUrl = normalizeUrl(config.gatewayUrl || DEFAULT_GATEWAY_URL);
383
+ this.token = config.gatewayToken || "";
384
+ this.sessionKey = sessionId;
385
+ }
386
+ messages = [];
387
+ abortController = null;
388
+ baseUrl;
389
+ token;
390
+ sessionKey;
419
391
  chunkCallbacks = [];
420
392
  doneCallbacks = [];
421
393
  errorCallbacks = [];
422
- fullText = "";
423
- ws = null;
424
- isConnected = false;
425
- gatewayUrl;
426
- gatewayToken;
427
- sessionKey;
428
394
  send(message, _attachments) {
429
- if (this.ws && this.ws.readyState === WebSocket2.OPEN && this.isConnected) {
430
- this.sendAgentRequest(message);
431
- return;
432
- }
433
- this.fullText = "";
434
- this.connectAndSend(message);
395
+ this.sendRequest(message);
435
396
  }
436
397
  onChunk(cb) {
437
398
  this.chunkCallbacks.push(cb);
@@ -443,130 +404,78 @@ var OpenClawSession = class {
443
404
  this.errorCallbacks.push(cb);
444
405
  }
445
406
  kill() {
446
- if (this.ws) {
447
- this.ws.close();
448
- this.ws = null;
449
- }
450
- this.isConnected = false;
451
- }
452
- connectAndSend(message) {
453
- try {
454
- this.ws = new WebSocket2(this.gatewayUrl);
455
- } catch (err) {
456
- this.emitError(new Error(`Failed to connect to OpenClaw: ${err}`));
457
- return;
458
- }
459
- this.ws.on("open", () => {
460
- log.debug(`OpenClaw WS connected to ${this.gatewayUrl}`);
461
- const connectMsg = {
462
- type: "req",
463
- id: randomUUID(),
464
- method: "connect",
465
- params: {
466
- minProtocol: 3,
467
- maxProtocol: 3,
468
- client: {
469
- id: "gateway-client",
470
- displayName: "Agent Bridge CLI",
471
- version: "0.1.0",
472
- platform: "node",
473
- mode: "backend"
474
- },
475
- role: "operator",
476
- scopes: ["operator.read", "operator.write"],
477
- caps: [],
478
- commands: [],
479
- permissions: {},
480
- auth: { token: this.gatewayToken }
481
- }
482
- };
483
- this.ws.send(JSON.stringify(connectMsg));
484
- });
485
- this.ws.on("message", (data) => {
486
- this.handleMessage(data.toString(), message);
487
- });
488
- this.ws.on("error", (err) => {
489
- this.emitError(new Error(`OpenClaw WebSocket error: ${err.message}`));
490
- });
491
- this.ws.on("close", () => {
492
- this.isConnected = false;
493
- });
407
+ this.abortController?.abort();
408
+ this.abortController = null;
494
409
  }
495
- handleMessage(raw, pendingMessage) {
496
- let msg;
410
+ async sendRequest(message) {
411
+ this.messages.push({ role: "user", content: message });
412
+ this.abortController = new AbortController();
497
413
  try {
498
- msg = JSON.parse(raw);
499
- } catch {
500
- return;
501
- }
502
- if (msg.type === "event" && msg.event !== "agent") {
503
- return;
504
- }
505
- if (msg.type === "res" && !this.isConnected) {
506
- if (msg.ok && msg.payload?.type === "hello-ok") {
507
- this.isConnected = true;
508
- log.debug("OpenClaw handshake complete");
509
- if (pendingMessage) {
510
- this.sendAgentRequest(pendingMessage);
511
- }
512
- } else {
513
- this.emitError(
514
- new Error(`OpenClaw auth failed: ${msg.error?.message || "unknown"}`)
515
- );
516
- this.ws?.close();
517
- }
518
- return;
519
- }
520
- if (msg.type === "event" && msg.event === "agent" && msg.payload) {
521
- const { stream, data } = msg.payload;
522
- if (stream === "assistant" && data?.text) {
523
- const prevLen = this.fullText.length;
524
- this.fullText = data.text;
525
- if (this.fullText.length > prevLen) {
526
- const delta = this.fullText.slice(prevLen);
527
- for (const cb of this.chunkCallbacks) cb(delta);
528
- }
414
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
415
+ method: "POST",
416
+ headers: {
417
+ "Content-Type": "application/json",
418
+ "Authorization": `Bearer ${this.token}`,
419
+ "x-openclaw-session-key": this.sessionKey
420
+ },
421
+ body: JSON.stringify({
422
+ model: "openclaw:main",
423
+ messages: [...this.messages],
424
+ stream: true
425
+ }),
426
+ signal: this.abortController.signal
427
+ });
428
+ if (!response.ok) {
429
+ const text = await response.text().catch(() => "");
430
+ this.emitError(new Error(`OpenClaw HTTP ${response.status}: ${text || response.statusText}`));
431
+ return;
529
432
  }
530
- if (stream === "lifecycle" && data?.phase === "end") {
531
- for (const cb of this.doneCallbacks) cb();
433
+ if (!response.body) {
434
+ this.emitError(new Error("OpenClaw response has no body"));
435
+ return;
532
436
  }
533
- return;
534
- }
535
- if (msg.type === "res" && this.isConnected) {
536
- if (msg.ok && msg.payload) {
537
- if (msg.payload.status === "accepted") {
538
- return;
539
- }
540
- if (msg.payload.response) {
541
- for (const cb of this.chunkCallbacks) cb(msg.payload.response);
437
+ const reader = response.body.getReader();
438
+ const decoder = new TextDecoder();
439
+ let buffer = "";
440
+ let fullText = "";
441
+ while (true) {
442
+ const { done, value } = await reader.read();
443
+ if (done) break;
444
+ buffer += decoder.decode(value, { stream: true });
445
+ const lines = buffer.split("\n");
446
+ buffer = lines.pop();
447
+ for (const line of lines) {
448
+ const trimmed = line.trim();
449
+ if (!trimmed || trimmed.startsWith(":")) continue;
450
+ if (!trimmed.startsWith("data: ")) continue;
451
+ const data = trimmed.slice(6);
452
+ if (data === "[DONE]") {
453
+ if (fullText) {
454
+ this.messages.push({ role: "assistant", content: fullText });
455
+ }
456
+ for (const cb of this.doneCallbacks) cb();
457
+ return;
458
+ }
459
+ try {
460
+ const parsed = JSON.parse(data);
461
+ const content = parsed.choices?.[0]?.delta?.content;
462
+ if (content) {
463
+ fullText += content;
464
+ for (const cb of this.chunkCallbacks) cb(content);
465
+ }
466
+ } catch {
467
+ }
542
468
  }
543
- for (const cb of this.doneCallbacks) cb();
544
- } else {
545
- this.emitError(
546
- new Error(`OpenClaw error: ${msg.error?.message || "unknown"}`)
547
- );
548
469
  }
549
- return;
550
- }
551
- if (msg.type === "error") {
552
- this.emitError(new Error(`OpenClaw error: ${msg.message || "unknown"}`));
553
- }
554
- }
555
- sendAgentRequest(message) {
556
- const req = {
557
- type: "req",
558
- id: randomUUID(),
559
- method: "agent",
560
- params: {
561
- message,
562
- sessionKey: this.sessionKey,
563
- idempotencyKey: `idem-${Date.now()}-${randomUUID().slice(0, 8)}`
470
+ if (fullText) {
471
+ this.messages.push({ role: "assistant", content: fullText });
564
472
  }
565
- };
566
- try {
567
- this.ws.send(JSON.stringify(req));
473
+ for (const cb of this.doneCallbacks) cb();
568
474
  } catch (err) {
569
- this.emitError(new Error(`Failed to send to OpenClaw: ${err}`));
475
+ if (err instanceof Error && err.name === "AbortError") {
476
+ return;
477
+ }
478
+ this.emitError(err instanceof Error ? err : new Error(String(err)));
570
479
  }
571
480
  }
572
481
  emitError(err) {
@@ -587,30 +496,28 @@ var OpenClawAdapter = class extends AgentAdapter {
587
496
  this.config = config;
588
497
  }
589
498
  async isAvailable() {
590
- const url = this.config.gatewayUrl || DEFAULT_GATEWAY_URL;
591
- return new Promise((resolve) => {
592
- let ws;
593
- const timer = setTimeout(() => {
594
- ws?.close();
595
- resolve(false);
596
- }, 5e3);
597
- try {
598
- ws = new WebSocket2(url);
599
- } catch {
600
- clearTimeout(timer);
601
- resolve(false);
602
- return;
603
- }
604
- ws.on("open", () => {
605
- clearTimeout(timer);
606
- ws.close();
607
- resolve(true);
608
- });
609
- ws.on("error", () => {
610
- clearTimeout(timer);
611
- resolve(false);
499
+ const baseUrl = normalizeUrl(this.config.gatewayUrl || DEFAULT_GATEWAY_URL);
500
+ try {
501
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
502
+ method: "POST",
503
+ headers: { "Content-Type": "application/json" },
504
+ body: JSON.stringify({
505
+ model: "openclaw:main",
506
+ messages: [],
507
+ stream: false
508
+ }),
509
+ signal: AbortSignal.timeout(5e3)
612
510
  });
613
- });
511
+ if (response.status === 404) {
512
+ log.warn(
513
+ "OpenClaw endpoint not found. Enable chatCompletions in openclaw.json."
514
+ );
515
+ return false;
516
+ }
517
+ return true;
518
+ } catch {
519
+ return false;
520
+ }
614
521
  }
615
522
  createSession(id, config) {
616
523
  const merged = { ...this.config, ...config };
@@ -1112,27 +1019,6 @@ var GeminiAdapter = class extends AgentAdapter {
1112
1019
  }
1113
1020
  };
1114
1021
 
1115
- // src/utils/openclaw-config.ts
1116
- import { readFileSync as readFileSync2 } from "fs";
1117
- import { join as join4 } from "path";
1118
- import { homedir as homedir2 } from "os";
1119
- var OPENCLAW_CONFIG_PATH = join4(homedir2(), ".openclaw", "openclaw.json");
1120
- function readOpenClawToken(configPath) {
1121
- const path = configPath || OPENCLAW_CONFIG_PATH;
1122
- try {
1123
- const raw = readFileSync2(path, "utf-8");
1124
- const config = JSON.parse(raw);
1125
- const token = config?.gateway?.auth?.token;
1126
- if (typeof token === "string" && token.length > 0) {
1127
- return token;
1128
- }
1129
- log.warn("OpenClaw config found but gateway.auth.token is missing or empty");
1130
- return null;
1131
- } catch {
1132
- return null;
1133
- }
1134
- }
1135
-
1136
1022
  // src/commands/connect.ts
1137
1023
  var DEFAULT_BRIDGE_URL = "wss://bridge.agents.hot/ws";
1138
1024
  function createAdapter(type, config) {
@@ -1219,6 +1105,14 @@ function registerConnectCommand(program2) {
1219
1105
  log.warn("Sandbox not available on this platform, continuing without sandbox");
1220
1106
  }
1221
1107
  }
1108
+ if (agentType === "openclaw") {
1109
+ const { isChatCompletionsEnabled } = await import("./openclaw-config-OFFNWVDK.js");
1110
+ if (!isChatCompletionsEnabled()) {
1111
+ log.warn(
1112
+ 'OpenClaw chatCompletions endpoint may not be enabled.\n Add to ~/.openclaw/openclaw.json:\n { "gateway": { "http": { "endpoints": { "chatCompletions": { "enabled": true } } } } }\n Continuing anyway (gateway may be on a remote host)...'
1113
+ );
1114
+ }
1115
+ }
1222
1116
  const adapterConfig = {
1223
1117
  project: opts.project,
1224
1118
  gatewayUrl: opts.gatewayUrl || config.gatewayUrl,
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isChatCompletionsEnabled,
4
+ readOpenClawConfig,
5
+ readOpenClawToken
6
+ } from "./chunk-W24WCWEC.js";
7
+ export {
8
+ isChatCompletionsEnabled,
9
+ readOpenClawConfig,
10
+ readOpenClawToken
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annals/agent-bridge",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "CLI bridge connecting local AI agents to the Agents.Hot platform",
5
5
  "type": "module",
6
6
  "bin": {