Skip to content

Commit 7bfd509

Browse files
feat(cli): add --async mode and increase default timeout to 300s
- --async: send message and exit immediately, don't wait for response - --timeout now in seconds (default 300s instead of 120s) - Async mode prints messageId for later retrieval via `botschat messages`
1 parent afb5a9b commit 7bfd509

2 files changed

Lines changed: 106 additions & 11 deletions

File tree

packages/cli/src/commands/chat.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ export const chatCmd = new Command("chat")
1515
.option("-c, --channel <channelId>", "Channel ID")
1616
.option("-a, --agent <agentId>", "Agent ID")
1717
.option("--no-stream", "Wait for full response instead of streaming")
18+
.option("--async", "Send message and exit immediately without waiting for response")
1819
.option("--pipe", "Read message from stdin")
19-
.option("--timeout <ms>", "Timeout in ms for single-shot mode", "120000")
20+
.option("--timeout <seconds>", "Timeout in seconds for single-shot mode", "300")
2021
.action(async (message: string | undefined, opts) => {
2122
try {
2223
const cfg = loadConfig();
@@ -41,7 +42,6 @@ export const chatCmd = new Command("chat")
4142
let sessionId = opts.session || cfg.defaultSession;
4243

4344
if (!channelId || !sessionId) {
44-
// Auto-resolve from first channel / first session
4545
const { channels } = await channelsApi.list();
4646
if (channels.length === 0) {
4747
printError("No channels found. Create one first.");
@@ -55,7 +55,6 @@ export const chatCmd = new Command("chat")
5555
if (!sessionId) {
5656
const { sessions } = await sessionsApi.list(channelId);
5757
if (sessions.length === 0) {
58-
// Create a session
5958
const session = await sessionsApi.create(channelId);
6059
sessionId = session.sessionKey;
6160
} else {
@@ -70,15 +69,19 @@ export const chatCmd = new Command("chat")
7069
const wsHost = cfg.url.replace(/^https?:\/\//, "");
7170
const wsUrl = `${wsProtocol}://${wsHost}/api/ws/${cfg.userId}/${encodeURIComponent(sessionId!)}`;
7271

72+
const timeoutMs = parseFloat(opts.timeout) * 1000;
73+
7374
if (interactive) {
7475
await runInteractive(wsUrl, sessionId!, opts.agent, cfg.userId);
76+
} else if (opts.async) {
77+
await runAsync(wsUrl, sessionId!, message!, opts.agent);
7578
} else {
7679
await runSingleShot(
7780
wsUrl,
7881
sessionId!,
7982
message!,
8083
opts.agent,
81-
parseInt(opts.timeout),
84+
timeoutMs,
8285
opts.stream !== false,
8386
);
8487
}
@@ -88,12 +91,13 @@ export const chatCmd = new Command("chat")
8891
}
8992
});
9093

94+
/** Send message and wait for response. */
9195
async function runSingleShot(
9296
wsUrl: string,
9397
sessionKey: string,
9498
message: string,
9599
agentId?: string,
96-
timeout = 120000,
100+
timeout = 300000,
97101
stream = true,
98102
): Promise<void> {
99103
return new Promise((resolve, reject) => {
@@ -107,7 +111,6 @@ async function runSingleShot(
107111
noReconnect: true,
108112
onStatusChange: async (connected) => {
109113
if (connected) {
110-
// Send the message
111114
const msg: WSMessage = {
112115
type: "user.message",
113116
sessionKey,
@@ -156,7 +159,6 @@ async function runSingleShot(
156159
if (msg.type === "agent.text") {
157160
clearTimeout(timer);
158161
if (!streaming) {
159-
// Non-streaming full response
160162
const text = msg.text as string;
161163
if (isJsonMode()) {
162164
printJson(msg);
@@ -174,6 +176,59 @@ async function runSingleShot(
174176
});
175177
}
176178

179+
/** Send message and exit immediately — don't wait for response. */
180+
async function runAsync(
181+
wsUrl: string,
182+
sessionKey: string,
183+
message: string,
184+
agentId?: string,
185+
): Promise<void> {
186+
return new Promise((resolve, reject) => {
187+
const messageId = randomUUID();
188+
189+
const ws = new BotsChatWSClient({
190+
url: wsUrl,
191+
getToken,
192+
noReconnect: true,
193+
onStatusChange: async (connected) => {
194+
if (connected) {
195+
const msg: WSMessage = {
196+
type: "user.message",
197+
sessionKey,
198+
text: message,
199+
messageId,
200+
};
201+
if (agentId) msg.targetAgentId = agentId;
202+
await ws.send(msg);
203+
204+
if (isJsonMode()) {
205+
printJson({ sent: true, messageId, sessionKey });
206+
} else {
207+
console.log(`Message sent (id: ${messageId})`);
208+
}
209+
210+
// Brief delay to ensure message is flushed over WS
211+
setTimeout(() => {
212+
ws.disconnect();
213+
resolve();
214+
}, 500);
215+
}
216+
},
217+
onMessage: () => {
218+
// Ignore responses in async mode
219+
},
220+
});
221+
222+
ws.connect();
223+
224+
// Timeout for connection
225+
setTimeout(() => {
226+
ws.disconnect();
227+
reject(new Error("Timeout connecting to server"));
228+
}, 15000);
229+
});
230+
}
231+
177232
async function runInteractive(
178233
wsUrl: string,
179234
sessionKey: string,
@@ -252,7 +307,6 @@ async function runInteractive(
252307
if (agentId) msg.targetAgentId = agentId;
253308
await ws.send(msg);
254309

255-
// Don't prompt until we get a response
256310
const waitForResponse = () => {
257311
const origOnMsg = ws["opts"].onMessage;
258312
ws["opts"].onMessage = (m) => {

packages/plugin/bin/botschat-cli.mjs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ var BotsChatWSClient = class {
11111111
};
11121112

11131113
// src/commands/chat.ts
1114-
var chatCmd = new Command9("chat").description("Chat with an AI agent").argument("[message]", "Message to send (omit for interactive mode)").option("-i, --interactive", "Interactive REPL mode").option("-s, --session <sessionId>", "Session ID").option("-c, --channel <channelId>", "Channel ID").option("-a, --agent <agentId>", "Agent ID").option("--no-stream", "Wait for full response instead of streaming").option("--pipe", "Read message from stdin").option("--timeout <ms>", "Timeout in ms for single-shot mode", "120000").action(async (message, opts) => {
1114+
var chatCmd = new Command9("chat").description("Chat with an AI agent").argument("[message]", "Message to send (omit for interactive mode)").option("-i, --interactive", "Interactive REPL mode").option("-s, --session <sessionId>", "Session ID").option("-c, --channel <channelId>", "Channel ID").option("-a, --agent <agentId>", "Agent ID").option("--no-stream", "Wait for full response instead of streaming").option("--async", "Send message and exit immediately without waiting for response").option("--pipe", "Read message from stdin").option("--timeout <seconds>", "Timeout in seconds for single-shot mode", "300").action(async (message, opts) => {
11151115
try {
11161116
const cfg = loadConfig();
11171117
if (!cfg.userId || !cfg.token) {
@@ -1152,15 +1152,18 @@ var chatCmd = new Command9("chat").description("Chat with an AI agent").argument
11521152
const wsProtocol = cfg.url.startsWith("https") ? "wss" : "ws";
11531153
const wsHost = cfg.url.replace(/^https?:\/\//, "");
11541154
const wsUrl = `${wsProtocol}://${wsHost}/api/ws/${cfg.userId}/${encodeURIComponent(sessionId)}`;
1155+
const timeoutMs = parseFloat(opts.timeout) * 1e3;
11551156
if (interactive) {
11561157
await runInteractive(wsUrl, sessionId, opts.agent, cfg.userId);
1158+
} else if (opts.async) {
1159+
await runAsync(wsUrl, sessionId, message, opts.agent);
11571160
} else {
11581161
await runSingleShot(
11591162
wsUrl,
11601163
sessionId,
11611164
message,
11621165
opts.agent,
1163-
parseInt(opts.timeout),
1166+
timeoutMs,
11641167
opts.stream !== false
11651168
);
11661169
}
@@ -1169,7 +1172,7 @@ var chatCmd = new Command9("chat").description("Chat with an AI agent").argument
11691172
process.exit(1);
11701173
}
11711174
});
1172-
async function runSingleShot(wsUrl, sessionKey, message, agentId, timeout = 12e4, stream = true) {
1175+
async function runSingleShot(wsUrl, sessionKey, message, agentId, timeout = 3e5, stream = true) {
11731176
return new Promise((resolve, reject) => {
11741177
let fullText = "";
11751178
let streaming = false;
@@ -1240,6 +1243,44 @@ async function runSingleShot(wsUrl, sessionKey, message, agentId, timeout = 12e4
12401243
ws.connect();
12411244
});
12421245
}
1246+
async function runAsync(wsUrl, sessionKey, message, agentId) {
1247+
return new Promise((resolve, reject) => {
1248+
const messageId = randomUUID3();
1249+
const ws = new BotsChatWSClient({
1250+
url: wsUrl,
1251+
getToken,
1252+
noReconnect: true,
1253+
onStatusChange: async (connected) => {
1254+
if (connected) {
1255+
const msg = {
1256+
type: "user.message",
1257+
sessionKey,
1258+
text: message,
1259+
messageId
1260+
};
1261+
if (agentId) msg.targetAgentId = agentId;
1262+
await ws.send(msg);
1263+
if (isJsonMode()) {
1264+
printJson({ sent: true, messageId, sessionKey });
1265+
} else {
1266+
console.log(`Message sent (id: ${messageId})`);
1267+
}
1268+
setTimeout(() => {
1269+
ws.disconnect();
1270+
resolve();
1271+
}, 500);
1272+
}
1273+
},
1274+
onMessage: () => {
1275+
}
1276+
});
1277+
ws.connect();
1278+
setTimeout(() => {
1279+
ws.disconnect();
1280+
reject(new Error("Timeout connecting to server"));
1281+
}, 15e3);
1282+
});
1283+
}
12431284
async function runInteractive(wsUrl, sessionKey, agentId, userId) {
12441285
return new Promise((resolve) => {
12451286
let streaming = false;

0 commit comments

Comments
 (0)