@snowyroad/arp 0.3.5 → 0.3.7
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/cli.js +221 -27
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1439,7 +1439,27 @@ var RelayClient = class {
|
|
|
1439
1439
|
return [];
|
|
1440
1440
|
}
|
|
1441
1441
|
}
|
|
1442
|
-
|
|
1442
|
+
/** Spread optional usage into the outbound body. Omits undefined fields so a no-usage
|
|
1443
|
+
* call produces a body byte-identical to the pre-billing shape. */
|
|
1444
|
+
usageBody(u) {
|
|
1445
|
+
if (!u) return {};
|
|
1446
|
+
const b = { costReported: u.costReported };
|
|
1447
|
+
if (u.model !== void 0) b.model = u.model;
|
|
1448
|
+
if (u.provider !== void 0) b.provider = u.provider;
|
|
1449
|
+
if (u.authMode !== void 0) b.authMode = u.authMode;
|
|
1450
|
+
if (u.costUsed !== void 0) b.costUsed = u.costUsed;
|
|
1451
|
+
if (u.costCurrency !== void 0) b.costCurrency = u.costCurrency;
|
|
1452
|
+
if (u.contextUsed !== void 0) b.contextUsed = u.contextUsed;
|
|
1453
|
+
if (u.contextSize !== void 0) b.contextSize = u.contextSize;
|
|
1454
|
+
if (u.tokensUsed !== void 0) b.tokensUsed = u.tokensUsed;
|
|
1455
|
+
if (u.inputTokens !== void 0) b.inputTokens = u.inputTokens;
|
|
1456
|
+
if (u.outputTokens !== void 0) b.outputTokens = u.outputTokens;
|
|
1457
|
+
if (u.cachedReadTokens !== void 0) b.cachedReadTokens = u.cachedReadTokens;
|
|
1458
|
+
if (u.cachedWriteTokens !== void 0) b.cachedWriteTokens = u.cachedWriteTokens;
|
|
1459
|
+
if (u.thoughtTokens !== void 0) b.thoughtTokens = u.thoughtTokens;
|
|
1460
|
+
return b;
|
|
1461
|
+
}
|
|
1462
|
+
async postMessage(channelId, content, usage) {
|
|
1443
1463
|
const ch = this.pathId(channelId, "channelId");
|
|
1444
1464
|
if (!ch) return;
|
|
1445
1465
|
const url = `${this.cfg.relayHttpUrl}/channels/${ch}/messages`;
|
|
@@ -1449,7 +1469,8 @@ var RelayClient = class {
|
|
|
1449
1469
|
content,
|
|
1450
1470
|
agentId: this.cfg.agentUuid,
|
|
1451
1471
|
agentName: this.cfg.agentName,
|
|
1452
|
-
messageType: "agent"
|
|
1472
|
+
messageType: "agent",
|
|
1473
|
+
...this.usageBody(usage)
|
|
1453
1474
|
});
|
|
1454
1475
|
try {
|
|
1455
1476
|
const res = await this.deps.fetchFn(url, {
|
|
@@ -1467,7 +1488,7 @@ var RelayClient = class {
|
|
|
1467
1488
|
/** Post a bounded-flow reply (turn or synthesis) to the flow-scoped endpoint.
|
|
1468
1489
|
* agentId MUST be the agent NAME — the relay's flow gate resolves turn ownership and
|
|
1469
1490
|
* synthesis role via resolveAgentUUID (a name lookup); a UUID resolves to uuid.Nil -> 403. */
|
|
1470
|
-
async postFlowMessage(channelId, flowId, content) {
|
|
1491
|
+
async postFlowMessage(channelId, flowId, content, usage) {
|
|
1471
1492
|
const ch = this.pathId(channelId, "channelId");
|
|
1472
1493
|
const fl = this.pathId(flowId, "flowId");
|
|
1473
1494
|
if (!ch || !fl) return;
|
|
@@ -1478,7 +1499,8 @@ var RelayClient = class {
|
|
|
1478
1499
|
// The relay's flow gate resolves turn ownership + synthesis role by NAME
|
|
1479
1500
|
// (resolveAgentUUID is a name lookup), so the flow reply must carry the agent name.
|
|
1480
1501
|
agentId: this.cfg.agentName,
|
|
1481
|
-
agentName: this.cfg.agentName
|
|
1502
|
+
agentName: this.cfg.agentName,
|
|
1503
|
+
...this.usageBody(usage)
|
|
1482
1504
|
});
|
|
1483
1505
|
try {
|
|
1484
1506
|
const res = await this.deps.fetchFn(url, {
|
|
@@ -1697,10 +1719,10 @@ ${toolStatusLine(this.toolMode)}
|
|
|
1697
1719
|
}
|
|
1698
1720
|
async start(opts) {
|
|
1699
1721
|
this.session = await this.adapter.start(opts);
|
|
1700
|
-
this.session.onTurn((full) => {
|
|
1722
|
+
this.session.onTurn((full, usage) => {
|
|
1701
1723
|
this.beacon?.end();
|
|
1702
1724
|
if (full.replace(/<<silent>>/gi, "").trim() === "") return;
|
|
1703
|
-
this.onReply(full.replace(/^\s*(?:<<silent>>\s*)+/i, "").trim());
|
|
1725
|
+
this.onReply(full.replace(/^\s*(?:<<silent>>\s*)+/i, "").trim(), usage);
|
|
1704
1726
|
});
|
|
1705
1727
|
}
|
|
1706
1728
|
/**
|
|
@@ -1867,7 +1889,7 @@ var ActivityBeacon = class {
|
|
|
1867
1889
|
// src/adapter.ts
|
|
1868
1890
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1869
1891
|
import { accessSync, constants, existsSync as existsSync2, statSync } from "fs";
|
|
1870
|
-
import { delimiter, dirname as
|
|
1892
|
+
import { delimiter, dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
1871
1893
|
|
|
1872
1894
|
// src/acp/client.ts
|
|
1873
1895
|
import { spawn } from "child_process";
|
|
@@ -1967,6 +1989,7 @@ var AcpClient = class {
|
|
|
1967
1989
|
child = null;
|
|
1968
1990
|
conn = null;
|
|
1969
1991
|
_sessionId = null;
|
|
1992
|
+
loadSupported = false;
|
|
1970
1993
|
/**
|
|
1971
1994
|
* The currently-running turn's reply accumulator. Set for the duration of one
|
|
1972
1995
|
* turn so agent_message_chunk text lands in THIS turn's buffer only. Because
|
|
@@ -2054,6 +2077,15 @@ var AcpClient = class {
|
|
|
2054
2077
|
if (u.sessionUpdate === "agent_message_chunk" && u.content?.type === "text" && this.activeTurnBuffer) {
|
|
2055
2078
|
this.activeTurnBuffer.text += u.content.text;
|
|
2056
2079
|
}
|
|
2080
|
+
if (u.sessionUpdate === "usage_update" && this.activeTurnBuffer) {
|
|
2081
|
+
this.activeTurnBuffer.usage ??= {};
|
|
2082
|
+
this.activeTurnBuffer.usage.contextUsed = u.used;
|
|
2083
|
+
this.activeTurnBuffer.usage.contextSize = u.size;
|
|
2084
|
+
if (u.cost) {
|
|
2085
|
+
this.activeTurnBuffer.usage.costCumulative = u.cost.amount;
|
|
2086
|
+
this.activeTurnBuffer.usage.costCurrency = u.cost.currency;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2057
2089
|
},
|
|
2058
2090
|
requestPermission: async (req) => {
|
|
2059
2091
|
const verdict = evaluateAcpPermission(this.policy.mode, this.policy.configDirAbs, req);
|
|
@@ -2075,7 +2107,7 @@ var AcpClient = class {
|
|
|
2075
2107
|
}
|
|
2076
2108
|
};
|
|
2077
2109
|
this.conn = new ClientSideConnection(() => client, stream);
|
|
2078
|
-
await this.guard(
|
|
2110
|
+
const init = await this.guard(
|
|
2079
2111
|
this.conn.initialize({
|
|
2080
2112
|
protocolVersion: 1,
|
|
2081
2113
|
clientCapabilities: {
|
|
@@ -2085,10 +2117,29 @@ var AcpClient = class {
|
|
|
2085
2117
|
clientInfo: { name: "arp-bridge", version: "0.1.0" }
|
|
2086
2118
|
})
|
|
2087
2119
|
);
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
this.
|
|
2120
|
+
this.loadSupported = init.agentCapabilities?.loadSession === true;
|
|
2121
|
+
const candidateId = this._sessionId ?? this.launch.session?.persistedId ?? null;
|
|
2122
|
+
let liveId = null;
|
|
2123
|
+
if (candidateId && this.loadSupported) {
|
|
2124
|
+
try {
|
|
2125
|
+
await this.guard(
|
|
2126
|
+
this.conn.loadSession({ sessionId: candidateId, cwd: this.launch.cwd, mcpServers: [] })
|
|
2127
|
+
);
|
|
2128
|
+
liveId = candidateId;
|
|
2129
|
+
} catch (err) {
|
|
2130
|
+
console.warn(
|
|
2131
|
+
`[arp-bridge] session/load failed; starting a fresh session: ${sanitizeForTty(String(err?.message ?? err))}`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (!liveId) {
|
|
2136
|
+
const session = await this.guard(
|
|
2137
|
+
this.conn.newSession({ cwd: this.launch.cwd, mcpServers: [] })
|
|
2138
|
+
);
|
|
2139
|
+
liveId = session.sessionId;
|
|
2140
|
+
}
|
|
2141
|
+
this._sessionId = liveId;
|
|
2142
|
+
this.launch.session?.save(liveId);
|
|
2092
2143
|
}
|
|
2093
2144
|
/**
|
|
2094
2145
|
* Send one user turn. Resolves with the full assembled reply text once the
|
|
@@ -2119,13 +2170,27 @@ var AcpClient = class {
|
|
|
2119
2170
|
const buffer = { text: "" };
|
|
2120
2171
|
this.activeTurnBuffer = buffer;
|
|
2121
2172
|
try {
|
|
2122
|
-
await this.guard(
|
|
2173
|
+
const resp = await this.guard(
|
|
2123
2174
|
this.conn.prompt({
|
|
2124
2175
|
sessionId: this._sessionId,
|
|
2125
2176
|
prompt: [{ type: "text", text }]
|
|
2126
2177
|
})
|
|
2127
2178
|
);
|
|
2128
|
-
|
|
2179
|
+
const u = resp?.usage;
|
|
2180
|
+
if (u) {
|
|
2181
|
+
buffer.usage = {
|
|
2182
|
+
...buffer.usage ?? {},
|
|
2183
|
+
tokenBreakdown: {
|
|
2184
|
+
inputTokens: u.inputTokens,
|
|
2185
|
+
outputTokens: u.outputTokens,
|
|
2186
|
+
cachedReadTokens: u.cachedReadTokens ?? void 0,
|
|
2187
|
+
cachedWriteTokens: u.cachedWriteTokens ?? void 0,
|
|
2188
|
+
thoughtTokens: u.thoughtTokens ?? void 0,
|
|
2189
|
+
totalTokens: u.totalTokens
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
return { text: buffer.text, usage: buffer.usage };
|
|
2129
2194
|
} finally {
|
|
2130
2195
|
if (this.activeTurnBuffer === buffer) this.activeTurnBuffer = null;
|
|
2131
2196
|
}
|
|
@@ -2189,6 +2254,94 @@ var AcpClient = class {
|
|
|
2189
2254
|
}
|
|
2190
2255
|
};
|
|
2191
2256
|
|
|
2257
|
+
// src/sessionStore.ts
|
|
2258
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync2, chmodSync as chmodSync2 } from "fs";
|
|
2259
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
2260
|
+
function safe(s) {
|
|
2261
|
+
return s.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2262
|
+
}
|
|
2263
|
+
function sessionFilePath(dir, relayUrl, agentName, channelId) {
|
|
2264
|
+
return join3(dir, "sessions", relayHostSegment(relayUrl), `${safe(agentName)}__${safe(channelId)}.json`);
|
|
2265
|
+
}
|
|
2266
|
+
function loadStoredSession(dir, relayUrl, agentName, channelId) {
|
|
2267
|
+
const file = sessionFilePath(dir, relayUrl, agentName, channelId);
|
|
2268
|
+
let raw;
|
|
2269
|
+
try {
|
|
2270
|
+
raw = readFileSync2(file, "utf8");
|
|
2271
|
+
} catch {
|
|
2272
|
+
return null;
|
|
2273
|
+
}
|
|
2274
|
+
try {
|
|
2275
|
+
const p = JSON.parse(raw);
|
|
2276
|
+
if (typeof p.sessionId === "string" && p.sessionId.trim() !== "" && typeof p.cwd === "string" && p.cwd.trim() !== "") {
|
|
2277
|
+
const rec = { sessionId: p.sessionId, cwd: p.cwd };
|
|
2278
|
+
if (typeof p.costCumulativeLastSeen === "number") rec.costCumulativeLastSeen = p.costCumulativeLastSeen;
|
|
2279
|
+
if (typeof p.costCurrency === "string") rec.costCurrency = p.costCurrency;
|
|
2280
|
+
return rec;
|
|
2281
|
+
}
|
|
2282
|
+
return null;
|
|
2283
|
+
} catch {
|
|
2284
|
+
return null;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
function saveStoredSession(dir, relayUrl, agentName, channelId, rec) {
|
|
2288
|
+
const file = sessionFilePath(dir, relayUrl, agentName, channelId);
|
|
2289
|
+
mkdirSync2(dirname2(file), { recursive: true, mode: 448 });
|
|
2290
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
2291
|
+
writeFileSync2(tmp, JSON.stringify(rec, null, 2) + "\n", { mode: 384 });
|
|
2292
|
+
renameSync2(tmp, file);
|
|
2293
|
+
try {
|
|
2294
|
+
chmodSync2(file, 384);
|
|
2295
|
+
} catch {
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
function updateStoredCost(dir, relayUrl, agentName, channelId, cumulative, currency) {
|
|
2299
|
+
const cur = loadStoredSession(dir, relayUrl, agentName, channelId);
|
|
2300
|
+
if (!cur) return;
|
|
2301
|
+
cur.costCumulativeLastSeen = cumulative;
|
|
2302
|
+
if (currency) cur.costCurrency = currency;
|
|
2303
|
+
saveStoredSession(dir, relayUrl, agentName, channelId, cur);
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// src/usage/source.ts
|
|
2307
|
+
var AcpUsageSource = class {
|
|
2308
|
+
constructor(deps) {
|
|
2309
|
+
this.deps = deps;
|
|
2310
|
+
}
|
|
2311
|
+
deps;
|
|
2312
|
+
forTurn(raw) {
|
|
2313
|
+
if (!raw || raw.contextUsed === void 0 && raw.contextSize === void 0 && raw.costCumulative === void 0 && raw.tokenBreakdown === void 0) {
|
|
2314
|
+
return void 0;
|
|
2315
|
+
}
|
|
2316
|
+
const out = {
|
|
2317
|
+
costReported: false,
|
|
2318
|
+
model: this.deps.model,
|
|
2319
|
+
provider: this.deps.provider,
|
|
2320
|
+
authMode: this.deps.authMode
|
|
2321
|
+
};
|
|
2322
|
+
if (raw.contextUsed !== void 0) out.contextUsed = raw.contextUsed;
|
|
2323
|
+
if (raw.contextSize !== void 0) out.contextSize = raw.contextSize;
|
|
2324
|
+
if (raw.costCumulative !== void 0) {
|
|
2325
|
+
out.costReported = true;
|
|
2326
|
+
out.costCurrency = raw.costCurrency;
|
|
2327
|
+
const prior = this.deps.loadBaseline() ?? 0;
|
|
2328
|
+
const delta = Math.max(0, raw.costCumulative - prior);
|
|
2329
|
+
out.costUsed = delta;
|
|
2330
|
+
this.deps.saveBaseline(raw.costCumulative, raw.costCurrency);
|
|
2331
|
+
}
|
|
2332
|
+
if (raw.tokenBreakdown) {
|
|
2333
|
+
const b = raw.tokenBreakdown;
|
|
2334
|
+
out.inputTokens = b.inputTokens;
|
|
2335
|
+
out.outputTokens = b.outputTokens;
|
|
2336
|
+
out.cachedReadTokens = b.cachedReadTokens;
|
|
2337
|
+
out.cachedWriteTokens = b.cachedWriteTokens;
|
|
2338
|
+
out.thoughtTokens = b.thoughtTokens;
|
|
2339
|
+
out.tokensUsed = b.totalTokens;
|
|
2340
|
+
}
|
|
2341
|
+
return out;
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2192
2345
|
// src/adapter.ts
|
|
2193
2346
|
function defaultToolPolicy() {
|
|
2194
2347
|
return { mode: "readonly", configDirAbs: resolve2(configDir(process.env)) };
|
|
@@ -2205,9 +2358,9 @@ function pinned(pkg) {
|
|
|
2205
2358
|
var npxBinaryAbs = null;
|
|
2206
2359
|
function resolveNpxBinary() {
|
|
2207
2360
|
if (npxBinaryAbs) return npxBinaryAbs;
|
|
2208
|
-
const nodeDir =
|
|
2361
|
+
const nodeDir = dirname3(process.execPath);
|
|
2209
2362
|
for (const name of ["npx", "npx.cmd"]) {
|
|
2210
|
-
const candidate =
|
|
2363
|
+
const candidate = join4(nodeDir, name);
|
|
2211
2364
|
if (existsSync2(candidate)) {
|
|
2212
2365
|
npxBinaryAbs = candidate;
|
|
2213
2366
|
return candidate;
|
|
@@ -2220,7 +2373,7 @@ function resolveNpxBinary() {
|
|
|
2220
2373
|
function which(cmd, pathEnv = process.env.PATH ?? "") {
|
|
2221
2374
|
for (const dir of pathEnv.split(delimiter)) {
|
|
2222
2375
|
if (!dir) continue;
|
|
2223
|
-
const candidate =
|
|
2376
|
+
const candidate = join4(dir, cmd);
|
|
2224
2377
|
try {
|
|
2225
2378
|
accessSync(candidate, constants.X_OK);
|
|
2226
2379
|
if (statSync(candidate).isFile()) return resolve2(candidate);
|
|
@@ -2274,13 +2427,17 @@ var defaultAcpClientFactory = (launch) => new AcpClient(launch);
|
|
|
2274
2427
|
var MAX_CONSECUTIVE_RESTARTS = 3;
|
|
2275
2428
|
var RESTART_BACKOFF_MS = 250;
|
|
2276
2429
|
var AcpAdapter = class {
|
|
2277
|
-
constructor(agent, makeClient = defaultAcpClientFactory, backoffMs = RESTART_BACKOFF_MS, policy = defaultToolPolicy()) {
|
|
2430
|
+
constructor(agent, makeClient = defaultAcpClientFactory, backoffMs = RESTART_BACKOFF_MS, policy = defaultToolPolicy(), session, usageSource) {
|
|
2278
2431
|
this.makeClient = makeClient;
|
|
2279
2432
|
this.backoffMs = backoffMs;
|
|
2433
|
+
this.session = session;
|
|
2434
|
+
this.usageSource = usageSource;
|
|
2280
2435
|
this.launch = { ...launchSpecFor(agent), policy };
|
|
2281
2436
|
}
|
|
2282
2437
|
makeClient;
|
|
2283
2438
|
backoffMs;
|
|
2439
|
+
session;
|
|
2440
|
+
usageSource;
|
|
2284
2441
|
launch;
|
|
2285
2442
|
// --- supervised live state (set in start()) ---
|
|
2286
2443
|
client = null;
|
|
@@ -2294,7 +2451,17 @@ var AcpAdapter = class {
|
|
|
2294
2451
|
/** Latched once the loop guard trips, so we stop trying (and stop retrying turns). */
|
|
2295
2452
|
gaveUp = false;
|
|
2296
2453
|
async start(_opts) {
|
|
2297
|
-
|
|
2454
|
+
const rec = this.session?.load() ?? null;
|
|
2455
|
+
const cwd = rec?.cwd ?? this.launch.cwd;
|
|
2456
|
+
const launch = {
|
|
2457
|
+
...this.launch,
|
|
2458
|
+
cwd,
|
|
2459
|
+
session: this.session ? {
|
|
2460
|
+
persistedId: rec?.sessionId ?? null,
|
|
2461
|
+
save: (id) => this.session.save({ sessionId: id, cwd })
|
|
2462
|
+
} : void 0
|
|
2463
|
+
};
|
|
2464
|
+
this.client = this.makeClient(launch);
|
|
2298
2465
|
await this.client.start();
|
|
2299
2466
|
return {
|
|
2300
2467
|
submit: (text) => {
|
|
@@ -2322,6 +2489,12 @@ var AcpAdapter = class {
|
|
|
2322
2489
|
* never posted to the channel. A local aside on a dead/gave-up client returns a
|
|
2323
2490
|
* clear "agent unavailable" string rather than restarting (restart supervision is
|
|
2324
2491
|
* reserved for channel turns; a local REPL caller sees the message inline).
|
|
2492
|
+
*
|
|
2493
|
+
* BILLING NOTE: local asides deliberately bypass handleTurn, so they do NOT advance
|
|
2494
|
+
* the cost baseline. Any cost they accrue in the warm session surfaces in the NEXT
|
|
2495
|
+
* channel turn's cumulative-delta. Do NOT add a usageSource.forTurn call here — that
|
|
2496
|
+
* would double-count (the same cumulative gets billed once locally and again on the
|
|
2497
|
+
* next channel turn).
|
|
2325
2498
|
*/
|
|
2326
2499
|
async converseLocal(text) {
|
|
2327
2500
|
const client = this.client;
|
|
@@ -2329,7 +2502,7 @@ var AcpAdapter = class {
|
|
|
2329
2502
|
return "[arp-bridge] agent unavailable for local conversation";
|
|
2330
2503
|
}
|
|
2331
2504
|
try {
|
|
2332
|
-
return await client.submit(text);
|
|
2505
|
+
return (await client.submit(text)).text;
|
|
2333
2506
|
} catch (err) {
|
|
2334
2507
|
return `[arp-bridge] local turn failed: ${err?.message ?? String(err)}`;
|
|
2335
2508
|
}
|
|
@@ -2347,9 +2520,10 @@ var AcpAdapter = class {
|
|
|
2347
2520
|
const client = this.client;
|
|
2348
2521
|
if (!client) return false;
|
|
2349
2522
|
try {
|
|
2350
|
-
const
|
|
2523
|
+
const result = await client.submit(text);
|
|
2351
2524
|
this.consecutiveRestarts = 0;
|
|
2352
|
-
this.
|
|
2525
|
+
const usage = this.usageSource?.forTurn(result.usage);
|
|
2526
|
+
this.turnCbs.forEach((cb) => cb(result.text, usage));
|
|
2353
2527
|
return true;
|
|
2354
2528
|
} catch (err) {
|
|
2355
2529
|
if (this.stopped) {
|
|
@@ -2520,14 +2694,34 @@ var ClaudeAdapter = class {
|
|
|
2520
2694
|
};
|
|
2521
2695
|
}
|
|
2522
2696
|
};
|
|
2523
|
-
function createAdapter(cfg) {
|
|
2697
|
+
function createAdapter(cfg, channelId) {
|
|
2524
2698
|
const policy = {
|
|
2525
2699
|
mode: cfg.toolMode,
|
|
2526
2700
|
configDirAbs: resolve2(configDir(process.env)),
|
|
2527
2701
|
agentName: cfg.agentName
|
|
2528
2702
|
// for the once-per-process "arp tools full <name>" denial hint
|
|
2529
2703
|
};
|
|
2530
|
-
|
|
2704
|
+
if (cfg.agentMode !== "acp") return new ClaudeAdapter(policy);
|
|
2705
|
+
const session = channelId ? makeSessionPersistence(cfg, channelId) : void 0;
|
|
2706
|
+
const usageSource = channelId ? makeUsageSource(cfg, channelId) : void 0;
|
|
2707
|
+
return new AcpAdapter(cfg.agent, void 0, void 0, policy, session, usageSource);
|
|
2708
|
+
}
|
|
2709
|
+
function makeSessionPersistence(cfg, channelId) {
|
|
2710
|
+
const dir = configDir(process.env);
|
|
2711
|
+
return {
|
|
2712
|
+
load: () => loadStoredSession(dir, cfg.relayWsUrl, cfg.agentName, channelId),
|
|
2713
|
+
save: (rec) => saveStoredSession(dir, cfg.relayWsUrl, cfg.agentName, channelId, rec)
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
function makeUsageSource(cfg, channelId) {
|
|
2717
|
+
const dir = configDir(process.env);
|
|
2718
|
+
return new AcpUsageSource({
|
|
2719
|
+
model: cfg.model,
|
|
2720
|
+
provider: cfg.agent,
|
|
2721
|
+
authMode: cfg.agentMode === "acp" ? "oauth" : "apikey",
|
|
2722
|
+
loadBaseline: () => loadStoredSession(dir, cfg.relayWsUrl, cfg.agentName, channelId)?.costCumulativeLastSeen,
|
|
2723
|
+
saveBaseline: (cumulative, currency) => updateStoredCost(dir, cfg.relayWsUrl, cfg.agentName, channelId, cumulative, currency)
|
|
2724
|
+
});
|
|
2531
2725
|
}
|
|
2532
2726
|
|
|
2533
2727
|
// src/elicit.ts
|
|
@@ -2612,15 +2806,15 @@ async function startBridge(cfg, relay, deps) {
|
|
|
2612
2806
|
const inFlight = pending.get(channelId);
|
|
2613
2807
|
if (inFlight) return inFlight;
|
|
2614
2808
|
const p = (async () => {
|
|
2615
|
-
const adapter = deps.makeAdapter(cfg);
|
|
2809
|
+
const adapter = deps.makeAdapter(cfg, channelId);
|
|
2616
2810
|
const beacon = new ActivityBeacon((state) => relay.sendActivity(channelId, state));
|
|
2617
2811
|
const session = new ChannelSession(
|
|
2618
2812
|
adapter,
|
|
2619
|
-
(text) => void relay.postMessage(channelId, text),
|
|
2813
|
+
(text, usage) => void relay.postMessage(channelId, text, usage),
|
|
2620
2814
|
cfg.agentName,
|
|
2621
2815
|
channelId,
|
|
2622
2816
|
{
|
|
2623
|
-
postReply: (flowId, content) => relay.postFlowMessage(channelId, flowId, content),
|
|
2817
|
+
postReply: (flowId, content, usage) => relay.postFlowMessage(channelId, flowId, content, usage),
|
|
2624
2818
|
fetchHistory: (flowId) => relay.fetchFlowMessages(channelId, flowId)
|
|
2625
2819
|
},
|
|
2626
2820
|
() => relay.fetchChannelContext(channelId),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/arp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Connect your own coding agent (Claude Code, Codex, Gemini, Grok) to an Agent Relay Protocol channel and collaborate with other agents and humans.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "SnowyRoad",
|