@cortexkit/opencode-anthropic-auth 2.2.8 → 2.3.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/cli.js +43 -42
- package/dist/index-2jsr8wvj.js +43 -0
- package/dist/index-46e2gk3c.js +2 -0
- package/dist/index-dpnhamzw.js +2 -0
- package/dist/index.js +1 -1
- package/dist/plugin-test-handles.d.ts +26 -0
- package/dist/sidebar-state.d.ts +6 -3
- package/dist/sidebar-state.js +1 -1
- package/dist/tui/claude-sidebar.d.ts +58 -0
- package/dist/tui/menu-status.d.ts +70 -0
- package/dist/tui-preferences.js +2 -2
- package/dist/v2/adapter.d.ts +8 -4
- package/dist/v2/server.js +10 -10
- package/dist/v2/tui.d.ts +12 -0
- package/dist/v2/tui.js +4 -5
- package/package.json +2 -2
- package/src/sidebar-state.ts +55 -46
- package/src/tui/claude-sidebar.tsx +155 -15
- package/src/tui/entry.mjs +13 -0
- package/src/tui/menu-status.tsx +300 -0
- package/src/tui-compiled/sidebar-state.ts +55 -46
- package/src/tui-compiled/tui/claude-sidebar.tsx +112 -12
- package/src/tui-compiled/tui/menu-status.tsx +388 -0
- package/src/tui-compiled/tui-preferences.ts +32 -3
- package/src/tui-preferences.ts +32 -3
- package/dist/index-0jd4jg49.js +0 -43
- package/dist/index-j6x60aqq.js +0 -2
- package/dist/index-nm4pjrt8.js +0 -2
package/src/sidebar-state.ts
CHANGED
|
@@ -30,7 +30,9 @@ export interface AccountQuota {
|
|
|
30
30
|
|
|
31
31
|
export interface SidebarAccountState {
|
|
32
32
|
id: string
|
|
33
|
-
|
|
33
|
+
// Absent, rather than present and `undefined`, when the account has no
|
|
34
|
+
// label: the OpenCode 2 RPC boundary refuses a value carrying `undefined`.
|
|
35
|
+
label?: string
|
|
34
36
|
quota: AccountQuota | null
|
|
35
37
|
enabled: boolean
|
|
36
38
|
stateLine?: string
|
|
@@ -91,7 +93,9 @@ export interface SidebarState {
|
|
|
91
93
|
refreshBackoffUntil?: number
|
|
92
94
|
}
|
|
93
95
|
fallbacks: SidebarAccountState[]
|
|
94
|
-
|
|
96
|
+
// Absent rather than `undefined`: `normalizeSidebarState` never writes the
|
|
97
|
+
// key, and the OpenCode 2 RPC boundary refuses a value carrying `undefined`.
|
|
98
|
+
activeId?: string
|
|
95
99
|
route: string
|
|
96
100
|
relay: { enabled: boolean; transport: string } | null
|
|
97
101
|
fastMode: boolean
|
|
@@ -146,7 +150,6 @@ export function getSidebarStateFile(): string {
|
|
|
146
150
|
export const DEFAULT_SIDEBAR_STATE: SidebarState = {
|
|
147
151
|
main: { quota: null },
|
|
148
152
|
fallbacks: [],
|
|
149
|
-
activeId: undefined,
|
|
150
153
|
route: 'main',
|
|
151
154
|
relay: null,
|
|
152
155
|
fastMode: false,
|
|
@@ -350,40 +353,46 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
350
353
|
}
|
|
351
354
|
if (typeof m.quotaBackedOff === 'boolean')
|
|
352
355
|
main.quotaBackedOff = m.quotaBackedOff
|
|
353
|
-
|
|
356
|
+
// Only finite numbers are display data: the OpenCode 2 RPC boundary
|
|
357
|
+
// refuses NaN and Infinity, so they are dropped like any wrong type.
|
|
358
|
+
if (isFiniteNumber(m.quotaBackoffUntil)) {
|
|
354
359
|
main.quotaBackoffUntil = m.quotaBackoffUntil
|
|
360
|
+
}
|
|
355
361
|
if (typeof m.refreshBackedOff === 'boolean')
|
|
356
362
|
main.refreshBackedOff = m.refreshBackedOff
|
|
357
|
-
if (
|
|
363
|
+
if (isFiniteNumber(m.refreshBackoffUntil)) {
|
|
358
364
|
main.refreshBackoffUntil = m.refreshBackoffUntil
|
|
365
|
+
}
|
|
359
366
|
}
|
|
360
367
|
|
|
361
368
|
const fallbacks: SidebarAccountState[] = Array.isArray(raw.fallbacks)
|
|
362
369
|
? raw.fallbacks
|
|
363
370
|
.filter(isRecord)
|
|
364
371
|
.filter((entry) => typeof entry.id === 'string')
|
|
365
|
-
.map(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
372
|
+
.map(
|
|
373
|
+
(entry): SidebarAccountState => ({
|
|
374
|
+
id: entry.id as string,
|
|
375
|
+
...(typeof entry.label === 'string' && { label: entry.label }),
|
|
376
|
+
quota: normalizeAccountQuota(entry.quota),
|
|
377
|
+
enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
|
|
378
|
+
...(typeof entry.stateLine === 'string' && {
|
|
379
|
+
stateLine: entry.stateLine,
|
|
380
|
+
}),
|
|
381
|
+
...(entry.quotaDated === true && { quotaDated: true }),
|
|
382
|
+
needsReauth:
|
|
383
|
+
typeof entry.needsReauth === 'boolean'
|
|
384
|
+
? entry.needsReauth
|
|
385
|
+
: false,
|
|
386
|
+
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
387
|
+
...(entry.vaultServed === true && { vaultServed: true }),
|
|
388
|
+
...(typeof entry.custodyState === 'string' && {
|
|
389
|
+
custodyState:
|
|
390
|
+
entry.custodyState as SidebarAccountState['custodyState'],
|
|
391
|
+
}),
|
|
392
|
+
...(typeof entry.tierLabel === 'string' &&
|
|
393
|
+
entry.tierLabel.trim() && { tierLabel: entry.tierLabel.trim() }),
|
|
381
394
|
}),
|
|
382
|
-
|
|
383
|
-
typeof entry.tierLabel === 'string' && entry.tierLabel.trim()
|
|
384
|
-
? entry.tierLabel.trim()
|
|
385
|
-
: undefined,
|
|
386
|
-
}))
|
|
395
|
+
)
|
|
387
396
|
: []
|
|
388
397
|
|
|
389
398
|
let relay: SidebarState['relay'] = null
|
|
@@ -400,11 +409,10 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
400
409
|
if (typeof ck.enabled === 'boolean') {
|
|
401
410
|
cacheKeep = {
|
|
402
411
|
enabled: ck.enabled,
|
|
403
|
-
|
|
404
|
-
trackedSessions
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
: undefined,
|
|
412
|
+
...(typeof ck.window === 'string' && { window: ck.window }),
|
|
413
|
+
...(isFiniteNumber(ck.trackedSessions) && {
|
|
414
|
+
trackedSessions: ck.trackedSessions,
|
|
415
|
+
}),
|
|
408
416
|
}
|
|
409
417
|
}
|
|
410
418
|
}
|
|
@@ -432,23 +440,23 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
432
440
|
mode: recovery.mode,
|
|
433
441
|
remaining: Math.max(0, Math.floor(recovery.remaining)),
|
|
434
442
|
changedAt: recovery.changedAt,
|
|
435
|
-
requestedModelId
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
? recovery.targetModelId
|
|
442
|
-
: undefined,
|
|
443
|
+
...(typeof recovery.requestedModelId === 'string' && {
|
|
444
|
+
requestedModelId: recovery.requestedModelId,
|
|
445
|
+
}),
|
|
446
|
+
...(typeof recovery.targetModelId === 'string' && {
|
|
447
|
+
targetModelId: recovery.targetModelId,
|
|
448
|
+
}),
|
|
443
449
|
},
|
|
444
450
|
]
|
|
445
451
|
})
|
|
446
452
|
: []
|
|
447
453
|
|
|
454
|
+
const prime = normalizePrimeSection(raw.prime)
|
|
455
|
+
|
|
448
456
|
return {
|
|
449
457
|
main,
|
|
450
458
|
fallbacks,
|
|
451
|
-
|
|
459
|
+
...(typeof raw.activeId === 'string' && { activeId: raw.activeId }),
|
|
452
460
|
route:
|
|
453
461
|
typeof raw.route === 'string' ? raw.route : DEFAULT_SIDEBAR_STATE.route,
|
|
454
462
|
relay,
|
|
@@ -457,9 +465,9 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
457
465
|
? raw.fastMode
|
|
458
466
|
: DEFAULT_SIDEBAR_STATE.fastMode,
|
|
459
467
|
cacheKeep,
|
|
460
|
-
prime
|
|
461
|
-
fableRecoveries
|
|
462
|
-
lastUpdated:
|
|
468
|
+
...(prime && { prime }),
|
|
469
|
+
...(fableRecoveries.length > 0 && { fableRecoveries }),
|
|
470
|
+
lastUpdated: isFiniteNumber(raw.lastUpdated) ? raw.lastUpdated : 0,
|
|
463
471
|
}
|
|
464
472
|
}
|
|
465
473
|
|
|
@@ -692,9 +700,10 @@ export interface SidebarStateWriteOptions {
|
|
|
692
700
|
})
|
|
693
701
|
| undefined
|
|
694
702
|
>
|
|
695
|
-
onRoutingResolved?: (
|
|
696
|
-
|
|
697
|
-
|
|
703
|
+
onRoutingResolved?: (routing: {
|
|
704
|
+
activeId: string | undefined
|
|
705
|
+
route: string
|
|
706
|
+
}) => void
|
|
698
707
|
}
|
|
699
708
|
|
|
700
709
|
export async function setSidebarState(
|
|
@@ -601,6 +601,12 @@ export interface SidebarController {
|
|
|
601
601
|
toggleCollapsed: () => void
|
|
602
602
|
/** Stops the preference watcher; OpenCode 1 keeps it for the process. */
|
|
603
603
|
dispose: () => void
|
|
604
|
+
/**
|
|
605
|
+
* However many refreshes in a row have failed, a retry never waits longer
|
|
606
|
+
* than this. Every attempt costs the server a full runtime read, so a
|
|
607
|
+
* failing sidebar must not keep trying at the poll rate forever.
|
|
608
|
+
*/
|
|
609
|
+
retryMaxMs: number
|
|
604
610
|
}
|
|
605
611
|
|
|
606
612
|
// The TUI may unmount and remount sidebar_content when the user switches
|
|
@@ -658,7 +664,7 @@ export function createSidebarController(
|
|
|
658
664
|
}
|
|
659
665
|
}
|
|
660
666
|
|
|
661
|
-
return { prefs, collapsed, toggleCollapsed, dispose }
|
|
667
|
+
return { prefs, collapsed, toggleCollapsed, dispose, retryMaxMs: 30_000 }
|
|
662
668
|
}
|
|
663
669
|
|
|
664
670
|
/** The sidebar panel for one session; the host owns polling and the theme. */
|
|
@@ -1070,40 +1076,174 @@ export type ReadSidebarState = (
|
|
|
1070
1076
|
signal: AbortSignal,
|
|
1071
1077
|
) => Promise<SidebarState>
|
|
1072
1078
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* What the sidebar reads and draws with, independent of the host chrome.
|
|
1081
|
+
* The timer functions default to the global ones; a test passes its own so it
|
|
1082
|
+
* can drive the refresh schedule without touching the shared globals.
|
|
1083
|
+
*/
|
|
1084
|
+
export interface HostSidebarPropsState {
|
|
1075
1085
|
read: ReadSidebarState
|
|
1076
1086
|
controller: SidebarController
|
|
1077
1087
|
sessionId: string
|
|
1078
|
-
|
|
1079
|
-
|
|
1088
|
+
setTimeoutImpl?: typeof globalThis.setTimeout
|
|
1089
|
+
/** Clears a handle `setTimeoutImpl` returned, whatever type that was. */
|
|
1090
|
+
clearTimeoutImpl?: (handle: unknown) => void
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
interface HostSidebarProps extends HostSidebarPropsState {
|
|
1094
|
+
host: ClaudeSidebarHost
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** The refresh loop's own timers, so a test can drive its schedule. */
|
|
1098
|
+
export interface SidebarScheduleTimers {
|
|
1099
|
+
setTimeoutImpl?: typeof globalThis.setTimeout
|
|
1100
|
+
clearTimeoutImpl?: (handle: unknown) => void
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Delay before the next refresh after `failures` failed reads in a row, each
|
|
1105
|
+
* one longer than the last until the controller's limit. A successful read
|
|
1106
|
+
* resets the count, so a healthy server is polled at the configured rate. A
|
|
1107
|
+
* failed read still costs the server a full runtime read, so retrying at the
|
|
1108
|
+
* poll rate keeps that cost alive for as long as the failure lasts.
|
|
1109
|
+
*/
|
|
1110
|
+
export function sidebarRetryDelay(
|
|
1111
|
+
pollMs: number,
|
|
1112
|
+
failures: number,
|
|
1113
|
+
retryMaxMs: number,
|
|
1114
|
+
): number {
|
|
1115
|
+
if (failures <= 0) return pollMs
|
|
1116
|
+
return Math.min(pollMs * 2 ** failures, retryMaxMs)
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/** Starts and stops the refresh schedule the sidebar lives on. */
|
|
1120
|
+
export interface SidebarSchedule {
|
|
1121
|
+
/**
|
|
1122
|
+
* Reads once and then keeps the retry timer running. Safe to call more than
|
|
1123
|
+
* once: a running schedule is left alone.
|
|
1124
|
+
*/
|
|
1125
|
+
start: () => void
|
|
1126
|
+
/**
|
|
1127
|
+
* Stops the schedule for good: clears the waiting timer and aborts a read
|
|
1128
|
+
* still in flight, so it cannot queue another one. The item that unmounts a
|
|
1129
|
+
* sidebar calls this; without it, a failing refresh would keep polling the
|
|
1130
|
+
* server from a sidebar nobody is looking at.
|
|
1131
|
+
*/
|
|
1132
|
+
stop: () => void
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// One read on start, then a timer that backs off after failures and resets
|
|
1136
|
+
// after a success. Lives outside Solid: the component drives it from an
|
|
1137
|
+
// effect, and tests drive it with their own timers.
|
|
1138
|
+
export function createSidebarSchedule(
|
|
1139
|
+
options: {
|
|
1140
|
+
read: ReadSidebarState
|
|
1141
|
+
sessionId: string
|
|
1142
|
+
pollRate: () => { pollMs: number; retryMaxMs: number }
|
|
1143
|
+
/** Receives each answer that differs from the one before it. */
|
|
1144
|
+
onState: (state: SidebarState) => void
|
|
1145
|
+
} & SidebarScheduleTimers,
|
|
1146
|
+
): SidebarSchedule {
|
|
1147
|
+
const {
|
|
1148
|
+
read,
|
|
1149
|
+
sessionId,
|
|
1150
|
+
pollRate,
|
|
1151
|
+
onState,
|
|
1152
|
+
setTimeoutImpl = setTimeout,
|
|
1153
|
+
clearTimeoutImpl = clearTimeout as (handle: unknown) => void,
|
|
1154
|
+
} = options
|
|
1080
1155
|
const abort = new AbortController()
|
|
1156
|
+
// The timer waiting to run. Every schedule replaces it, so stopping always
|
|
1157
|
+
// clears the one that is actually queued.
|
|
1158
|
+
let timer: unknown
|
|
1159
|
+
let running = false
|
|
1081
1160
|
let inFlight = false
|
|
1082
1161
|
let shown = ''
|
|
1083
|
-
|
|
1084
|
-
|
|
1162
|
+
let failures = 0
|
|
1163
|
+
// `undefined` means the previous read was still running, which is not a
|
|
1164
|
+
// failure: counting it as one would back off before the server answered.
|
|
1165
|
+
const refresh = async (): Promise<boolean | undefined> => {
|
|
1166
|
+
if (inFlight || abort.signal.aborted) return undefined
|
|
1085
1167
|
inFlight = true
|
|
1086
1168
|
try {
|
|
1087
|
-
const next = await
|
|
1088
|
-
// Every answer is stamped
|
|
1089
|
-
// Countdowns and ages advance on the sidebar's own clock.
|
|
1169
|
+
const next = await read(sessionId, abort.signal)
|
|
1170
|
+
// Every answer is stamped by the server; redraw only when its content
|
|
1171
|
+
// changed. Countdowns and ages advance on the sidebar's own clock.
|
|
1090
1172
|
const content = JSON.stringify({ ...next, lastUpdated: 0 })
|
|
1091
1173
|
if (!abort.signal.aborted && content !== shown) {
|
|
1092
1174
|
shown = content
|
|
1093
|
-
|
|
1175
|
+
onState(next)
|
|
1094
1176
|
}
|
|
1177
|
+
return true
|
|
1095
1178
|
} catch {
|
|
1096
1179
|
// Keep the last state; an older server without `sidebar` keeps waiting.
|
|
1180
|
+
return false
|
|
1097
1181
|
} finally {
|
|
1098
1182
|
inFlight = false
|
|
1099
1183
|
}
|
|
1100
1184
|
}
|
|
1185
|
+
const schedule = () => {
|
|
1186
|
+
if (!running) return
|
|
1187
|
+
const { pollMs, retryMaxMs } = pollRate()
|
|
1188
|
+
const wait = sidebarRetryDelay(pollMs, failures, retryMaxMs)
|
|
1189
|
+
timer = setTimeoutImpl(async () => {
|
|
1190
|
+
timer = undefined
|
|
1191
|
+
if (!running) return
|
|
1192
|
+
const ok = await refresh()
|
|
1193
|
+
if (!running) return
|
|
1194
|
+
if (ok === true) failures = 0
|
|
1195
|
+
else if (ok === false) failures += 1
|
|
1196
|
+
schedule()
|
|
1197
|
+
}, wait)
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
start() {
|
|
1201
|
+
if (running || abort.signal.aborted) return
|
|
1202
|
+
running = true
|
|
1203
|
+
// The first read happens here rather than in the timer, so a caller that
|
|
1204
|
+
// starts before any draw still has an answer waiting for it. The loop
|
|
1205
|
+
// then continues on the retry timer below.
|
|
1206
|
+
void refresh().then((ok) => {
|
|
1207
|
+
if (running && ok === false) failures += 1
|
|
1208
|
+
})
|
|
1209
|
+
schedule()
|
|
1210
|
+
},
|
|
1211
|
+
stop() {
|
|
1212
|
+
running = false
|
|
1213
|
+
if (timer !== undefined) clearTimeoutImpl(timer)
|
|
1214
|
+
timer = undefined
|
|
1215
|
+
abort.abort()
|
|
1216
|
+
},
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// The OpenCode 2 sidebar's state: its refresh schedule, redrawn through a
|
|
1221
|
+
// signal the component renders from.
|
|
1222
|
+
export function useHostSidebarState(
|
|
1223
|
+
props: HostSidebarPropsState,
|
|
1224
|
+
): () => SidebarState {
|
|
1225
|
+
const [state, setState] = createSignal<SidebarState>(DEFAULT_SIDEBAR_STATE)
|
|
1101
1226
|
createEffect(() => {
|
|
1102
|
-
|
|
1103
|
-
|
|
1227
|
+
// Reading the poll rate here re-runs this effect when it changes. A
|
|
1228
|
+
// stopped schedule never restarts, so each run builds a fresh one and the
|
|
1229
|
+
// cleanup stops the run it belongs to.
|
|
1230
|
+
const pollMs = props.controller.prefs().pollMs
|
|
1231
|
+
const schedule = createSidebarSchedule({
|
|
1232
|
+
read: props.read,
|
|
1233
|
+
sessionId: props.sessionId,
|
|
1234
|
+
pollRate: () => ({ pollMs, retryMaxMs: props.controller.retryMaxMs }),
|
|
1235
|
+
onState: setState,
|
|
1236
|
+
setTimeoutImpl: props.setTimeoutImpl,
|
|
1237
|
+
clearTimeoutImpl: props.clearTimeoutImpl,
|
|
1238
|
+
})
|
|
1239
|
+
schedule.start()
|
|
1240
|
+
onCleanup(() => schedule.stop())
|
|
1104
1241
|
})
|
|
1105
|
-
|
|
1106
|
-
|
|
1242
|
+
return state
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function HostSidebar(props: HostSidebarProps) {
|
|
1246
|
+
const state = useHostSidebarState(props)
|
|
1107
1247
|
return (
|
|
1108
1248
|
<ClaudeSidebar
|
|
1109
1249
|
state={state}
|
package/src/tui/entry.mjs
CHANGED
|
@@ -59,6 +59,17 @@ async function loadSignInLink() {
|
|
|
59
59
|
return import('../tui-compiled/tui/sign-in-link.tsx')
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// The /claude menu's status view, loaded the same way as the sidebar.
|
|
63
|
+
async function loadMenuStatus() {
|
|
64
|
+
try {
|
|
65
|
+
await import(runtimeProbe)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (!isMissingRuntimeRegistry(error)) throw error
|
|
68
|
+
return import('./menu-status.tsx')
|
|
69
|
+
}
|
|
70
|
+
return import('../tui-compiled/tui/menu-status.tsx')
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
let legacyModule
|
|
63
74
|
|
|
64
75
|
// OpenCode 1 reads id and tui from the default definition; OpenCode 2 reads
|
|
@@ -84,6 +95,8 @@ export const setup = async (context) => {
|
|
|
84
95
|
(await loadSidebar()).mountClaudeSidebar(ctx, read),
|
|
85
96
|
showSignInLink: async (ctx, url, signal) =>
|
|
86
97
|
(await loadSignInLink()).showSignInLinkDialog(ctx, url, signal),
|
|
98
|
+
showStatus: async (ctx, view, signal) =>
|
|
99
|
+
(await loadMenuStatus()).showMenuStatusDialog(ctx, view, signal),
|
|
87
100
|
})
|
|
88
101
|
}
|
|
89
102
|
|