@1presence/bridge 0.55.0 → 0.57.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 +85 -11
- package/dist/config.js +12 -2
- package/package.json +1 -1
package/dist/claude.js
CHANGED
|
@@ -163,6 +163,28 @@ function describeCliFailure(apiErrorText, authFailure) {
|
|
|
163
163
|
}
|
|
164
164
|
return 'Local Mode stopped unexpectedly. Please try again.';
|
|
165
165
|
}
|
|
166
|
+
// Join every non-empty error fragment the SDK might carry (a `result` string,
|
|
167
|
+
// an `errors: string[]`, an error enum, a request_id, …) into one de-duplicated
|
|
168
|
+
// line. A 4xx/5xx surfaces its detail unpredictably: `result` rides on the
|
|
169
|
+
// success-shaped error result, `errors[]` on SDKResultError, the coarse bucket
|
|
170
|
+
// on `assistant.error` — so we gather from all of them rather than trusting one.
|
|
171
|
+
function joinErrorDetail(...parts) {
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const part of parts) {
|
|
175
|
+
for (const item of Array.isArray(part) ? part : [part]) {
|
|
176
|
+
const s = (typeof item === 'string' ? item : item == null ? '' : String(item)).trim();
|
|
177
|
+
if (s && !seen.has(s)) {
|
|
178
|
+
seen.add(s);
|
|
179
|
+
out.push(s);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return out.join(' | ');
|
|
184
|
+
}
|
|
185
|
+
// Lines the SDK/CLI writes to its own stderr that look like a failure — surfaced
|
|
186
|
+
// even outside verbose mode so a turn that dies upstream leaves a trail.
|
|
187
|
+
const SDK_STDERR_ERROR_RE = /\b(error|exception|fail(?:ed|ure)?|invalid|unauthor|forbidden|refus|denied|40[0-9]|429|5\d\d|overloaded|rate.?limit)\b/i;
|
|
166
188
|
/**
|
|
167
189
|
* Copy for an actionable rate-limit notice. The SDK emits `rate_limit_event`
|
|
168
190
|
* whenever rate-limit info CHANGES — including the routine `allowed` case on
|
|
@@ -492,14 +514,31 @@ export function spawnClaude(params) {
|
|
|
492
514
|
const status = event['api_error_status'];
|
|
493
515
|
if (status === 401 || status === 403)
|
|
494
516
|
sawAuthFailure = true;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
517
|
+
// Gather the reason from EVERY error-bearing field, not just `result`
|
|
518
|
+
// (empty on most 4xx/5xx). `errors[]` rides on SDKResultError. Fold into
|
|
519
|
+
// apiErrorText, keeping any coarse enum the assistant.error path set.
|
|
520
|
+
const detail = joinErrorDetail(event['result'], event['errors']);
|
|
521
|
+
apiErrorText = joinErrorDetail(apiErrorText, detail) || apiErrorText;
|
|
522
|
+
const subtype = event['subtype'] ? ` subtype=${event['subtype']}` : '';
|
|
498
523
|
// Operator visibility — a result-borne error would otherwise reach the
|
|
499
524
|
// user as a chat error with NOTHING in the bridge logs (the failure
|
|
500
525
|
// mode that made the empty-content `invalid_request` regression so hard
|
|
501
|
-
// to diagnose).
|
|
502
|
-
|
|
526
|
+
// to diagnose). Logged in ALL modes (not just verbose). Mechanical log
|
|
527
|
+
// only; no product logic.
|
|
528
|
+
process.stderr.write(paint(SECTION_COLORS.result, `[bridge] result error${status != null ? ` (${status})` : ''}${subtype}: ${apiErrorText || 'unknown'}`) + '\n');
|
|
529
|
+
// When no field carried a reason (the SDK bucketed it as bare "unknown"),
|
|
530
|
+
// dump the raw result object so nothing is silently swallowed — the only
|
|
531
|
+
// remaining place a clue could hide. Bounded so it can't flood the log.
|
|
532
|
+
if (!detail) {
|
|
533
|
+
let raw;
|
|
534
|
+
try {
|
|
535
|
+
raw = JSON.stringify(event);
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
raw = String(event);
|
|
539
|
+
}
|
|
540
|
+
process.stderr.write(paint(SECTION_COLORS.result, `[bridge] result raw: ${raw.slice(0, 2000)}`) + '\n');
|
|
541
|
+
}
|
|
503
542
|
}
|
|
504
543
|
}
|
|
505
544
|
return true;
|
|
@@ -571,8 +610,15 @@ export function spawnClaude(params) {
|
|
|
571
610
|
includePartialMessages: false, // whole messages, matching the old non-partial path
|
|
572
611
|
permissionMode: 'default',
|
|
573
612
|
env: safeEnv,
|
|
574
|
-
|
|
575
|
-
|
|
613
|
+
// Surface the SDK/CLI's own stderr. In verbose mode pass everything; in
|
|
614
|
+
// normal mode pass only error-looking lines so an upstream failure still
|
|
615
|
+
// leaves a trail (the SDK abstracts the raw API body into an enum, so its
|
|
616
|
+
// stderr is sometimes the only place the real reason appears).
|
|
617
|
+
stderr: (line) => {
|
|
618
|
+
const t = line.trim();
|
|
619
|
+
if (t && (verbose || SDK_STDERR_ERROR_RE.test(t)))
|
|
620
|
+
process.stderr.write(`[claude] ${t}\n`);
|
|
621
|
+
},
|
|
576
622
|
...(pinnedModel ? { model: pinnedModel } : {}),
|
|
577
623
|
};
|
|
578
624
|
const promptMessages = buildPromptMessages(history);
|
|
@@ -584,12 +630,21 @@ export function spawnClaude(params) {
|
|
|
584
630
|
continue;
|
|
585
631
|
switch (m.type) {
|
|
586
632
|
case 'system': {
|
|
587
|
-
|
|
633
|
+
const subtype = m.subtype;
|
|
634
|
+
if (subtype === 'init') {
|
|
588
635
|
const init = m;
|
|
589
636
|
const event = { type: 'system', subtype: 'init', model: init.model, apiKeySource: init.apiKeySource };
|
|
590
637
|
if (handleEvent(event))
|
|
591
638
|
onEvent(event);
|
|
592
639
|
}
|
|
640
|
+
else if (subtype === 'api_retry') {
|
|
641
|
+
// The SDK retries retryable API failures (5xx / overloaded / some
|
|
642
|
+
// 429s) before giving up. Log each attempt with its status + bucket
|
|
643
|
+
// so a turn that eventually dies after retries leaves a full trail —
|
|
644
|
+
// in all modes, not just verbose. Diagnostic only; never forwarded.
|
|
645
|
+
const r = m;
|
|
646
|
+
process.stderr.write(paint(SECTION_COLORS.result, `[bridge] api retry ${r.attempt ?? '?'}/${r.max_retries ?? '?'}${r.error_status != null ? ` (${r.error_status})` : ''}: ${r.error ?? 'unknown'}${r.retry_delay_ms != null ? `, next in ${r.retry_delay_ms}ms` : ''}`) + '\n');
|
|
647
|
+
}
|
|
593
648
|
break;
|
|
594
649
|
}
|
|
595
650
|
case 'assistant': {
|
|
@@ -599,9 +654,21 @@ export function spawnClaude(params) {
|
|
|
599
654
|
sawApiError = true;
|
|
600
655
|
if (am.error === 'authentication_failed' || am.error === 'oauth_org_not_allowed')
|
|
601
656
|
sawAuthFailure = true;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
657
|
+
// Pull any extra reason off the synthetic error message + the
|
|
658
|
+
// request_id (the handle to look the failure up upstream) so the
|
|
659
|
+
// log/chat error isn't just the coarse `unknown` bucket.
|
|
660
|
+
const msgText = Array.isArray(am.message?.['content'])
|
|
661
|
+
? am.message['content']
|
|
662
|
+
.filter((b) => b['type'] === 'text' && typeof b['text'] === 'string')
|
|
663
|
+
.map((b) => b['text'].trim())
|
|
664
|
+
.filter(Boolean)
|
|
665
|
+
.join(' ')
|
|
666
|
+
: '';
|
|
667
|
+
const rid = am.request_id ? `request_id=${am.request_id}` : '';
|
|
668
|
+
const full = joinErrorDetail(`API Error: ${am.error}`, msgText, rid);
|
|
669
|
+
if (!apiErrorText || /^API Error: \w+$/.test(apiErrorText))
|
|
670
|
+
apiErrorText = full;
|
|
671
|
+
process.stderr.write(paint(SECTION_COLORS.result, `[bridge] assistant error: ${joinErrorDetail(am.error, msgText, rid)}`) + '\n');
|
|
605
672
|
break;
|
|
606
673
|
}
|
|
607
674
|
const event = { type: 'assistant', message: am.message, error: am.error };
|
|
@@ -627,6 +694,7 @@ export function spawnClaude(params) {
|
|
|
627
694
|
is_error: rm['is_error'],
|
|
628
695
|
api_error_status: rm['api_error_status'],
|
|
629
696
|
result: rm['result'],
|
|
697
|
+
errors: rm['errors'], // string[] on SDKResultError — the reason on a non-success result
|
|
630
698
|
};
|
|
631
699
|
if (handleEvent(event))
|
|
632
700
|
onEvent(event);
|
|
@@ -670,6 +738,12 @@ export function spawnClaude(params) {
|
|
|
670
738
|
if (killedForViolation)
|
|
671
739
|
return;
|
|
672
740
|
const message = err?.message ?? String(err);
|
|
741
|
+
// Log the raw thrown error IN FULL before onError sanitises it for chat —
|
|
742
|
+
// describeCliFailure deliberately strips provider detail from the
|
|
743
|
+
// user-facing copy, so the operator log is the only place the full reason
|
|
744
|
+
// (incl. stack) survives. All modes, not just verbose.
|
|
745
|
+
const stack = err?.stack;
|
|
746
|
+
process.stderr.write(paint(SECTION_COLORS.result, `[bridge] query() threw: ${stack || message}`) + '\n');
|
|
673
747
|
if (/40[13]\b|unauthor|invalid (api key|authentication)|please run \/login/i.test(message)) {
|
|
674
748
|
sawAuthFailure = true;
|
|
675
749
|
}
|
package/dist/config.js
CHANGED
|
@@ -76,15 +76,25 @@ function detectClaudeDefaultModel() {
|
|
|
76
76
|
// The fixed menu — keep the option count small so the timeout default is easy
|
|
77
77
|
// to glance at. Option 1's `model: null` means "let Claude Code pick" (no
|
|
78
78
|
// `--model` flag passed to the subprocess).
|
|
79
|
+
// NB on `[1m]` (1M-context Opus): the bridge always runs on the user's claude.ai
|
|
80
|
+
// subscription (it strips ANTHROPIC_API_KEY — see claude.ts), and it is UNCONFIRMED
|
|
81
|
+
// whether the 1M context window (a tier-gated API beta) is servable on subscription
|
|
82
|
+
// auth. It is offered but is NOT the default — auto-selecting a maybe-unsupported
|
|
83
|
+
// variant is the risk we avoid; deliberately picking it to test is fine. (An earlier
|
|
84
|
+
// every-turn `400 / "unknown"` was first blamed on this but was actually a tool-schema
|
|
85
|
+
// bug that hit every model — see vault/Bugs.md.) `num` must stay contiguous 1..N in
|
|
86
|
+
// array order — the jump-key handler maps a typed digit `n` to `idx = n - 1`.
|
|
79
87
|
const MODEL_OPTIONS = [
|
|
80
88
|
{ num: 1, model: null, label: 'Use Claude Code default' },
|
|
81
|
-
{ num: 2, model: 'claude-opus-4-8
|
|
82
|
-
{ num: 3, model: 'claude-opus-4-8', label: 'claude-opus-4-8' },
|
|
89
|
+
{ num: 2, model: 'claude-opus-4-8', label: 'claude-opus-4-8' },
|
|
90
|
+
{ num: 3, model: 'claude-opus-4-8[1m]', label: 'claude-opus-4-8[1m] (1M context — experimental on subscription)' },
|
|
83
91
|
{ num: 4, model: 'claude-opus-4-7', label: 'claude-opus-4-7' },
|
|
84
92
|
{ num: 5, model: 'claude-sonnet-4-6', label: 'claude-sonnet-4-6' },
|
|
85
93
|
{ num: 6, model: 'claude-haiku-4-5', label: 'claude-haiku-4-5' },
|
|
86
94
|
];
|
|
87
95
|
const PROMPT_TIMEOUT_MS = 10_000;
|
|
96
|
+
// Default to plain claude-opus-4-8 (definitely servable on a subscription), not
|
|
97
|
+
// the experimental `[1m]` variant.
|
|
88
98
|
const DEFAULT_OPTION_NUM = 2;
|
|
89
99
|
function promptForModel(defaultModel) {
|
|
90
100
|
return new Promise((resolve) => {
|