@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
245 lines (243 loc) • 8.58 kB
JavaScript
// @bun
import {
parseFilterTokens
} from "./chunk-wk6ynbab.js";
import {
SqliteTraceStore
} from "./chunk-2re833ev.js";
import {
ensureJsonOnlyFormat
} from "./chunk-7sfagm12.js";
import {
findAgentRootOrFail
} from "./chunk-kk3h6qaj.js";
import {
createCliLogger,
source_default
} from "./chunk-gzwt1qdr.js";
import"./chunk-nxy2ya5r.js";
import"./chunk-wzj4dc7n.js";
import {
AgentProject
} from "./chunk-p0hjqn4r.js";
import"./chunk-np5wcwfv.js";
import"./chunk-dq2xpa24.js";
import"./chunk-6w0knnta.js";
import"./chunk-40x04ckt.js";
import"./chunk-t76d8fxx.js";
import"./chunk-nh2akp42.js";
import"./chunk-0fdvzjbh.js";
import"./chunk-2a5b6azq.js";
import"./chunk-vay209b5.js";
import"./chunk-3xrpxgq4.js";
import"./chunk-rfm3jr1m.js";
import"./chunk-w346ejn9.js";
import"./chunk-knvm2anf.js";
import"./chunk-65h5trb5.js";
import"./chunk-s2akeqpw.js";
import"./chunk-6771vrjp.js";
import"./chunk-g8mm42v1.js";
import"./chunk-50hzjdck.js";
import"./chunk-nn2jb0x0.js";
import"./chunk-v8xvth6j.js";
import"./chunk-kkk13rcb.js";
import"./chunk-ytpp1kam.js";
import"./chunk-na956zz3.js";
import"./chunk-f4bw8q7c.js";
import"./chunk-0v8vgrns.js";
import"./chunk-54qt5g7m.js";
import"./chunk-dhs2bg35.js";
// src/commands/adk-conversations.ts
function formatDuration(ms) {
if (ms == null)
return "---";
if (ms < 1)
return "<1ms";
if (ms < 1000)
return `${Math.round(ms)}ms`;
if (ms < 60000)
return `${(ms / 1000).toFixed(1)}s`;
return `${(ms / 60000).toFixed(1)}m`;
}
function formatTimestamp(ts) {
return new Date(ts).toLocaleString("en-US", { hour12: false });
}
function statusIcon(hasErrors) {
return hasErrors ? source_default.red("\u2717") : source_default.green("\u2713");
}
function buildConversationTurns(allSpans, options) {
const byTrace = new Map;
for (const span of allSpans) {
const group = byTrace.get(span.id.trace) ?? [];
group.push(span);
byTrace.set(span.id.trace, group);
}
const turns = [];
for (const [traceId, spans] of byTrace) {
const roots = spans.filter((s) => s.id.parent === null);
const sorted = (roots.length > 0 ? roots : spans).sort((a, b) => a.timing.startedAt - b.timing.startedAt);
const root = sorted[0];
const hasError = spans.some((s) => s.status === "error");
const hasRunning = spans.some((s) => s.status === "running");
const status = hasError ? "error" : hasRunning ? "running" : "ok";
let trigger = null;
for (const span of spans) {
if (span.name === "request.incoming" || span.name === "handler.conversation" || span.name === "handler.trigger") {
trigger = span.label && span.label !== span.name ? `${span.name} (${span.label})` : span.name;
break;
}
}
const toolCalls = [];
for (const span of spans) {
if (span.name === "autonomous.tool" || span.name === "handler.action") {
toolCalls.push({
name: span.label && span.label !== span.name ? span.label : span.name,
status: span.status,
duration: span.timing.duration ?? null,
error: span.error
});
}
}
const errors = [];
for (const span of spans) {
if (span.status === "error" && span.error) {
errors.push(`[${span.name}] ${span.error}`);
}
}
let llmContent;
if (options.includeLlm) {
llmContent = [];
for (const span of spans) {
if (span.name === "cognitive.request" || span.name === "autonomous.iteration") {
llmContent.push({ name: span.name, data: span.data });
}
}
}
turns.push({
traceId,
startedAt: root.timing.startedAt,
duration: root.timing.duration ?? null,
status,
trigger,
toolCalls,
errors,
llmContent
});
}
return turns.sort((a, b) => a.startedAt - b.startedAt);
}
function formatConversationDetail(conversationId, turns) {
const lines = [];
lines.push(`Conversation ${source_default.bold(conversationId)} (${turns.length} turn${turns.length === 1 ? "" : "s"})`);
lines.push("");
for (let i = 0;i < turns.length; i++) {
const turn = turns[i];
const time = formatTimestamp(turn.startedAt);
const dur = formatDuration(turn.duration);
const icon = turn.status === "error" ? source_default.red("\u2717") : turn.status === "running" ? source_default.yellow("\u25CC") : source_default.green("\u2713");
const traceShort = source_default.dim(turn.traceId.slice(0, 8));
lines.push(` ${i + 1}. [${time}] ${icon} ${dur} ${traceShort} ${turn.trigger ?? "unknown"}`);
for (const tool of turn.toolCalls) {
const toolIcon = tool.status === "error" ? source_default.red("\u2717") : source_default.green("\u2713");
lines.push(` ${toolIcon} ${tool.name} ${source_default.dim(formatDuration(tool.duration))}`);
if (tool.error) {
lines.push(` ${source_default.red(tool.error)}`);
}
}
for (const err of turn.errors) {
if (!turn.toolCalls.some((t) => t.error && err.endsWith(`] ${t.error}`))) {
lines.push(` ${source_default.red(err)}`);
}
}
if (turn.llmContent?.length) {
lines.push(` ${source_default.dim("\u2500\u2500\u2500 LLM \u2500\u2500\u2500")}`);
for (const entry of turn.llmContent) {
const dataStr = typeof entry.data === "string" ? entry.data : JSON.stringify(entry.data, null, 2);
lines.push(` ${source_default.magenta(entry.name)}`);
for (const line of dataStr.split(`
`)) {
lines.push(` ${source_default.dim(line)}`);
}
}
}
}
return lines.join(`
`);
}
async function adkConversationsList(tokens, options) {
const logger = createCliLogger({ format: options?.format });
ensureJsonOnlyFormat(options.format);
const { options: filters, errors } = parseFilterTokens(tokens);
if (errors.length > 0) {
for (const err of errors) {
logger.error(`${err.message}${err.suggestion ? ` \u2014 ${err.suggestion}` : ""}`);
}
process.exit(1);
}
const agentRoot = await findAgentRootOrFail(process.cwd());
const project = await AgentProject.load(agentRoot);
const botId = project.agentInfo?.devId ?? project.agentInfo?.botId;
const store = new SqliteTraceStore(agentRoot);
try {
const conversations = store.listConversations({
limit: filters.limit ?? 20,
since: filters.since,
botId
});
if (conversations.length === 0) {
logger.info("No conversations found.").result({ conversations: [] });
return;
}
for (const conv of conversations) {
const lastTime = formatTimestamp(conv.lastSeen);
const icon = statusIcon(conv.hasErrors);
const idDisplay = conv.conversationId;
const channel = conv.integration ? `${conv.integration}${conv.channel ? `/${conv.channel}` : ""}` : "";
logger.info(`${icon} ${source_default.bold(idDisplay)} ${source_default.dim(lastTime)} ${conv.traceCount} turn${conv.traceCount === 1 ? "" : "s"}${channel ? ` ${source_default.cyan(channel)}` : ""}`).result({
conversationId: conv.conversationId,
firstSeen: new Date(conv.firstSeen).toISOString(),
lastSeen: new Date(conv.lastSeen).toISOString(),
traceCount: conv.traceCount,
integration: conv.integration,
channel: conv.channel,
hasErrors: conv.hasErrors
});
}
} finally {
store.close();
}
}
async function adkConversationsShow(conversationId, options) {
const logger = createCliLogger({ format: options?.format });
ensureJsonOnlyFormat(options.format);
const agentRoot = await findAgentRootOrFail(process.cwd());
const store = new SqliteTraceStore(agentRoot);
try {
const spans = store.getTraceByAttribute("conversationId", conversationId);
if (spans.length === 0) {
logger.info(`No traces found for conversation "${conversationId}".`).result({ conversationId, turns: [] });
return;
}
const turns = buildConversationTurns(spans, { includeLlm: options.includeLlm });
logger.info(formatConversationDetail(conversationId, turns)).result({
conversationId,
turnCount: turns.length,
turns: turns.map((t) => ({
traceId: t.traceId,
startedAt: new Date(t.startedAt).toISOString(),
duration: t.duration,
status: t.status,
trigger: t.trigger,
toolCalls: t.toolCalls,
errors: t.errors,
...t.llmContent ? { llmContent: t.llmContent } : {}
}))
});
} finally {
store.close();
}
}
export {
adkConversationsShow,
adkConversationsList
};