@acnlabs/acn-cli 1.0.1 → 1.0.3
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/dist/index.js +195 -65
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@acnlabs/acn-cli",
|
|
34
|
-
version: "1.0.
|
|
34
|
+
version: "1.0.3",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -429,27 +429,129 @@ function joinCommand() {
|
|
|
429
429
|
|
|
430
430
|
// src/commands/heartbeat.ts
|
|
431
431
|
var import_commander3 = require("commander");
|
|
432
|
+
|
|
433
|
+
// src/commands/model-heartbeat.ts
|
|
434
|
+
function resolvePreferredModel(opts) {
|
|
435
|
+
const env = opts?.env ?? process.env;
|
|
436
|
+
const fromFlag = opts?.model?.trim();
|
|
437
|
+
if (fromFlag) return fromFlag.slice(0, 200);
|
|
438
|
+
const fromEnv = env.ACN_PREFERRED_MODEL?.trim();
|
|
439
|
+
if (fromEnv) return fromEnv.slice(0, 200);
|
|
440
|
+
return void 0;
|
|
441
|
+
}
|
|
442
|
+
function resolveSupportedModels(opts) {
|
|
443
|
+
if (opts?.clear) return [];
|
|
444
|
+
const env = opts?.env ?? process.env;
|
|
445
|
+
const raw = (opts?.models?.trim() || env.ACN_SUPPORTED_MODELS?.trim() || "").trim();
|
|
446
|
+
if (!raw) return void 0;
|
|
447
|
+
const out = [];
|
|
448
|
+
const seen = /* @__PURE__ */ new Set();
|
|
449
|
+
for (const part of raw.split(/[,;\s]+/)) {
|
|
450
|
+
const mid = part.trim().slice(0, 200);
|
|
451
|
+
if (!mid) continue;
|
|
452
|
+
const key = mid.toLowerCase();
|
|
453
|
+
if (seen.has(key)) continue;
|
|
454
|
+
seen.add(key);
|
|
455
|
+
out.push(mid);
|
|
456
|
+
if (out.length >= 50) break;
|
|
457
|
+
}
|
|
458
|
+
return out.length ? out : void 0;
|
|
459
|
+
}
|
|
460
|
+
async function postAgentHeartbeat(opts) {
|
|
461
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
462
|
+
const origin = opts.baseUrl.replace(/\/+$/, "");
|
|
463
|
+
const url = `${origin}/api/v1/agents/${opts.agentId}/heartbeat`;
|
|
464
|
+
const headers = {
|
|
465
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
466
|
+
"Content-Type": "application/json"
|
|
467
|
+
};
|
|
468
|
+
const payload = {};
|
|
469
|
+
if (opts.preferredModel && opts.preferredModel.trim()) {
|
|
470
|
+
payload.preferred_model = opts.preferredModel.trim().slice(0, 200);
|
|
471
|
+
}
|
|
472
|
+
if (opts.supportedModels != null) {
|
|
473
|
+
payload.supported_models = opts.supportedModels;
|
|
474
|
+
}
|
|
475
|
+
const body = Object.keys(payload).length ? JSON.stringify(payload) : void 0;
|
|
476
|
+
try {
|
|
477
|
+
const res = await fetchFn(url, { method: "POST", headers, body });
|
|
478
|
+
if (!res.ok) {
|
|
479
|
+
return { ok: false, reason: `http_${res.status}` };
|
|
480
|
+
}
|
|
481
|
+
let preferred;
|
|
482
|
+
let supported;
|
|
483
|
+
try {
|
|
484
|
+
const json = await res.json();
|
|
485
|
+
preferred = json.preferred_model;
|
|
486
|
+
supported = json.supported_models;
|
|
487
|
+
} catch {
|
|
488
|
+
preferred = opts.preferredModel ?? null;
|
|
489
|
+
supported = opts.supportedModels ?? null;
|
|
490
|
+
}
|
|
491
|
+
return { ok: true, preferred_model: preferred, supported_models: supported };
|
|
492
|
+
} catch (err) {
|
|
493
|
+
return {
|
|
494
|
+
ok: false,
|
|
495
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function formatModelHeartbeatLog(opts) {
|
|
500
|
+
const preferred = (opts.preferred ?? "").trim();
|
|
501
|
+
const supported = opts.supported ?? [];
|
|
502
|
+
const bits = [
|
|
503
|
+
preferred ? `preferred_model=${preferred}` : "",
|
|
504
|
+
supported.length ? `supported_models=${supported.join(",")}` : ""
|
|
505
|
+
].filter(Boolean);
|
|
506
|
+
return bits.length ? ` ${bits.join(" ")}` : "";
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/commands/heartbeat.ts
|
|
432
510
|
function heartbeatCommand() {
|
|
433
511
|
return new import_commander3.Command("heartbeat").description("Send a heartbeat to keep this agent online").option("-i, --agent-id <id>", "Agent ID (defaults to value in ~/.acn/config.json)").option(
|
|
434
512
|
"-m, --model <modelId>",
|
|
435
513
|
"Declare runtime model (Host Catalog id) for Host Pricing prefill \u2014 self-reported (env: ACN_PREFERRED_MODEL)"
|
|
436
|
-
).
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
514
|
+
).option(
|
|
515
|
+
"--supported-models <ids>",
|
|
516
|
+
"Comma-separated models this runtime can run (Interfaze composer; env: ACN_SUPPORTED_MODELS)"
|
|
517
|
+
).option(
|
|
518
|
+
"--clear-supported-models",
|
|
519
|
+
"Clear metadata.supported_models on the server (sends empty list)"
|
|
520
|
+
).action(
|
|
521
|
+
async (opts) => {
|
|
522
|
+
const config = loadConfig();
|
|
523
|
+
const agentId = opts.agentId ?? config.agent_id;
|
|
524
|
+
if (!agentId) {
|
|
525
|
+
console.error("No agent ID found. Run `acn join` first or pass --agent-id.");
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
if (opts.clearSupportedModels && opts.supportedModels?.trim()) {
|
|
530
|
+
console.error(
|
|
531
|
+
"Use either --supported-models or --clear-supported-models, not both."
|
|
532
|
+
);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
const preferred = resolvePreferredModel({ model: opts.model });
|
|
536
|
+
const supported = resolveSupportedModels({
|
|
537
|
+
models: opts.supportedModels,
|
|
538
|
+
clear: !!opts.clearSupportedModels
|
|
539
|
+
});
|
|
540
|
+
const body = {};
|
|
541
|
+
if (preferred) body.preferred_model = preferred;
|
|
542
|
+
if (supported !== void 0) body.supported_models = supported;
|
|
543
|
+
const res = await acnPost(`/agents/${agentId}/heartbeat`, Object.keys(body).length ? body : void 0);
|
|
544
|
+
const notes = [
|
|
545
|
+
res.preferred_model ? `preferred_model=${res.preferred_model}` : "",
|
|
546
|
+
res.supported_models != null ? `supported_models=${res.supported_models.join(",") || "(cleared)"}` : ""
|
|
547
|
+
].filter(Boolean);
|
|
548
|
+
const modelNote = notes.length ? ` (${notes.join(" ")})` : "";
|
|
549
|
+
output(res, `Heartbeat sent for agent ${agentId}${modelNote}`);
|
|
550
|
+
} catch (err) {
|
|
551
|
+
handleError(err);
|
|
552
|
+
}
|
|
451
553
|
}
|
|
452
|
-
|
|
554
|
+
);
|
|
453
555
|
}
|
|
454
556
|
|
|
455
557
|
// src/commands/rotate-key.ts
|
|
@@ -1480,12 +1582,23 @@ function extractChatEnvelope(message) {
|
|
|
1480
1582
|
if (!chatId || !replyPath) return null;
|
|
1481
1583
|
if (replyChannel !== CHAT_REPLY_CHANNEL) return null;
|
|
1482
1584
|
if (!isAllowedChatReplyPath(chatId, replyPath)) return null;
|
|
1585
|
+
const requested = asNonEmptyString(ap.requested_model);
|
|
1586
|
+
let maxOut = null;
|
|
1587
|
+
const rawMax = ap.max_output_tokens;
|
|
1588
|
+
if (typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax > 0) {
|
|
1589
|
+
maxOut = Math.floor(rawMax);
|
|
1590
|
+
} else if (typeof rawMax === "string" && rawMax.trim()) {
|
|
1591
|
+
const n = Number.parseInt(rawMax.trim(), 10);
|
|
1592
|
+
if (Number.isFinite(n) && n > 0) maxOut = n;
|
|
1593
|
+
}
|
|
1483
1594
|
return {
|
|
1484
1595
|
chat_id: chatId,
|
|
1485
1596
|
reply_path: replyPath,
|
|
1486
1597
|
reply_channel: CHAT_REPLY_CHANNEL,
|
|
1487
1598
|
gateway_message_id: asNonEmptyString(ap.message_id) ?? asNonEmptyString(ap.messageId),
|
|
1488
|
-
user_text: extractUserText(message)
|
|
1599
|
+
user_text: extractUserText(message),
|
|
1600
|
+
requested_model: requested ? requested.slice(0, 200) : null,
|
|
1601
|
+
max_output_tokens: maxOut
|
|
1489
1602
|
};
|
|
1490
1603
|
}
|
|
1491
1604
|
function normalizeEvent(body, opts = {}) {
|
|
@@ -1628,8 +1741,8 @@ function extractUsage(payload) {
|
|
|
1628
1741
|
const rec = asRecord2(payload);
|
|
1629
1742
|
if (!rec) return void 0;
|
|
1630
1743
|
const usageRec = asRecord2(rec.usage) ?? rec;
|
|
1631
|
-
const input = asNonNegInt(usageRec.input_tokens) ?? asNonNegInt(usageRec.prompt_tokens);
|
|
1632
|
-
const output2 = asNonNegInt(usageRec.output_tokens) ?? asNonNegInt(usageRec.completion_tokens);
|
|
1744
|
+
const input = asNonNegInt(usageRec.input_tokens) ?? asNonNegInt(usageRec.prompt_tokens) ?? asNonNegInt(usageRec.input);
|
|
1745
|
+
const output2 = asNonNegInt(usageRec.output_tokens) ?? asNonNegInt(usageRec.completion_tokens) ?? asNonNegInt(usageRec.output);
|
|
1633
1746
|
if (input === null && output2 === null) return void 0;
|
|
1634
1747
|
const out = {
|
|
1635
1748
|
input_tokens: input ?? 0,
|
|
@@ -1641,6 +1754,18 @@ function extractUsage(payload) {
|
|
|
1641
1754
|
}
|
|
1642
1755
|
const modelId = extractModelId(payload);
|
|
1643
1756
|
if (modelId) out.model_id = modelId;
|
|
1757
|
+
const reasoning = asNonNegInt(usageRec.reasoning_tokens) ?? asNonNegInt(usageRec.reasoningTokens);
|
|
1758
|
+
const cacheRead = asNonNegInt(usageRec.cache_read_tokens) ?? asNonNegInt(usageRec.cacheRead);
|
|
1759
|
+
const cacheWrite = asNonNegInt(usageRec.cache_write_tokens) ?? asNonNegInt(usageRec.cacheWrite);
|
|
1760
|
+
const total = asNonNegInt(usageRec.total_tokens) ?? asNonNegInt(usageRec.total);
|
|
1761
|
+
const duration = asNonNegInt(usageRec.duration_ms) ?? asNonNegInt(usageRec.durationMs) ?? asNonNegInt(rec.duration_ms) ?? asNonNegInt(rec.durationMs);
|
|
1762
|
+
const provider = typeof usageRec.provider === "string" && usageRec.provider.trim() || typeof rec.provider === "string" && rec.provider.trim() || "";
|
|
1763
|
+
if (reasoning !== null) out.reasoning_tokens = reasoning;
|
|
1764
|
+
if (cacheRead !== null) out.cache_read_tokens = cacheRead;
|
|
1765
|
+
if (cacheWrite !== null) out.cache_write_tokens = cacheWrite;
|
|
1766
|
+
if (total !== null) out.total_tokens = total;
|
|
1767
|
+
if (duration !== null) out.duration_ms = duration;
|
|
1768
|
+
if (provider) out.provider = provider.slice(0, 80);
|
|
1644
1769
|
return out;
|
|
1645
1770
|
}
|
|
1646
1771
|
function parseCompletePayload(payload) {
|
|
@@ -1813,6 +1938,24 @@ async function postWriteback(event, complete, opts, deps) {
|
|
|
1813
1938
|
if (complete.usage.model_id) {
|
|
1814
1939
|
usageBody.model_id = complete.usage.model_id;
|
|
1815
1940
|
}
|
|
1941
|
+
if (complete.usage.reasoning_tokens != null) {
|
|
1942
|
+
usageBody.reasoning_tokens = complete.usage.reasoning_tokens;
|
|
1943
|
+
}
|
|
1944
|
+
if (complete.usage.cache_read_tokens != null) {
|
|
1945
|
+
usageBody.cache_read_tokens = complete.usage.cache_read_tokens;
|
|
1946
|
+
}
|
|
1947
|
+
if (complete.usage.cache_write_tokens != null) {
|
|
1948
|
+
usageBody.cache_write_tokens = complete.usage.cache_write_tokens;
|
|
1949
|
+
}
|
|
1950
|
+
if (complete.usage.total_tokens != null) {
|
|
1951
|
+
usageBody.total_tokens = complete.usage.total_tokens;
|
|
1952
|
+
}
|
|
1953
|
+
if (complete.usage.duration_ms != null) {
|
|
1954
|
+
usageBody.duration_ms = complete.usage.duration_ms;
|
|
1955
|
+
}
|
|
1956
|
+
if (complete.usage.provider) {
|
|
1957
|
+
usageBody.provider = complete.usage.provider;
|
|
1958
|
+
}
|
|
1816
1959
|
body.usage = usageBody;
|
|
1817
1960
|
} else if (complete.modelId) {
|
|
1818
1961
|
body.usage = { model_id: complete.modelId };
|
|
@@ -2297,43 +2440,6 @@ var KEEPALIVE_INTERVAL_MS = 3e4;
|
|
|
2297
2440
|
var MODEL_HEARTBEAT_INTERVAL_MS = 15 * 6e4;
|
|
2298
2441
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
2299
2442
|
var MAX_BACKOFF_MS = 3e4;
|
|
2300
|
-
function resolvePreferredModel(opts) {
|
|
2301
|
-
const env = opts?.env ?? process.env;
|
|
2302
|
-
const fromFlag = opts?.model?.trim();
|
|
2303
|
-
if (fromFlag) return fromFlag.slice(0, 200);
|
|
2304
|
-
const fromEnv = env.ACN_PREFERRED_MODEL?.trim();
|
|
2305
|
-
if (fromEnv) return fromEnv.slice(0, 200);
|
|
2306
|
-
return void 0;
|
|
2307
|
-
}
|
|
2308
|
-
async function postAgentHeartbeat(opts) {
|
|
2309
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
2310
|
-
const origin = opts.baseUrl.replace(/\/+$/, "");
|
|
2311
|
-
const url = `${origin}/api/v1/agents/${opts.agentId}/heartbeat`;
|
|
2312
|
-
const headers = {
|
|
2313
|
-
Authorization: `Bearer ${opts.apiKey}`,
|
|
2314
|
-
"Content-Type": "application/json"
|
|
2315
|
-
};
|
|
2316
|
-
const body = opts.preferredModel && opts.preferredModel.trim() ? JSON.stringify({ preferred_model: opts.preferredModel.trim().slice(0, 200) }) : void 0;
|
|
2317
|
-
try {
|
|
2318
|
-
const res = await fetchFn(url, { method: "POST", headers, body });
|
|
2319
|
-
if (!res.ok) {
|
|
2320
|
-
return { ok: false, reason: `http_${res.status}` };
|
|
2321
|
-
}
|
|
2322
|
-
let preferred;
|
|
2323
|
-
try {
|
|
2324
|
-
const json = await res.json();
|
|
2325
|
-
preferred = json.preferred_model;
|
|
2326
|
-
} catch {
|
|
2327
|
-
preferred = opts.preferredModel ?? null;
|
|
2328
|
-
}
|
|
2329
|
-
return { ok: true, preferred_model: preferred };
|
|
2330
|
-
} catch (err) {
|
|
2331
|
-
return {
|
|
2332
|
-
ok: false,
|
|
2333
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
2334
|
-
};
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
2443
|
function runListener(cfg) {
|
|
2338
2444
|
const wsUrl = toWebsocketUrl(cfg.baseUrl, cfg.agentId);
|
|
2339
2445
|
let backoff = INITIAL_BACKOFF_MS;
|
|
@@ -2346,25 +2452,32 @@ function runListener(cfg) {
|
|
|
2346
2452
|
let keepalive;
|
|
2347
2453
|
let modelHeartbeat;
|
|
2348
2454
|
const sendModelHeartbeat = () => {
|
|
2349
|
-
if (
|
|
2455
|
+
if (stopped) return;
|
|
2456
|
+
if (!cfg.preferredModel && cfg.supportedModels === void 0) return;
|
|
2350
2457
|
void postAgentHeartbeat({
|
|
2351
2458
|
baseUrl: cfg.baseUrl,
|
|
2352
2459
|
agentId: cfg.agentId,
|
|
2353
2460
|
apiKey: cfg.apiKey,
|
|
2354
|
-
preferredModel: cfg.preferredModel
|
|
2461
|
+
preferredModel: cfg.preferredModel,
|
|
2462
|
+
supportedModels: cfg.supportedModels
|
|
2355
2463
|
}).then((r) => {
|
|
2356
2464
|
if (!r.ok) {
|
|
2357
|
-
console.error(`[acn listen]
|
|
2465
|
+
console.error(`[acn listen] model heartbeat failed: ${r.reason}`);
|
|
2358
2466
|
return;
|
|
2359
2467
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2468
|
+
const note = formatModelHeartbeatLog({
|
|
2469
|
+
preferred: r.preferred_model ?? cfg.preferredModel,
|
|
2470
|
+
supported: r.supported_models ?? cfg.supportedModels
|
|
2471
|
+
});
|
|
2472
|
+
console.error(`[acn listen] model heartbeat ok${note}`);
|
|
2363
2473
|
});
|
|
2364
2474
|
};
|
|
2365
2475
|
ws.on("open", () => {
|
|
2366
2476
|
const mode = cfg.runtime ? `runtime=${cfg.runtime.runtime}` : cfg.forward ? `forward=${cfg.forward}` : `exec`;
|
|
2367
|
-
const modelNote =
|
|
2477
|
+
const modelNote = formatModelHeartbeatLog({
|
|
2478
|
+
preferred: cfg.preferredModel,
|
|
2479
|
+
supported: cfg.supportedModels
|
|
2480
|
+
});
|
|
2368
2481
|
console.error(
|
|
2369
2482
|
`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl} (${mode})${modelNote}`
|
|
2370
2483
|
);
|
|
@@ -2374,7 +2487,7 @@ function runListener(cfg) {
|
|
|
2374
2487
|
ws.send(JSON.stringify({ type: "ping" }));
|
|
2375
2488
|
}
|
|
2376
2489
|
}, KEEPALIVE_INTERVAL_MS);
|
|
2377
|
-
if (cfg.preferredModel) {
|
|
2490
|
+
if (cfg.preferredModel || cfg.supportedModels !== void 0) {
|
|
2378
2491
|
sendModelHeartbeat();
|
|
2379
2492
|
modelHeartbeat = setInterval(sendModelHeartbeat, MODEL_HEARTBEAT_INTERVAL_MS);
|
|
2380
2493
|
}
|
|
@@ -2488,6 +2601,12 @@ function listenCommand() {
|
|
|
2488
2601
|
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").option(
|
|
2489
2602
|
"-m, --model <modelId>",
|
|
2490
2603
|
"Declare runtime model (Host Catalog id) on connect + every 15m via REST heartbeat (self-reported; env: ACN_PREFERRED_MODEL)"
|
|
2604
|
+
).option(
|
|
2605
|
+
"--supported-models <ids>",
|
|
2606
|
+
"Comma-separated Host Catalog model ids this runtime can run (Interfaze composer dropdown; env: ACN_SUPPORTED_MODELS)"
|
|
2607
|
+
).option(
|
|
2608
|
+
"--clear-supported-models",
|
|
2609
|
+
"Clear metadata.supported_models on the server (sends empty list)"
|
|
2491
2610
|
).action(
|
|
2492
2611
|
(opts) => {
|
|
2493
2612
|
const config = loadConfig();
|
|
@@ -2579,11 +2698,22 @@ function listenCommand() {
|
|
|
2579
2698
|
audience: process.env.ACN_CHAT_JWT_AUDIENCE?.trim() || process.env.AGENTPLANET_JWT_AUDIENCE?.trim()
|
|
2580
2699
|
});
|
|
2581
2700
|
const preferredModel = resolvePreferredModel({ model: opts.model });
|
|
2701
|
+
if (opts.clearSupportedModels && opts.supportedModels?.trim()) {
|
|
2702
|
+
console.error(
|
|
2703
|
+
"Use either --supported-models or --clear-supported-models, not both."
|
|
2704
|
+
);
|
|
2705
|
+
process.exit(1);
|
|
2706
|
+
}
|
|
2707
|
+
const supportedModels = resolveSupportedModels({
|
|
2708
|
+
models: opts.supportedModels,
|
|
2709
|
+
clear: !!opts.clearSupportedModels
|
|
2710
|
+
});
|
|
2582
2711
|
runListener({
|
|
2583
2712
|
agentId,
|
|
2584
2713
|
apiKey,
|
|
2585
2714
|
baseUrl: config.base_url,
|
|
2586
2715
|
preferredModel,
|
|
2716
|
+
supportedModels,
|
|
2587
2717
|
forward: opts.forward,
|
|
2588
2718
|
exec: opts.exec,
|
|
2589
2719
|
runtime: opts.runtime ? {
|