@acnlabs/acn-cli 1.0.0 → 1.0.1
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 +109 -8
- package/package.json +2 -2
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.1",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -430,7 +430,10 @@ function joinCommand() {
|
|
|
430
430
|
// src/commands/heartbeat.ts
|
|
431
431
|
var import_commander3 = require("commander");
|
|
432
432
|
function heartbeatCommand() {
|
|
433
|
-
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)").
|
|
433
|
+
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
|
+
"-m, --model <modelId>",
|
|
435
|
+
"Declare runtime model (Host Catalog id) for Host Pricing prefill \u2014 self-reported (env: ACN_PREFERRED_MODEL)"
|
|
436
|
+
).action(async (opts) => {
|
|
434
437
|
const config = loadConfig();
|
|
435
438
|
const agentId = opts.agentId ?? config.agent_id;
|
|
436
439
|
if (!agentId) {
|
|
@@ -438,8 +441,11 @@ function heartbeatCommand() {
|
|
|
438
441
|
process.exit(1);
|
|
439
442
|
}
|
|
440
443
|
try {
|
|
441
|
-
const
|
|
442
|
-
|
|
444
|
+
const model = opts.model && opts.model.trim() || process.env.ACN_PREFERRED_MODEL?.trim() || "";
|
|
445
|
+
const body = model ? { preferred_model: model.slice(0, 200) } : void 0;
|
|
446
|
+
const res = await acnPost(`/agents/${agentId}/heartbeat`, body);
|
|
447
|
+
const modelNote = res.preferred_model ? ` (preferred_model=${res.preferred_model})` : "";
|
|
448
|
+
output(res, `Heartbeat sent for agent ${agentId}${modelNote}`);
|
|
443
449
|
} catch (err) {
|
|
444
450
|
handleError(err);
|
|
445
451
|
}
|
|
@@ -1604,6 +1610,20 @@ function asNonNegInt(v) {
|
|
|
1604
1610
|
}
|
|
1605
1611
|
return null;
|
|
1606
1612
|
}
|
|
1613
|
+
function extractModelId(payload) {
|
|
1614
|
+
const rec = asRecord2(payload);
|
|
1615
|
+
if (!rec) return void 0;
|
|
1616
|
+
const usageRec = asRecord2(rec.usage);
|
|
1617
|
+
for (const raw of [
|
|
1618
|
+
usageRec?.model_id,
|
|
1619
|
+
usageRec?.model,
|
|
1620
|
+
rec.model_id,
|
|
1621
|
+
rec.model
|
|
1622
|
+
]) {
|
|
1623
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim().slice(0, 200);
|
|
1624
|
+
}
|
|
1625
|
+
return void 0;
|
|
1626
|
+
}
|
|
1607
1627
|
function extractUsage(payload) {
|
|
1608
1628
|
const rec = asRecord2(payload);
|
|
1609
1629
|
if (!rec) return void 0;
|
|
@@ -1619,13 +1639,19 @@ function extractUsage(payload) {
|
|
|
1619
1639
|
if (ms === "peer_self" || ms === "gateway" || ms === "runtime_attested" || ms === "protocol") {
|
|
1620
1640
|
out.meter_source = ms;
|
|
1621
1641
|
}
|
|
1642
|
+
const modelId = extractModelId(payload);
|
|
1643
|
+
if (modelId) out.model_id = modelId;
|
|
1622
1644
|
return out;
|
|
1623
1645
|
}
|
|
1624
1646
|
function parseCompletePayload(payload) {
|
|
1625
1647
|
const content = extractContent(payload);
|
|
1626
1648
|
if (!content) return { ok: false, reason: "complete_missing_content" };
|
|
1627
1649
|
const usage = extractUsage(payload);
|
|
1628
|
-
|
|
1650
|
+
const modelId = extractModelId(payload);
|
|
1651
|
+
const result = { content };
|
|
1652
|
+
if (usage) result.usage = usage;
|
|
1653
|
+
else if (modelId) result.modelId = modelId;
|
|
1654
|
+
return { ok: true, result };
|
|
1629
1655
|
}
|
|
1630
1656
|
async function mintAgentJwt(opts, fetchFn = fetch) {
|
|
1631
1657
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -1779,11 +1805,17 @@ async function postWriteback(event, complete, opts, deps) {
|
|
|
1779
1805
|
reply_to_id: replyToId
|
|
1780
1806
|
};
|
|
1781
1807
|
if (complete.usage) {
|
|
1782
|
-
|
|
1808
|
+
const usageBody = {
|
|
1783
1809
|
input_tokens: complete.usage.input_tokens,
|
|
1784
1810
|
output_tokens: complete.usage.output_tokens,
|
|
1785
1811
|
meter_source: complete.usage.meter_source ?? "peer_self"
|
|
1786
1812
|
};
|
|
1813
|
+
if (complete.usage.model_id) {
|
|
1814
|
+
usageBody.model_id = complete.usage.model_id;
|
|
1815
|
+
}
|
|
1816
|
+
body.usage = usageBody;
|
|
1817
|
+
} else if (complete.modelId) {
|
|
1818
|
+
body.usage = { model_id: complete.modelId };
|
|
1787
1819
|
}
|
|
1788
1820
|
const postOnce = async (token) => {
|
|
1789
1821
|
const controller = new AbortController();
|
|
@@ -2262,8 +2294,46 @@ function rawToString(data) {
|
|
|
2262
2294
|
return Buffer.from(data).toString("utf-8");
|
|
2263
2295
|
}
|
|
2264
2296
|
var KEEPALIVE_INTERVAL_MS = 3e4;
|
|
2297
|
+
var MODEL_HEARTBEAT_INTERVAL_MS = 15 * 6e4;
|
|
2265
2298
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
2266
2299
|
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
|
+
}
|
|
2267
2337
|
function runListener(cfg) {
|
|
2268
2338
|
const wsUrl = toWebsocketUrl(cfg.baseUrl, cfg.agentId);
|
|
2269
2339
|
let backoff = INITIAL_BACKOFF_MS;
|
|
@@ -2274,15 +2344,40 @@ function runListener(cfg) {
|
|
|
2274
2344
|
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
2275
2345
|
});
|
|
2276
2346
|
let keepalive;
|
|
2347
|
+
let modelHeartbeat;
|
|
2348
|
+
const sendModelHeartbeat = () => {
|
|
2349
|
+
if (!cfg.preferredModel || stopped) return;
|
|
2350
|
+
void postAgentHeartbeat({
|
|
2351
|
+
baseUrl: cfg.baseUrl,
|
|
2352
|
+
agentId: cfg.agentId,
|
|
2353
|
+
apiKey: cfg.apiKey,
|
|
2354
|
+
preferredModel: cfg.preferredModel
|
|
2355
|
+
}).then((r) => {
|
|
2356
|
+
if (!r.ok) {
|
|
2357
|
+
console.error(`[acn listen] preferred_model heartbeat failed: ${r.reason}`);
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
console.error(
|
|
2361
|
+
`[acn listen] preferred_model heartbeat ok model=${r.preferred_model ?? cfg.preferredModel}`
|
|
2362
|
+
);
|
|
2363
|
+
});
|
|
2364
|
+
};
|
|
2277
2365
|
ws.on("open", () => {
|
|
2278
2366
|
const mode = cfg.runtime ? `runtime=${cfg.runtime.runtime}` : cfg.forward ? `forward=${cfg.forward}` : `exec`;
|
|
2279
|
-
|
|
2367
|
+
const modelNote = cfg.preferredModel ? ` preferred_model=${cfg.preferredModel}` : "";
|
|
2368
|
+
console.error(
|
|
2369
|
+
`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl} (${mode})${modelNote}`
|
|
2370
|
+
);
|
|
2280
2371
|
backoff = INITIAL_BACKOFF_MS;
|
|
2281
2372
|
keepalive = setInterval(() => {
|
|
2282
2373
|
if (ws.readyState === import_ws.default.OPEN) {
|
|
2283
2374
|
ws.send(JSON.stringify({ type: "ping" }));
|
|
2284
2375
|
}
|
|
2285
2376
|
}, KEEPALIVE_INTERVAL_MS);
|
|
2377
|
+
if (cfg.preferredModel) {
|
|
2378
|
+
sendModelHeartbeat();
|
|
2379
|
+
modelHeartbeat = setInterval(sendModelHeartbeat, MODEL_HEARTBEAT_INTERVAL_MS);
|
|
2380
|
+
}
|
|
2286
2381
|
});
|
|
2287
2382
|
ws.on("message", (data) => {
|
|
2288
2383
|
let frame;
|
|
@@ -2304,6 +2399,7 @@ function runListener(cfg) {
|
|
|
2304
2399
|
});
|
|
2305
2400
|
ws.on("close", (code, reason) => {
|
|
2306
2401
|
if (keepalive) clearInterval(keepalive);
|
|
2402
|
+
if (modelHeartbeat) clearInterval(modelHeartbeat);
|
|
2307
2403
|
if (stopped) return;
|
|
2308
2404
|
if (code === 4401 || code === 4403 || code === 4429) {
|
|
2309
2405
|
console.error(
|
|
@@ -2389,7 +2485,10 @@ function listenCommand() {
|
|
|
2389
2485
|
"--chat-complete-timeout <ms>",
|
|
2390
2486
|
`Host complete timeout in ms (default ${DEFAULT_COMPLETE_TIMEOUT_MS})`,
|
|
2391
2487
|
String(DEFAULT_COMPLETE_TIMEOUT_MS)
|
|
2392
|
-
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").
|
|
2488
|
+
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").option(
|
|
2489
|
+
"-m, --model <modelId>",
|
|
2490
|
+
"Declare runtime model (Host Catalog id) on connect + every 15m via REST heartbeat (self-reported; env: ACN_PREFERRED_MODEL)"
|
|
2491
|
+
).action(
|
|
2393
2492
|
(opts) => {
|
|
2394
2493
|
const config = loadConfig();
|
|
2395
2494
|
const apiKey = config.api_key;
|
|
@@ -2479,10 +2578,12 @@ function listenCommand() {
|
|
|
2479
2578
|
agentId,
|
|
2480
2579
|
audience: process.env.ACN_CHAT_JWT_AUDIENCE?.trim() || process.env.AGENTPLANET_JWT_AUDIENCE?.trim()
|
|
2481
2580
|
});
|
|
2581
|
+
const preferredModel = resolvePreferredModel({ model: opts.model });
|
|
2482
2582
|
runListener({
|
|
2483
2583
|
agentId,
|
|
2484
2584
|
apiKey,
|
|
2485
2585
|
baseUrl: config.base_url,
|
|
2586
|
+
preferredModel,
|
|
2486
2587
|
forward: opts.forward,
|
|
2487
2588
|
exec: opts.exec,
|
|
2488
2589
|
runtime: opts.runtime ? {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acnlabs/acn-cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Official CLI for ACN (Agent Collaboration Network)
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"acn": "dist/index.js"
|