@1presence/bridge 0.58.0 → 0.60.0
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/claude.js +24 -1
- package/dist/config.js +7 -5
- package/dist/index.js +63 -6
- package/package.json +1 -1
package/dist/claude.js
CHANGED
|
@@ -41,6 +41,16 @@ import { getBridgeModel } from './config.js';
|
|
|
41
41
|
// Track whether we've already announced the model this process — printing it
|
|
42
42
|
// per-spawn is noisy; once on startup is what the user actually wants to see.
|
|
43
43
|
let modelAnnounced = false;
|
|
44
|
+
// Claude Code engine version driving the turns, captured from the SDK init
|
|
45
|
+
// event (`claude_code_version`). Constant per process, so cached module-level
|
|
46
|
+
// and read by the after-turn status line — mirrors the `v2.x.x` segment of the
|
|
47
|
+
// local statusline. Null until the first turn's init event arrives.
|
|
48
|
+
let cliVersion = null;
|
|
49
|
+
export function getCliVersion() { return cliVersion; }
|
|
50
|
+
const rateLimitWindows = new Map();
|
|
51
|
+
export function getRateLimitWindow(type) {
|
|
52
|
+
return rateLimitWindows.get(type);
|
|
53
|
+
}
|
|
44
54
|
// Verbose flag — when set via --verbose, log full tool inputs and outputs
|
|
45
55
|
// PLUS the entire system prompt. Great for prompt debugging, noisy for
|
|
46
56
|
// message debugging (the prompt dump buries the conversation).
|
|
@@ -384,10 +394,13 @@ export function spawnClaude(params) {
|
|
|
384
394
|
const keySource = event['apiKeySource'];
|
|
385
395
|
const model = event['model'];
|
|
386
396
|
const sid = event['session_id'];
|
|
397
|
+
const ccv = event['claude_code_version'];
|
|
387
398
|
if (sid)
|
|
388
399
|
currentSessionId = sid;
|
|
389
400
|
if (model)
|
|
390
401
|
extractedModel = model;
|
|
402
|
+
if (ccv)
|
|
403
|
+
cliVersion = ccv;
|
|
391
404
|
if (!modelAnnounced) {
|
|
392
405
|
const source = keySource === 'none' || !keySource ? 'claude.ai subscription' : keySource;
|
|
393
406
|
const pin = getBridgeModel() ? ' (selected at startup)' : '';
|
|
@@ -647,7 +660,7 @@ export function spawnClaude(params) {
|
|
|
647
660
|
const subtype = m.subtype;
|
|
648
661
|
if (subtype === 'init') {
|
|
649
662
|
const init = m;
|
|
650
|
-
const event = { type: 'system', subtype: 'init', model: init.model, apiKeySource: init.apiKeySource };
|
|
663
|
+
const event = { type: 'system', subtype: 'init', model: init.model, apiKeySource: init.apiKeySource, claude_code_version: init.claude_code_version };
|
|
651
664
|
if (handleEvent(event))
|
|
652
665
|
onEvent(event);
|
|
653
666
|
}
|
|
@@ -739,6 +752,16 @@ export function spawnClaude(params) {
|
|
|
739
752
|
// a notice when the user is actually warned or throttled.
|
|
740
753
|
const info = m.rate_limit_info;
|
|
741
754
|
const status = info?.status;
|
|
755
|
+
// Retain the latest reading for this window so the after-turn status
|
|
756
|
+
// line can render the full 5h/7d picture even though each event only
|
|
757
|
+
// carries one window. utilization is a 0–100 used-percentage.
|
|
758
|
+
if (info?.rateLimitType && typeof info.utilization === 'number') {
|
|
759
|
+
rateLimitWindows.set(info.rateLimitType, {
|
|
760
|
+
utilization: info.utilization,
|
|
761
|
+
resetsAt: info.resetsAt,
|
|
762
|
+
status: status ?? 'allowed',
|
|
763
|
+
});
|
|
764
|
+
}
|
|
742
765
|
if (status === 'allowed_warning' || status === 'rejected') {
|
|
743
766
|
// Admin-only ephemeral notice — jargon is fine in Local Mode.
|
|
744
767
|
onNotice?.(formatRateLimitNotice(status, info?.resetsAt));
|
package/dist/config.js
CHANGED
|
@@ -79,8 +79,8 @@ function detectClaudeDefaultModel() {
|
|
|
79
79
|
// NB on `[1m]` (1M-context Opus): the bridge always runs on the user's claude.ai
|
|
80
80
|
// subscription (it strips ANTHROPIC_API_KEY — see claude.ts), and it is UNCONFIRMED
|
|
81
81
|
// whether the 1M context window (a tier-gated API beta) is servable on subscription
|
|
82
|
-
// auth. It is
|
|
83
|
-
//
|
|
82
|
+
// auth. It is the default (see DEFAULT_OPTION_NUM below); if it proves unservable on
|
|
83
|
+
// a given plan, the user can arrow to option 2 for the plain variant. (An earlier
|
|
84
84
|
// every-turn `400 / "unknown"` was first blamed on this but was actually a tool-schema
|
|
85
85
|
// bug that hit every model — see vault/Bugs.md.) `num` must stay contiguous 1..N in
|
|
86
86
|
// array order — the jump-key handler maps a typed digit `n` to `idx = n - 1`.
|
|
@@ -93,9 +93,11 @@ const MODEL_OPTIONS = [
|
|
|
93
93
|
{ num: 6, model: 'claude-haiku-4-5', label: 'claude-haiku-4-5' },
|
|
94
94
|
];
|
|
95
95
|
const PROMPT_TIMEOUT_MS = 10_000;
|
|
96
|
-
// Default to
|
|
97
|
-
//
|
|
98
|
-
|
|
96
|
+
// Default to the 1M-context Opus (`claude-opus-4-8[1m]`). The `[1m]` window is a
|
|
97
|
+
// tier-gated API beta whose availability on subscription auth is unconfirmed (see
|
|
98
|
+
// the NB above), but it's our preferred default — anyone can arrow to option 2 for
|
|
99
|
+
// the plain variant if `[1m]` proves unservable on their plan.
|
|
100
|
+
const DEFAULT_OPTION_NUM = 3;
|
|
99
101
|
function promptForModel(defaultModel) {
|
|
100
102
|
return new Promise((resolve) => {
|
|
101
103
|
const initialIdx = Math.max(0, MODEL_OPTIONS.findIndex((o) => o.num === DEFAULT_OPTION_NUM));
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
9
9
|
import { getValidAuth, ensureFreshToken, forceRefreshToken, isTokenValid, AuthCancelledError } from './auth.js';
|
|
10
|
-
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS } from './claude.js';
|
|
10
|
+
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS, getCliVersion, getRateLimitWindow } from './claude.js';
|
|
11
11
|
import { ensureModelChoice } from './config.js';
|
|
12
12
|
import { checkAndUpdate } from './update.js';
|
|
13
13
|
import { makeBridgeAccumulator, postSaveTurn } from './accumulator.js';
|
|
@@ -101,6 +101,51 @@ function contextWindowFor(model) {
|
|
|
101
101
|
}
|
|
102
102
|
return DEFAULT_CONTEXT_WINDOW;
|
|
103
103
|
}
|
|
104
|
+
// ─── Rate-limit window rendering ──────────────────────────────────────────────
|
|
105
|
+
// Mirrors the local Claude Code statusline's ⏱️ line: a 10-cell bar + used % +
|
|
106
|
+
// reset time per subscription window. Data comes from the SDK's rate_limit_event
|
|
107
|
+
// (captured across turns in claude.ts), so no auth/header probe is needed.
|
|
108
|
+
// 7-day usage may arrive as the generic `seven_day` window or a model-specific
|
|
109
|
+
// variant; surface whichever the SDK has reported, preferring the generic one.
|
|
110
|
+
function sevenDayWindow() {
|
|
111
|
+
return getRateLimitWindow('seven_day')
|
|
112
|
+
?? getRateLimitWindow('seven_day_opus')
|
|
113
|
+
?? getRateLimitWindow('seven_day_sonnet');
|
|
114
|
+
}
|
|
115
|
+
function makeBar(pct) {
|
|
116
|
+
const width = 10;
|
|
117
|
+
const filled = Math.max(0, Math.min(width, Math.floor((pct * width) / 100)));
|
|
118
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
119
|
+
}
|
|
120
|
+
// Green < 70%, yellow 70–89%, red ≥ 90% — same thresholds as the statusline.
|
|
121
|
+
function rlColor(pct) {
|
|
122
|
+
return pct >= 90 ? '31' : pct >= 70 ? '33' : '32';
|
|
123
|
+
}
|
|
124
|
+
// "12:20a.m." within a day, "Mon 11:00p.m." when the reset is > 24h out.
|
|
125
|
+
function formatReset(resetsAt) {
|
|
126
|
+
if (!resetsAt)
|
|
127
|
+
return '';
|
|
128
|
+
const ms = resetsAt < 1e12 ? resetsAt * 1000 : resetsAt;
|
|
129
|
+
const d = new Date(ms);
|
|
130
|
+
const h24 = d.getHours();
|
|
131
|
+
const h12 = h24 % 12 === 0 ? 12 : h24 % 12;
|
|
132
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
133
|
+
const time = `${h12}:${mm}${h24 < 12 ? 'a.m.' : 'p.m.'}`;
|
|
134
|
+
if (ms - Date.now() > 86_400_000) {
|
|
135
|
+
const wd = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getDay()];
|
|
136
|
+
return `${wd} ${time}`;
|
|
137
|
+
}
|
|
138
|
+
return time;
|
|
139
|
+
}
|
|
140
|
+
// One coloured "5h ██░░░░░░░░ 20% resets 12:20a.m." segment; null if unknown.
|
|
141
|
+
function formatWindow(label, w) {
|
|
142
|
+
if (!w)
|
|
143
|
+
return null;
|
|
144
|
+
const pct = Math.round(w.utilization);
|
|
145
|
+
const reset = formatReset(w.resetsAt);
|
|
146
|
+
const seg = `${label} ${makeBar(pct)} ${pct}%${reset ? ` resets ${reset}` : ''}`;
|
|
147
|
+
return paint(rlColor(pct), seg);
|
|
148
|
+
}
|
|
104
149
|
// ─── System prompt fetch ──────────────────────────────────────────────────────
|
|
105
150
|
// Pulls the fully-built system prompt from agent-api (via gateway proxy).
|
|
106
151
|
// This MUST match the hosted runtime exactly — STATIC_SYSTEM_PROMPT + dynamic
|
|
@@ -436,14 +481,26 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
436
481
|
parts.push(costStr);
|
|
437
482
|
const suffix = ` ${parts.join(' ')}`;
|
|
438
483
|
console.log(`[${new Date().toLocaleTimeString()}] ✓ done${suffix}`);
|
|
439
|
-
// Status-bar line, mirroring local Claude Code:
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
484
|
+
// Status-bar line, mirroring the local Claude Code statusline:
|
|
485
|
+
// 🤖 model v<version> | 🧠 context% | 💰 cost
|
|
486
|
+
// ⏱️ 5h <bar> n% resets … | 7d <bar> n% resets …
|
|
487
|
+
// Dimmed and indented so it groups under the done line without competing
|
|
488
|
+
// with it. The cost segment falls back to "plan usage" whenever the running
|
|
489
|
+
// total is 0 (the subscription case). The ⏱️ window line is only printed
|
|
490
|
+
// once the SDK has reported at least one window this process.
|
|
443
491
|
sessionCostUsd += costUsd;
|
|
444
492
|
const ctxPct = Math.max(0, Math.min(100, Math.round((contextTokens / contextWindowFor(model)) * 100)));
|
|
445
493
|
const costSeg = sessionCostUsd > 0 ? `$${sessionCostUsd.toFixed(2)} session` : 'plan usage';
|
|
446
|
-
|
|
494
|
+
const ver = getCliVersion();
|
|
495
|
+
const verSeg = ver ? ` v${ver}` : '';
|
|
496
|
+
console.log(paint('90', ` 🤖 ${friendlyModelName(model)}${verSeg} | 🧠 ${ctxPct}% | 💰 ${costSeg}`));
|
|
497
|
+
const windows = [
|
|
498
|
+
formatWindow('5h', getRateLimitWindow('five_hour')),
|
|
499
|
+
formatWindow('7d', sevenDayWindow()),
|
|
500
|
+
].filter((s) => s !== null);
|
|
501
|
+
if (windows.length > 0) {
|
|
502
|
+
console.log(` ${paint('90', '⏱️')} ${windows.join(paint('90', ' | '))}`);
|
|
503
|
+
}
|
|
447
504
|
const mapped = toBridgeUsage(usage);
|
|
448
505
|
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
449
506
|
currentWs.send(JSON.stringify({
|