158 lines
5.6 KiB
TypeScript
158 lines
5.6 KiB
TypeScript
#!/usr/bin/env bun
|
||
/**
|
||
* hwlab-code-agent CLI — HWLAB Code Agent 客户端
|
||
* Skill(cli-spec)
|
||
*
|
||
* 通过 hwlab-code-agent 直接调 HWLAB v0.2 cloud-api 管理 agent turn:
|
||
* spawn - 创建 session + 提交 prompt,立即返回 traceId
|
||
* poll - 轮询等待 turn 完成
|
||
* result - 查询 turn 结果
|
||
* trace - 查询 turn trace
|
||
*/
|
||
|
||
import path from "node:path";
|
||
import { spawn, poll, getResult, getTrace } from "./src/client.ts";
|
||
import { queryKafkaEventStream } from "../../../internal/cloud/kafka-event-bridge.ts";
|
||
|
||
const argv = process.argv.slice(2);
|
||
const cmd = argv[0];
|
||
|
||
function parseArgs(from: number): Record<string, string | undefined> {
|
||
const args: Record<string, string | undefined> = {};
|
||
for (let i = from; i < argv.length; i++) {
|
||
if (argv[i].startsWith("--")) {
|
||
const key = argv[i].slice(2);
|
||
const val = (i + 1 < argv.length && !argv[i + 1].startsWith("--")) ? argv[i + 1] : "true";
|
||
args[key] = val;
|
||
if (val !== "true") i++;
|
||
}
|
||
}
|
||
return args;
|
||
}
|
||
|
||
async function main() {
|
||
switch (cmd) {
|
||
case "spawn": {
|
||
const args = parseArgs(1);
|
||
await spawn(args);
|
||
break;
|
||
}
|
||
case "poll": {
|
||
const traceId = argv[1];
|
||
if (!traceId) {
|
||
process.stderr.write(JSON.stringify({ ok: false, error: { code: "usage", message: "poll <traceId> [--timeout N]" } }) + "\n");
|
||
process.exit(1);
|
||
}
|
||
await poll(traceId, parseArgs(2));
|
||
break;
|
||
}
|
||
case "result": {
|
||
const traceId = argv[1];
|
||
if (!traceId) {
|
||
process.stderr.write(JSON.stringify({ ok: false, error: { code: "usage", message: "result <traceId>" } }) + "\n");
|
||
process.exit(1);
|
||
}
|
||
await getResult(traceId);
|
||
break;
|
||
}
|
||
case "trace": {
|
||
const traceId = argv[1];
|
||
if (!traceId) {
|
||
process.stderr.write(JSON.stringify({ ok: false, error: { code: "usage", message: "trace <traceId> [--full]" } }) + "\n");
|
||
process.exit(1);
|
||
}
|
||
await getTrace(traceId, parseArgs(2));
|
||
break;
|
||
}
|
||
case "kafka": {
|
||
const subcommand = argv[1] || "tail";
|
||
if (subcommand !== "tail" && subcommand !== "query") {
|
||
process.stderr.write(JSON.stringify({ ok: false, error: { code: "usage", message: "kafka tail [--stream hwlab|agentrun] [--trace-id TRACE_ID] [--session-id SESSION_ID] [--run-id RUN_ID] [--command-id COMMAND_ID]" } }) + "\n");
|
||
process.exit(1);
|
||
}
|
||
await kafkaTail(parseArgs(2));
|
||
break;
|
||
}
|
||
default:
|
||
process.stderr.write(JSON.stringify({
|
||
ok: false,
|
||
usage: {
|
||
spawn: "HWLAB_CODE_AGENT_PROVIDER_PROFILE=PROFILE bun scripts/hwlab-code-agent-cli.ts spawn --message '...' [--profile PROFILE]",
|
||
poll: "bun scripts/hwlab-code-agent-cli.ts poll <traceId> [--timeout 600000]",
|
||
result: "bun scripts/hwlab-code-agent-cli.ts result <traceId>",
|
||
trace: "bun scripts/hwlab-code-agent-cli.ts trace <traceId> [--full]",
|
||
kafka: "bun tools/hwlab-code-agent kafka tail --stream hwlab --trace-id trc_... --limit 20"
|
||
}
|
||
}, null, 2) + "\n");
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
async function kafkaTail(args: Record<string, string | undefined>) {
|
||
const result = await queryKafkaEventStream({
|
||
env: process.env,
|
||
stream: args.stream || "hwlab",
|
||
topic: args.topic || null,
|
||
traceId: args["trace-id"] || args.traceId || null,
|
||
sessionId: args["session-id"] || args.sessionId || null,
|
||
runId: args["run-id"] || args.runId || null,
|
||
commandId: args["command-id"] || args.commandId || null,
|
||
limit: boundedInteger(args.limit, 20, 1, 500),
|
||
timeoutMs: boundedInteger(args["timeout-ms"] || args.timeoutMs, 5000, 250, 60000),
|
||
fromBeginning: args["from-end"] === "true" || args.fromEnd === "true" ? false : true
|
||
});
|
||
process.stdout.write(JSON.stringify({
|
||
ok: true,
|
||
action: "hwlab-code-agent.kafka.tail",
|
||
stream: result.stream,
|
||
topic: result.topic,
|
||
count: result.count,
|
||
limit: result.limit,
|
||
timeoutMs: result.timeoutMs,
|
||
filters: result.filters,
|
||
rows: result.events.map(kafkaEventRow),
|
||
events: args.full === "true" ? result.events : undefined,
|
||
valuesPrinted: false
|
||
}, null, 2) + "\n");
|
||
}
|
||
|
||
function kafkaEventRow(record: any) {
|
||
const value = record?.value && typeof record.value === "object" ? record.value : {};
|
||
const event = value.event && typeof value.event === "object" ? value.event : {};
|
||
const context = value.context && typeof value.context === "object" ? value.context : {};
|
||
return clean({
|
||
topic: record.topic,
|
||
partition: record.partition,
|
||
offset: record.offset,
|
||
key: record.key,
|
||
producedAt: text(value.producedAt),
|
||
eventType: text(value.eventType ?? event.eventType),
|
||
traceId: text(value.traceId ?? event.traceId),
|
||
sessionId: text(value.sessionId ?? event.sessionId),
|
||
runId: text(context.runId ?? event.runId ?? value.runId),
|
||
commandId: text(context.commandId ?? event.commandId ?? value.commandId),
|
||
label: text(event.label),
|
||
status: text(event.status),
|
||
sourceSeq: context.sourceSeq ?? event.sourceSeq
|
||
});
|
||
}
|
||
|
||
function boundedInteger(value: unknown, fallback: number, min: number, max: number) {
|
||
const parsed = Number.parseInt(String(value ?? ""), 10);
|
||
const selected = Number.isFinite(parsed) ? parsed : fallback;
|
||
return Math.min(max, Math.max(min, selected));
|
||
}
|
||
|
||
function text(value: unknown) {
|
||
return String(value ?? "").trim();
|
||
}
|
||
|
||
function clean(value: Record<string, unknown>) {
|
||
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== "" && item !== null));
|
||
}
|
||
|
||
main().catch((e) => {
|
||
process.stderr.write(JSON.stringify({ ok: false, error: { code: "unhandled", message: e.message } }) + "\n");
|
||
process.exit(1);
|
||
});
|