linkshell-cli 0.5.11 → 0.5.13
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/src/runtime/acp/agent-workspace.d.ts +1 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +79 -1
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +2 -1
- package/dist/cli/src/runtime/bridge-session.js +11 -6
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +3151 -2569
- package/dist/shared-protocol/src/index.js +18 -0
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +3 -3
- package/src/runtime/acp/agent-workspace.ts +99 -1
- package/src/runtime/bridge-session.ts +12 -7
- package/web/assets/index-BMb-oP7N.js +94 -0
- package/web/assets/{index-1_KqdVKY.css → index-BXxBRB9A.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-C43Eiy_b.js +0 -94
|
@@ -53,6 +53,21 @@ function firstNumber(value, keys) {
|
|
|
53
53
|
}
|
|
54
54
|
return undefined;
|
|
55
55
|
}
|
|
56
|
+
// Best-effort context-window size (tokens) for a model id, used as the "% used"
|
|
57
|
+
// denominator only when the provider doesn't report it directly. Conservative
|
|
58
|
+
// on purpose: returns undefined for anything we can't confidently map, so the
|
|
59
|
+
// UI shows a raw token count instead of a misleading percentage. Claude omits
|
|
60
|
+
// context_window in result.usage, so this fallback is what gives Claude a meter.
|
|
61
|
+
function contextWindowForModel(model) {
|
|
62
|
+
if (!model)
|
|
63
|
+
return undefined;
|
|
64
|
+
const m = model.toLowerCase();
|
|
65
|
+
if (m.includes("claude")) {
|
|
66
|
+
// 1M-context variants are tagged (e.g. "claude-opus-4-8[1m]"); else 200k.
|
|
67
|
+
return /\b1m\b|\[1m\]|-1m/.test(m) ? 1_000_000 : 200_000;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
56
71
|
function stringArray(value) {
|
|
57
72
|
if (!Array.isArray(value))
|
|
58
73
|
return [];
|
|
@@ -2477,12 +2492,22 @@ export class AgentWorkspaceProxy {
|
|
|
2477
2492
|
}
|
|
2478
2493
|
if (method.startsWith("account/") ||
|
|
2479
2494
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
2480
|
-
method === "thread/tokenUsage/updated" ||
|
|
2481
2495
|
method === "serverRequest/resolved" ||
|
|
2482
2496
|
method === "mcpServer/oauthLogin/completed") {
|
|
2483
2497
|
return;
|
|
2484
2498
|
}
|
|
2485
2499
|
const conversationId = this.conversationIdFromParams(params) ?? this.fallbackConversationId();
|
|
2500
|
+
if (method === "thread/tokenUsage/updated") {
|
|
2501
|
+
// Codex emits cumulative token usage for the thread. Carry it onto the
|
|
2502
|
+
// conversation so clients can show a context meter (parity with Codex's
|
|
2503
|
+
// own /status). Cost is Claude-only; Codex usually omits it.
|
|
2504
|
+
if (conversationId) {
|
|
2505
|
+
const raw = asRecord(params);
|
|
2506
|
+
const usage = asRecord(raw?.usage) ? raw?.usage : params;
|
|
2507
|
+
this.applyConversationUsage(conversationId, usage);
|
|
2508
|
+
}
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2486
2511
|
if (method === "thread/status/changed") {
|
|
2487
2512
|
const status = agentStatusFromThreadStatus(params);
|
|
2488
2513
|
if (conversationId && status) {
|
|
@@ -2541,6 +2566,13 @@ export class AgentWorkspaceProxy {
|
|
|
2541
2566
|
}
|
|
2542
2567
|
if (method === "turn/completed") {
|
|
2543
2568
|
if (conversationId) {
|
|
2569
|
+
// Claude reports cumulative usage + cost on turn completion (Codex sends
|
|
2570
|
+
// it via thread/tokenUsage/updated instead). Capture it for the meter.
|
|
2571
|
+
const raw = asRecord(params);
|
|
2572
|
+
const cost = firstNumber(raw, ["totalCostUsd", "total_cost_usd"]);
|
|
2573
|
+
if (raw?.usage != null || cost != null) {
|
|
2574
|
+
this.applyConversationUsage(conversationId, raw?.usage, cost);
|
|
2575
|
+
}
|
|
2544
2576
|
this.forgetCurrentTurn(conversationId, this.extractTurnId(params));
|
|
2545
2577
|
this.updateConversationStatus(conversationId, "idle");
|
|
2546
2578
|
}
|
|
@@ -3371,6 +3403,52 @@ export class AgentWorkspaceProxy {
|
|
|
3371
3403
|
payload: { conversationId: conversation.id, conversation: { ...conversation } },
|
|
3372
3404
|
}));
|
|
3373
3405
|
}
|
|
3406
|
+
// Normalize a provider usage payload (Claude result.usage, Codex
|
|
3407
|
+
// thread/tokenUsage/updated) into our wire shape, merge onto the conversation,
|
|
3408
|
+
// and emit. Field names vary by provider so we probe snake_case + camelCase.
|
|
3409
|
+
// Missing fields preserve the previous value (cumulative view). No-ops when
|
|
3410
|
+
// nothing meaningful resolves, so a stray empty payload won't blank the meter.
|
|
3411
|
+
applyConversationUsage(conversationId, raw, costUsd) {
|
|
3412
|
+
const conversation = this.conversations.get(conversationId);
|
|
3413
|
+
if (!conversation)
|
|
3414
|
+
return;
|
|
3415
|
+
const rec = asRecord(raw);
|
|
3416
|
+
const num = (...keys) => {
|
|
3417
|
+
for (const k of keys) {
|
|
3418
|
+
const v = rec?.[k];
|
|
3419
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
3420
|
+
return v;
|
|
3421
|
+
}
|
|
3422
|
+
return undefined;
|
|
3423
|
+
};
|
|
3424
|
+
const inputTokens = num("input_tokens", "inputTokens", "input");
|
|
3425
|
+
const outputTokens = num("output_tokens", "outputTokens", "output");
|
|
3426
|
+
const cacheReadTokens = num("cache_read_input_tokens", "cacheReadInputTokens", "cache_read_tokens", "cached_input_tokens", "cacheReadTokens");
|
|
3427
|
+
let totalTokens = num("total_tokens", "totalTokens", "total");
|
|
3428
|
+
if (totalTokens == null && (inputTokens != null || outputTokens != null)) {
|
|
3429
|
+
totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
3430
|
+
}
|
|
3431
|
+
const contextWindow = num("context_window", "contextWindow", "model_context_window", "window") ??
|
|
3432
|
+
contextWindowForModel(conversation.model);
|
|
3433
|
+
const prev = conversation.usage ?? {};
|
|
3434
|
+
const next = {
|
|
3435
|
+
inputTokens: inputTokens ?? prev.inputTokens,
|
|
3436
|
+
outputTokens: outputTokens ?? prev.outputTokens,
|
|
3437
|
+
cacheReadTokens: cacheReadTokens ?? prev.cacheReadTokens,
|
|
3438
|
+
totalTokens: totalTokens ?? prev.totalTokens,
|
|
3439
|
+
contextWindow: contextWindow ?? prev.contextWindow,
|
|
3440
|
+
totalCostUsd: costUsd ?? prev.totalCostUsd,
|
|
3441
|
+
updatedAt: Date.now(),
|
|
3442
|
+
};
|
|
3443
|
+
if (next.inputTokens == null &&
|
|
3444
|
+
next.outputTokens == null &&
|
|
3445
|
+
next.totalTokens == null &&
|
|
3446
|
+
next.totalCostUsd == null) {
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
conversation.usage = next;
|
|
3450
|
+
this.emitConversation(conversation);
|
|
3451
|
+
}
|
|
3374
3452
|
emitStatus(conversationId, status, text) {
|
|
3375
3453
|
this.addItem(conversationId, {
|
|
3376
3454
|
id: id("status"),
|