aibroker 0.5.1 → 0.6.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/README.md +263 -104
- package/dist/adapters/iterm/iterm2-api.d.ts +20 -0
- package/dist/adapters/iterm/iterm2-api.d.ts.map +1 -0
- package/dist/adapters/iterm/iterm2-api.js +244 -0
- package/dist/adapters/iterm/iterm2-api.js.map +1 -0
- package/dist/adapters/iterm/sessions.d.ts +1 -0
- package/dist/adapters/iterm/sessions.d.ts.map +1 -1
- package/dist/adapters/iterm/sessions.js +26 -2
- package/dist/adapters/iterm/sessions.js.map +1 -1
- package/dist/adapters/kokoro/media.js +2 -2
- package/dist/adapters/kokoro/media.js.map +1 -1
- package/dist/adapters/pailot/gateway.d.ts +5 -6
- package/dist/adapters/pailot/gateway.d.ts.map +1 -1
- package/dist/adapters/pailot/gateway.js +575 -34
- package/dist/adapters/pailot/gateway.js.map +1 -1
- package/dist/aibp/bridge.d.ts +123 -0
- package/dist/aibp/bridge.d.ts.map +1 -0
- package/dist/aibp/bridge.js +363 -0
- package/dist/aibp/bridge.js.map +1 -0
- package/dist/aibp/envelope.d.ts +26 -0
- package/dist/aibp/envelope.d.ts.map +1 -0
- package/dist/aibp/envelope.js +101 -0
- package/dist/aibp/envelope.js.map +1 -0
- package/dist/aibp/index.d.ts +11 -0
- package/dist/aibp/index.d.ts.map +1 -0
- package/dist/aibp/index.js +10 -0
- package/dist/aibp/index.js.map +1 -0
- package/dist/aibp/registry.d.ts +71 -0
- package/dist/aibp/registry.d.ts.map +1 -0
- package/dist/aibp/registry.js +408 -0
- package/dist/aibp/registry.js.map +1 -0
- package/dist/aibp/types.d.ts +91 -0
- package/dist/aibp/types.d.ts.map +1 -0
- package/dist/aibp/types.js +8 -0
- package/dist/aibp/types.js.map +1 -0
- package/dist/core/hybrid.d.ts +2 -0
- package/dist/core/hybrid.d.ts.map +1 -1
- package/dist/core/hybrid.js +8 -0
- package/dist/core/hybrid.js.map +1 -1
- package/dist/core/state.d.ts +12 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +34 -0
- package/dist/core/state.js.map +1 -1
- package/dist/core/status-cache.d.ts +51 -0
- package/dist/core/status-cache.d.ts.map +1 -0
- package/dist/core/status-cache.js +62 -0
- package/dist/core/status-cache.js.map +1 -0
- package/dist/daemon/adapter-registry.d.ts +5 -0
- package/dist/daemon/adapter-registry.d.ts.map +1 -1
- package/dist/daemon/adapter-registry.js +94 -4
- package/dist/daemon/adapter-registry.js.map +1 -1
- package/dist/daemon/cli.d.ts +1 -0
- package/dist/daemon/cli.d.ts.map +1 -1
- package/dist/daemon/cli.js +95 -3
- package/dist/daemon/cli.js.map +1 -1
- package/dist/daemon/command-context.d.ts +28 -0
- package/dist/daemon/command-context.d.ts.map +1 -0
- package/dist/daemon/command-context.js +13 -0
- package/dist/daemon/command-context.js.map +1 -0
- package/dist/daemon/commands.d.ts +22 -0
- package/dist/daemon/commands.d.ts.map +1 -0
- package/dist/daemon/commands.js +849 -0
- package/dist/daemon/commands.js.map +1 -0
- package/dist/daemon/core-handlers.d.ts.map +1 -1
- package/dist/daemon/core-handlers.js +758 -3
- package/dist/daemon/core-handlers.js.map +1 -1
- package/dist/daemon/create-adapter.js +2 -1
- package/dist/daemon/create-adapter.js.map +1 -1
- package/dist/daemon/image-context.d.ts +56 -0
- package/dist/daemon/image-context.d.ts.map +1 -0
- package/dist/daemon/image-context.js +116 -0
- package/dist/daemon/image-context.js.map +1 -0
- package/dist/daemon/image-gen/index.d.ts +22 -0
- package/dist/daemon/image-gen/index.d.ts.map +1 -0
- package/dist/daemon/image-gen/index.js +129 -0
- package/dist/daemon/image-gen/index.js.map +1 -0
- package/dist/daemon/image-gen/providers/cloudflare.d.ts +13 -0
- package/dist/daemon/image-gen/providers/cloudflare.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/cloudflare.js +63 -0
- package/dist/daemon/image-gen/providers/cloudflare.js.map +1 -0
- package/dist/daemon/image-gen/providers/huggingface.d.ts +12 -0
- package/dist/daemon/image-gen/providers/huggingface.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/huggingface.js +58 -0
- package/dist/daemon/image-gen/providers/huggingface.js.map +1 -0
- package/dist/daemon/image-gen/providers/pollinations.d.ts +11 -0
- package/dist/daemon/image-gen/providers/pollinations.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/pollinations.js +39 -0
- package/dist/daemon/image-gen/providers/pollinations.js.map +1 -0
- package/dist/daemon/image-gen/providers/replicate.d.ts +9 -0
- package/dist/daemon/image-gen/providers/replicate.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/replicate.js +158 -0
- package/dist/daemon/image-gen/providers/replicate.js.map +1 -0
- package/dist/daemon/image-gen/types.d.ts +41 -0
- package/dist/daemon/image-gen/types.d.ts.map +1 -0
- package/dist/daemon/image-gen/types.js +5 -0
- package/dist/daemon/image-gen/types.js.map +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +260 -6
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/screenshot.d.ts +12 -0
- package/dist/daemon/screenshot.d.ts.map +1 -0
- package/dist/daemon/screenshot.js +252 -0
- package/dist/daemon/screenshot.js.map +1 -0
- package/dist/daemon/session-content.d.ts +27 -0
- package/dist/daemon/session-content.d.ts.map +1 -0
- package/dist/daemon/session-content.js +76 -0
- package/dist/daemon/session-content.js.map +1 -0
- package/dist/daemon/vision.d.ts +46 -0
- package/dist/daemon/vision.d.ts.map +1 -0
- package/dist/daemon/vision.js +176 -0
- package/dist/daemon/vision.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/validate.d.ts +52 -0
- package/dist/ipc/validate.d.ts.map +1 -0
- package/dist/ipc/validate.js +129 -0
- package/dist/ipc/validate.js.map +1 -0
- package/dist/mcp/index.d.ts +23 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +787 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/broker.d.ts +3 -1
- package/dist/types/broker.d.ts.map +1 -1
- package/dist/types/broker.js.map +1 -1
- package/package.json +5 -2
- package/templates/adapter/ONBOARDING_PROMPT.md +51 -29
- package/templates/adapter/README.md.tmpl +14 -31
- package/templates/adapter/package.json.tmpl +1 -1
- package/templates/adapter/src/watcher/commands.ts.tmpl +24 -126
- package/templates/adapter/src/watcher/index.ts.tmpl +112 -88
- package/templates/adapter/src/watcher/ipc-server.ts.tmpl +27 -3
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* watcher/index.ts — {{DISPLAY_NAME}} adapter entry point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. Command router — createMessageHandler() dispatches text / slash commands
|
|
8
|
-
* 4. IPC server — startIpcServer() accepts calls from hub and MCP tools
|
|
9
|
-
* 5. Hub registration — if hub is running, announce this adapter's socket
|
|
4
|
+
* Thin transport adapter: connects to {{DISPLAY_NAME}}, forwards all messages
|
|
5
|
+
* to the AIBroker hub daemon for processing, and delivers hub responses back
|
|
6
|
+
* via the upstream service.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
* watch() → detectHubMode() → connectWatcher() → startIpcServer()
|
|
13
|
-
* → [hub mode] register_adapter on hub
|
|
14
|
-
* → await forever (exits on SIGINT / SIGTERM)
|
|
8
|
+
* Requires the AIBroker daemon to be running. Does not function standalone.
|
|
15
9
|
*/
|
|
16
10
|
|
|
17
11
|
import { unlinkSync } from "node:fs";
|
|
@@ -22,11 +16,6 @@ import {
|
|
|
22
16
|
WatcherClient,
|
|
23
17
|
DAEMON_SOCKET_PATH,
|
|
24
18
|
setAppDir,
|
|
25
|
-
loadSessionRegistry,
|
|
26
|
-
APIBackend,
|
|
27
|
-
HybridSessionManager,
|
|
28
|
-
setHybridManager,
|
|
29
|
-
router,
|
|
30
19
|
log,
|
|
31
20
|
setLogPrefix,
|
|
32
21
|
createBrokerMessage,
|
|
@@ -41,84 +30,97 @@ import {
|
|
|
41
30
|
setConnectionStatus,
|
|
42
31
|
} from "./state.js";
|
|
43
32
|
|
|
44
|
-
// ── Hub
|
|
33
|
+
// ── Hub connection ──────────────────────────────────────────────────────────
|
|
45
34
|
|
|
46
35
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* Returns true if the hub is reachable on its standard socket within 2 seconds.
|
|
50
|
-
* On any failure the adapter runs in embedded (standalone) mode.
|
|
36
|
+
* Connect to the AIBroker hub daemon. Retries up to 3 times with 2s delay.
|
|
37
|
+
* Throws if the hub is not reachable — adapter cannot function without it.
|
|
51
38
|
*/
|
|
52
|
-
async function
|
|
39
|
+
async function connectToHub(): Promise<WatcherClient> {
|
|
53
40
|
const client = new WatcherClient(DAEMON_SOCKET_PATH);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
41
|
+
const MAX_RETRIES = 3;
|
|
42
|
+
const RETRY_DELAY = 2000;
|
|
43
|
+
|
|
44
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
const result = await Promise.race([
|
|
47
|
+
client.call_raw("ping", {}),
|
|
48
|
+
new Promise<null>((_, reject) =>
|
|
49
|
+
setTimeout(() => reject(new Error("timeout")), 2000),
|
|
50
|
+
),
|
|
51
|
+
]);
|
|
52
|
+
if (result !== null) return client;
|
|
53
|
+
} catch {
|
|
54
|
+
if (attempt < MAX_RETRIES) {
|
|
55
|
+
log(`Hub not reachable (attempt ${attempt}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}ms...`);
|
|
56
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
62
59
|
}
|
|
60
|
+
|
|
61
|
+
throw new Error(
|
|
62
|
+
`AIBroker daemon not reachable at ${DAEMON_SOCKET_PATH}. ` +
|
|
63
|
+
`Start it with: aibroker start`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Slash commands handled locally (require direct upstream service access).
|
|
69
|
+
* Everything else goes to the hub.
|
|
70
|
+
*/
|
|
71
|
+
const LOCAL_SLASH_COMMANDS = new Set(["/restart", "/login"]);
|
|
72
|
+
|
|
73
|
+
function isLocalSlashCommand(text: string): boolean {
|
|
74
|
+
return LOCAL_SLASH_COMMANDS.has(text.trim());
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
66
78
|
|
|
67
79
|
/**
|
|
68
|
-
* Start the {{DISPLAY_NAME}} adapter watcher.
|
|
80
|
+
* Start the {{DISPLAY_NAME}} adapter watcher — thin transport adapter.
|
|
69
81
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
82
|
+
* All command handling, session management, TTS, and screenshots are owned
|
|
83
|
+
* by the AIBroker hub daemon. This process only:
|
|
84
|
+
* 1. Connects to the upstream service
|
|
85
|
+
* 2. Forwards incoming messages to the hub
|
|
86
|
+
* 3. Delivers hub-originated messages via the upstream service (through IPC "deliver")
|
|
87
|
+
* 4. Handles /restart and /login locally
|
|
72
88
|
*/
|
|
73
89
|
export async function watch(): Promise<void> {
|
|
74
90
|
setLogPrefix("{{ADAPTER_NAME}}-watch");
|
|
75
91
|
setAppDir(join(homedir(), ".{{ADAPTER_NAME}}"));
|
|
76
92
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
skipDefaultSession: true,
|
|
87
|
-
});
|
|
88
|
-
const manager = new HybridSessionManager(apiBackend);
|
|
89
|
-
setHybridManager(manager);
|
|
90
|
-
manager.createApiSession("Default", process.env.AIBROKER_CWD ?? homedir());
|
|
91
|
-
router.setDefaultBackend(apiBackend);
|
|
92
|
-
|
|
93
|
-
// Detect whether the AIBroker hub daemon is running
|
|
94
|
-
const hubMode = await detectHubMode();
|
|
95
|
-
const hubClient = hubMode ? new WatcherClient(DAEMON_SOCKET_PATH) : null;
|
|
93
|
+
// Connect to AIBroker hub (required — no standalone mode)
|
|
94
|
+
let hubClient: WatcherClient;
|
|
95
|
+
try {
|
|
96
|
+
hubClient = await connectToHub();
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`[{{ADAPTER_NAME}}-watch] FATAL: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
+
console.error("[{{ADAPTER_NAME}}-watch] The AIBroker daemon must be running. Exiting.");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
96
102
|
|
|
97
103
|
console.log(`{{DISPLAY_NAME}} Adapter Watch`);
|
|
98
104
|
console.log(` Socket: ${ADAPTER_SOCKET_PATH}`);
|
|
99
|
-
console.log(`
|
|
105
|
+
console.log(` Hub: ${DAEMON_SOCKET_PATH}`);
|
|
100
106
|
console.log();
|
|
101
107
|
|
|
102
|
-
// Restore persisted session state
|
|
103
|
-
loadSessionRegistry();
|
|
104
|
-
|
|
105
108
|
// Graceful shutdown
|
|
106
109
|
let cleanupConnection: (() => void) | null = null;
|
|
107
110
|
|
|
108
111
|
const shutdown = (signal: string) => {
|
|
109
112
|
console.log(`\n[{{ADAPTER_NAME}}-watch] ${signal} received. Stopping.`);
|
|
113
|
+
clearInterval(heartbeatTimer);
|
|
110
114
|
setConnectionStatus("disconnected");
|
|
111
115
|
if (cleanupConnection) cleanupConnection();
|
|
112
|
-
|
|
113
|
-
hubClient.call_raw("unregister_adapter", { name: "{{ADAPTER_NAME}}" }).catch(() => {});
|
|
114
|
-
}
|
|
116
|
+
hubClient.call_raw("unregister_adapter", { name: "{{ADAPTER_NAME}}" }).catch(() => {});
|
|
115
117
|
try { unlinkSync(ADAPTER_SOCKET_PATH); } catch { /* ignore */ }
|
|
116
118
|
process.exit(0);
|
|
117
119
|
};
|
|
118
120
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
119
121
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
120
122
|
|
|
121
|
-
//
|
|
123
|
+
// Local handler for /restart and /login only
|
|
122
124
|
const handleMessage = createMessageHandler();
|
|
123
125
|
|
|
124
126
|
// Connect to the upstream service
|
|
@@ -131,25 +133,25 @@ export async function watch(): Promise<void> {
|
|
|
131
133
|
adapterStats.messagesReceived++;
|
|
132
134
|
adapterStats.lastMessageAt = timestamp;
|
|
133
135
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const message = createBrokerMessage(
|
|
137
|
-
"{{ADAPTER_NAME}}",
|
|
138
|
-
text.trim().startsWith("/") ? "command" : "text",
|
|
139
|
-
{ text },
|
|
140
|
-
);
|
|
141
|
-
message.timestamp = timestamp;
|
|
142
|
-
|
|
143
|
-
hubClient.call_raw("route_message", {
|
|
144
|
-
message: message as unknown as Record<string, unknown>,
|
|
145
|
-
}).catch((err) => {
|
|
146
|
-
log(`Hub route_message failed, handling locally: ${err instanceof Error ? err.message : String(err)}`);
|
|
147
|
-
handleMessage(text, timestamp);
|
|
148
|
-
});
|
|
149
|
-
} else {
|
|
150
|
-
// Embedded mode: handle everything locally
|
|
136
|
+
// Local commands stay in the adapter
|
|
137
|
+
if (isLocalSlashCommand(text)) {
|
|
151
138
|
handleMessage(text, timestamp);
|
|
139
|
+
return;
|
|
152
140
|
}
|
|
141
|
+
|
|
142
|
+
// Everything else → hub
|
|
143
|
+
const message = createBrokerMessage(
|
|
144
|
+
"{{ADAPTER_NAME}}",
|
|
145
|
+
text.trim().startsWith("/") ? "command" : "text",
|
|
146
|
+
{ text },
|
|
147
|
+
);
|
|
148
|
+
message.timestamp = timestamp;
|
|
149
|
+
|
|
150
|
+
hubClient.call_raw("route_message", {
|
|
151
|
+
message: message as unknown as Record<string, unknown>,
|
|
152
|
+
}).catch((err) => {
|
|
153
|
+
log(`Hub route_message failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
154
|
+
});
|
|
153
155
|
},
|
|
154
156
|
);
|
|
155
157
|
|
|
@@ -159,18 +161,40 @@ export async function watch(): Promise<void> {
|
|
|
159
161
|
// Start the adapter IPC server
|
|
160
162
|
startIpcServer(triggerLogin);
|
|
161
163
|
|
|
162
|
-
// Register with hub
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
164
|
+
// Register with hub
|
|
165
|
+
hubClient.call_raw("register_adapter", {
|
|
166
|
+
name: "{{ADAPTER_NAME}}",
|
|
167
|
+
socketPath: ADAPTER_SOCKET_PATH,
|
|
168
|
+
}).then(() => {
|
|
169
|
+
log("Registered with AIBroker hub daemon");
|
|
170
|
+
}).catch((err) => {
|
|
171
|
+
log(`Hub registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Hub heartbeat — re-register if the daemon restarts
|
|
175
|
+
const HUB_HEARTBEAT_INTERVAL = 30_000; // 30 seconds
|
|
176
|
+
const heartbeatTimer = setInterval(async () => {
|
|
177
|
+
try {
|
|
178
|
+
const result = await Promise.race([
|
|
179
|
+
hubClient.call_raw("ping", {}),
|
|
180
|
+
new Promise<null>((_, reject) =>
|
|
181
|
+
setTimeout(() => reject(new Error("timeout")), 5000),
|
|
182
|
+
),
|
|
183
|
+
]);
|
|
184
|
+
if (result === null) throw new Error("null response");
|
|
185
|
+
} catch {
|
|
186
|
+
// Hub unreachable — try to re-register
|
|
187
|
+
log("Hub heartbeat failed — attempting re-registration...");
|
|
188
|
+
hubClient.call_raw("register_adapter", {
|
|
189
|
+
name: "{{ADAPTER_NAME}}",
|
|
190
|
+
socketPath: ADAPTER_SOCKET_PATH,
|
|
191
|
+
}).then(() => {
|
|
192
|
+
log("Re-registered with AIBroker hub daemon");
|
|
193
|
+
}).catch((err) => {
|
|
194
|
+
log(`Hub re-registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}, HUB_HEARTBEAT_INTERVAL);
|
|
174
198
|
|
|
175
199
|
// Keep process alive
|
|
176
200
|
await new Promise(() => {});
|
|
@@ -68,9 +68,21 @@ export function startIpcServer(triggerLogin: () => Promise<string>): IpcServer {
|
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
case "voice": {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
// Hub sends pre-encoded audio as base64 buffer, or audioPath as fallback
|
|
72
|
+
const voiceB64 = typeof payload.buffer === "string" ? payload.buffer : "";
|
|
73
|
+
if (voiceB64) {
|
|
74
|
+
// Pre-encoded audio from hub — write to temp file and send
|
|
75
|
+
const { writeFileSync } = await import("node:fs");
|
|
76
|
+
const { join } = await import("node:path");
|
|
77
|
+
const { tmpdir } = await import("node:os");
|
|
78
|
+
const tmpPath = join(tmpdir(), `{{ADAPTER_NAME}}-voice-${Date.now()}.ogg`);
|
|
79
|
+
writeFileSync(tmpPath, Buffer.from(voiceB64, "base64"));
|
|
80
|
+
await sendVoice(tmpPath, recipient);
|
|
81
|
+
} else {
|
|
82
|
+
const audioPath = typeof payload.audioPath === "string" ? payload.audioPath : null;
|
|
83
|
+
if (!audioPath) return { ok: false, error: "deliver(voice): buffer or audioPath is required" };
|
|
84
|
+
await sendVoice(audioPath, recipient);
|
|
85
|
+
}
|
|
74
86
|
break;
|
|
75
87
|
}
|
|
76
88
|
case "file": {
|
|
@@ -81,6 +93,18 @@ export function startIpcServer(triggerLogin: () => Promise<string>): IpcServer {
|
|
|
81
93
|
await sendFile(filePath, caption, mimetype, recipient);
|
|
82
94
|
break;
|
|
83
95
|
}
|
|
96
|
+
case "image": {
|
|
97
|
+
// Image delivery — payload contains base64-encoded buffer and optional caption
|
|
98
|
+
const buffer = typeof payload.buffer === "string"
|
|
99
|
+
? Buffer.from(payload.buffer, "base64")
|
|
100
|
+
: null;
|
|
101
|
+
if (!buffer) return { ok: false, error: "deliver(image): buffer is required" };
|
|
102
|
+
const imgCaption = typeof payload.text === "string" ? payload.text : undefined;
|
|
103
|
+
// TODO: implement image sending via your SDK. For now, fall back to caption text.
|
|
104
|
+
if (imgCaption) await sendText(imgCaption, recipient);
|
|
105
|
+
log(`[{{ADAPTER_NAME}}] deliver(image): ${buffer.length} bytes — implement image sending`);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
84
108
|
case "command": {
|
|
85
109
|
// Commands from the hub (e.g. routed from another adapter) — deliver as text
|
|
86
110
|
const text = typeof payload.text === "string" ? payload.text : String(payload.text ?? "");
|