fix: route agent CLI via gateway
This commit is contained in:
194
src/commands/agent-via-gateway.ts
Normal file
194
src/commands/agent-via-gateway.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import type { CliDeps } from "../cli/deps.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
resolveSessionKey,
|
||||
resolveStorePath,
|
||||
} from "../config/sessions.js";
|
||||
import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { agentCommand } from "./agent.js";
|
||||
|
||||
type AgentGatewayResult = {
|
||||
payloads?: Array<{
|
||||
text?: string;
|
||||
mediaUrl?: string | null;
|
||||
mediaUrls?: string[];
|
||||
}>;
|
||||
meta?: unknown;
|
||||
};
|
||||
|
||||
type GatewayAgentResponse = {
|
||||
runId?: string;
|
||||
status?: string;
|
||||
summary?: string;
|
||||
result?: AgentGatewayResult;
|
||||
};
|
||||
|
||||
export type AgentCliOpts = {
|
||||
message: string;
|
||||
to?: string;
|
||||
sessionId?: string;
|
||||
thinking?: string;
|
||||
verbose?: string;
|
||||
json?: boolean;
|
||||
timeout?: string;
|
||||
deliver?: boolean;
|
||||
provider?: string;
|
||||
bestEffortDeliver?: boolean;
|
||||
lane?: string;
|
||||
runId?: string;
|
||||
extraSystemPrompt?: string;
|
||||
local?: boolean;
|
||||
};
|
||||
|
||||
function resolveGatewaySessionKey(opts: {
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
to?: string;
|
||||
sessionId?: string;
|
||||
}): string | undefined {
|
||||
const sessionCfg = opts.cfg.session;
|
||||
const scope = sessionCfg?.scope ?? "per-sender";
|
||||
const mainKey = sessionCfg?.mainKey ?? "main";
|
||||
const storePath = resolveStorePath(sessionCfg?.store);
|
||||
const store = loadSessionStore(storePath);
|
||||
|
||||
const ctx = opts.to?.trim() ? ({ From: opts.to } as { From: string }) : null;
|
||||
let sessionKey: string | undefined = ctx
|
||||
? resolveSessionKey(scope, ctx, mainKey)
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
opts.sessionId &&
|
||||
(!sessionKey || store[sessionKey]?.sessionId !== opts.sessionId)
|
||||
) {
|
||||
const foundKey = Object.keys(store).find(
|
||||
(key) => store[key]?.sessionId === opts.sessionId,
|
||||
);
|
||||
if (foundKey) sessionKey = foundKey;
|
||||
}
|
||||
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
function parseTimeoutSeconds(opts: {
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
timeout?: string;
|
||||
}) {
|
||||
const raw =
|
||||
opts.timeout !== undefined
|
||||
? Number.parseInt(String(opts.timeout), 10)
|
||||
: (opts.cfg.agent?.timeoutSeconds ?? 600);
|
||||
if (Number.isNaN(raw) || raw <= 0) {
|
||||
throw new Error("--timeout must be a positive integer (seconds)");
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
function normalizeProvider(raw?: string): string | undefined {
|
||||
const normalized = raw?.trim().toLowerCase();
|
||||
if (!normalized) return undefined;
|
||||
return normalized === "imsg" ? "imessage" : normalized;
|
||||
}
|
||||
|
||||
function formatPayloadForLog(payload: {
|
||||
text?: string;
|
||||
mediaUrls?: string[];
|
||||
mediaUrl?: string | null;
|
||||
}) {
|
||||
const lines: string[] = [];
|
||||
if (payload.text) lines.push(payload.text.trimEnd());
|
||||
const mediaUrl =
|
||||
typeof payload.mediaUrl === "string" && payload.mediaUrl.trim()
|
||||
? payload.mediaUrl.trim()
|
||||
: undefined;
|
||||
const media = payload.mediaUrls ?? (mediaUrl ? [mediaUrl] : []);
|
||||
for (const url of media) lines.push(`MEDIA:${url}`);
|
||||
return lines.join("\n").trimEnd();
|
||||
}
|
||||
|
||||
export async function agentViaGatewayCommand(
|
||||
opts: AgentCliOpts,
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const body = (opts.message ?? "").trim();
|
||||
if (!body) throw new Error("Message (--message) is required");
|
||||
if (!opts.to && !opts.sessionId) {
|
||||
throw new Error("Pass --to <E.164> or --session-id to choose a session");
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const timeoutSeconds = parseTimeoutSeconds({ cfg, timeout: opts.timeout });
|
||||
const gatewayTimeoutMs = Math.max(10_000, (timeoutSeconds + 30) * 1000);
|
||||
|
||||
const sessionKey = resolveGatewaySessionKey({
|
||||
cfg,
|
||||
to: opts.to,
|
||||
sessionId: opts.sessionId,
|
||||
});
|
||||
|
||||
const channel = normalizeProvider(opts.provider) ?? "whatsapp";
|
||||
const idempotencyKey = opts.runId?.trim() || randomIdempotencyKey();
|
||||
|
||||
const response = await callGateway<GatewayAgentResponse>({
|
||||
method: "agent",
|
||||
params: {
|
||||
message: body,
|
||||
to: opts.to,
|
||||
sessionId: opts.sessionId,
|
||||
sessionKey,
|
||||
thinking: opts.thinking,
|
||||
deliver: Boolean(opts.deliver),
|
||||
channel,
|
||||
timeout: timeoutSeconds,
|
||||
lane: opts.lane,
|
||||
extraSystemPrompt: opts.extraSystemPrompt,
|
||||
idempotencyKey,
|
||||
},
|
||||
expectFinal: true,
|
||||
timeoutMs: gatewayTimeoutMs,
|
||||
clientName: "cli",
|
||||
mode: "cli",
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(JSON.stringify(response, null, 2));
|
||||
return response;
|
||||
}
|
||||
|
||||
const result = response?.result;
|
||||
const payloads = result?.payloads ?? [];
|
||||
|
||||
if (payloads.length === 0) {
|
||||
runtime.log(
|
||||
response?.summary ? String(response.summary) : "No reply from agent.",
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
for (const payload of payloads) {
|
||||
const out = formatPayloadForLog(payload);
|
||||
if (out) runtime.log(out);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function agentCliCommand(
|
||||
opts: AgentCliOpts,
|
||||
runtime: RuntimeEnv,
|
||||
deps?: CliDeps,
|
||||
) {
|
||||
if (opts.local === true) {
|
||||
return await agentCommand(opts, runtime, deps);
|
||||
}
|
||||
|
||||
try {
|
||||
return await agentViaGatewayCommand(opts, runtime);
|
||||
} catch (err) {
|
||||
runtime.error?.(
|
||||
`Gateway agent failed; falling back to embedded: ${String(err)}`,
|
||||
);
|
||||
return await agentCommand(opts, runtime, deps);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user