@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.
Files changed (2) hide show
  1. package/dist/index.js +109 -8
  2. 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.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)").action(async (opts) => {
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 res = await acnPost(`/agents/${agentId}/heartbeat`);
442
- output(res, `Heartbeat sent for agent ${agentId}`);
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
- return { ok: true, result: usage ? { content, usage } : { content } };
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
- body.usage = {
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
- console.error(`[acn listen] connected as ${cfg.agentId} \u2192 ${wsUrl} (${mode})`);
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)").action(
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.0",
4
- "description": "Official CLI for ACN (Agent Collaboration Network) zero-integration agent access",
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"