@cortexkit/opencode-anthropic-auth 1.10.3 → 1.12.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.
@@ -1,4 +1,5 @@
1
1
  export type CommandModalName =
2
+ | 'claude-account'
2
3
  | 'claude-cache'
3
4
  | 'claude-cachekeep'
4
5
  | 'claude-quota'
@@ -6,6 +7,7 @@ export type CommandModalName =
6
7
  | 'claude-fast'
7
8
  | 'claude-routing'
8
9
  | 'claude-killswitch'
10
+ | 'claude-logging'
9
11
 
10
12
  export interface OpenDialogPayload {
11
13
  command: CommandModalName
@@ -23,6 +25,7 @@ export interface RpcNotification {
23
25
  export interface ApplyRequest {
24
26
  command: CommandModalName
25
27
  arguments: string
28
+ sessionId?: string
26
29
  }
27
30
 
28
31
  export interface ApplyResult {
@@ -13,8 +13,9 @@ async function call<T>(
13
13
  dir: string,
14
14
  method: string,
15
15
  params: Record<string, unknown>,
16
+ expectedPid?: number,
16
17
  ): Promise<T | null> {
17
- const entry = await discoverPortFile(dir)
18
+ const entry = await discoverPortFile(dir, expectedPid)
18
19
  if (!entry) return null
19
20
  const controller = new AbortController()
20
21
  const timer = setTimeout(() => controller.abort(), 2_000)
@@ -37,18 +38,24 @@ async function call<T>(
37
38
  }
38
39
  }
39
40
 
40
- export function createRpcClient(dir: string): RpcClient {
41
+ export function createRpcClient(dir: string, expectedPid?: number): RpcClient {
41
42
  return {
42
43
  async pending(lastReceivedId, sessionId) {
43
44
  const out = await call<{ messages: RpcNotification[] }>(
44
45
  dir,
45
46
  'pending-notifications',
46
47
  { lastReceivedId, sessionId },
48
+ expectedPid,
47
49
  )
48
50
  return out?.messages ?? []
49
51
  },
50
52
  async apply(request) {
51
- const out = await call<ApplyResult>(dir, 'apply', { ...request })
53
+ const out = await call<ApplyResult>(
54
+ dir,
55
+ 'apply',
56
+ { ...request },
57
+ expectedPid,
58
+ )
52
59
  return out ?? { text: 'apply failed', knobs: {} }
53
60
  },
54
61
  }
@@ -1,13 +1,13 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { homedir, tmpdir } from 'node:os'
3
- import { join } from 'node:path'
3
+ import { join, resolve } from 'node:path'
4
4
 
5
5
  const RPC_DIR_ENV = 'OPENCODE_ANTHROPIC_AUTH_RPC_DIR'
6
6
 
7
7
  // Both processes must resolve the SAME dir from the SAME project directory.
8
8
  export function getRpcDir(projectDirectory: string): string {
9
9
  const override = process.env[RPC_DIR_ENV]?.trim()
10
- if (override) return override
10
+ if (override) return resolve(projectDirectory, override)
11
11
  const hash = createHash('sha256')
12
12
  .update(projectDirectory)
13
13
  .digest('hex')
@@ -86,9 +86,11 @@ export async function startRpcServer(
86
86
  }
87
87
  return json(404, { error: 'unknown method' })
88
88
  } catch (error) {
89
- json(500, {
90
- error: error instanceof Error ? error.message : String(error),
91
- })
89
+ if (!res.headersSent && !res.writableEnded && !res.destroyed) {
90
+ json(500, {
91
+ error: error instanceof Error ? error.message : String(error),
92
+ })
93
+ }
92
94
  }
93
95
  }
94
96
 
@@ -14,6 +14,9 @@ export interface SidebarAccountState {
14
14
  label: string | undefined
15
15
  quota: AccountQuota | null
16
16
  enabled: boolean
17
+ // True when the account's refresh token is permanently dead (400
18
+ // invalid_grant) and it needs a re-login — distinct from a transient backoff.
19
+ needsReauth: boolean
17
20
  }
18
21
 
19
22
  export interface SidebarState {
@@ -58,24 +61,109 @@ export const DEFAULT_SIDEBAR_STATE: SidebarState = {
58
61
  fastMode: false,
59
62
  lastUpdated: 0,
60
63
  }
64
+ function isRecord(v: unknown): v is Record<string, unknown> {
65
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
66
+ }
67
+
68
+ export function normalizeSidebarState(raw: unknown): SidebarState {
69
+ if (!isRecord(raw)) return { ...DEFAULT_SIDEBAR_STATE }
70
+
71
+ const main: SidebarState['main'] = { quota: null }
72
+ if (isRecord(raw.main)) {
73
+ const m = raw.main
74
+ main.quota = isRecord(m.quota) ? (m.quota as AccountQuota) : null
75
+ if (typeof m.quotaBackedOff === 'boolean')
76
+ main.quotaBackedOff = m.quotaBackedOff
77
+ if (typeof m.quotaBackoffUntil === 'number')
78
+ main.quotaBackoffUntil = m.quotaBackoffUntil
79
+ if (typeof m.refreshBackedOff === 'boolean')
80
+ main.refreshBackedOff = m.refreshBackedOff
81
+ if (typeof m.refreshBackoffUntil === 'number')
82
+ main.refreshBackoffUntil = m.refreshBackoffUntil
83
+ }
84
+
85
+ const fallbacks: SidebarAccountState[] = Array.isArray(raw.fallbacks)
86
+ ? raw.fallbacks
87
+ .filter(isRecord)
88
+ .filter((entry) => typeof entry.id === 'string')
89
+ .map((entry) => ({
90
+ id: entry.id as string,
91
+ label: typeof entry.label === 'string' ? entry.label : undefined,
92
+ quota: isRecord(entry.quota) ? (entry.quota as AccountQuota) : null,
93
+ enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
94
+ needsReauth:
95
+ typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
96
+ }))
97
+ : []
98
+
99
+ let relay: SidebarState['relay'] = null
100
+ if (isRecord(raw.relay)) {
101
+ const r = raw.relay
102
+ if (typeof r.enabled === 'boolean' && typeof r.transport === 'string') {
103
+ relay = { enabled: r.enabled, transport: r.transport }
104
+ }
105
+ }
106
+
107
+ let cacheKeep: SidebarState['cacheKeep']
108
+ if (isRecord(raw.cacheKeep)) {
109
+ const ck = raw.cacheKeep
110
+ if (typeof ck.enabled === 'boolean') {
111
+ cacheKeep = {
112
+ enabled: ck.enabled,
113
+ window: typeof ck.window === 'string' ? ck.window : undefined,
114
+ trackedSessions:
115
+ typeof ck.trackedSessions === 'number'
116
+ ? ck.trackedSessions
117
+ : undefined,
118
+ }
119
+ }
120
+ }
121
+
122
+ return {
123
+ main,
124
+ fallbacks,
125
+ activeId: typeof raw.activeId === 'string' ? raw.activeId : undefined,
126
+ route:
127
+ typeof raw.route === 'string' ? raw.route : DEFAULT_SIDEBAR_STATE.route,
128
+ relay,
129
+ fastMode:
130
+ typeof raw.fastMode === 'boolean'
131
+ ? raw.fastMode
132
+ : DEFAULT_SIDEBAR_STATE.fastMode,
133
+ cacheKeep,
134
+ lastUpdated: typeof raw.lastUpdated === 'number' ? raw.lastUpdated : 0,
135
+ }
136
+ }
137
+
138
+ let writeChain: Promise<void> = Promise.resolve()
61
139
 
62
140
  export async function getSidebarState(): Promise<SidebarState> {
63
141
  try {
64
142
  const raw = await readFile(getSidebarStateFile(), 'utf8')
65
- return JSON.parse(raw) as SidebarState
143
+ return normalizeSidebarState(JSON.parse(raw))
66
144
  } catch {
67
145
  return DEFAULT_SIDEBAR_STATE
68
146
  }
69
147
  }
70
148
 
71
- export async function setSidebarState(state: SidebarState): Promise<void> {
72
- try {
73
- const stateFile = getSidebarStateFile()
74
- await mkdir(dirname(stateFile), { recursive: true })
75
- await writeFile(stateFile, JSON.stringify(state), 'utf8')
76
- } catch {
77
- // Best-effort — sidebar is non-critical
78
- }
149
+ export async function setSidebarState(
150
+ state: SidebarState,
151
+ stateFile = getSidebarStateFile(),
152
+ ): Promise<void> {
153
+ writeChain = writeChain
154
+ .then(async () => {
155
+ await mkdir(dirname(stateFile), { recursive: true })
156
+ await writeFile(stateFile, JSON.stringify(state), 'utf8')
157
+ })
158
+ .catch(() => {
159
+ // Best-effort — sidebar is non-critical
160
+ })
161
+
162
+ return writeChain
163
+ }
164
+
165
+ export async function drainSidebarWrites(): Promise<void> {
166
+ return writeChain
79
167
  }
80
168
 
81
169
  // Resolve the currently-active account from activeId for the collapsed sidebar
@@ -92,7 +180,7 @@ export function resolveActiveAccount(state: SidebarState): {
92
180
  // accounts out of state.fallbacks, so in normal operation every entry is
93
181
  // enabled. Kept so this pure helper stays correct for any caller and is
94
182
  // exercised directly by the unit tests.
95
- const fallback = state.fallbacks.find(
183
+ const fallback = state.fallbacks?.find(
96
184
  (account) => account.enabled && account.id === activeId,
97
185
  )
98
186
  if (fallback) {
@@ -103,7 +191,7 @@ export function resolveActiveAccount(state: SidebarState): {
103
191
  }
104
192
  }
105
193
  }
106
- return { id: 'main', name: 'main', quota: state.main.quota }
194
+ return { id: 'main', name: 'main', quota: state.main?.quota ?? null }
107
195
  }
108
196
 
109
197
  export function getCollapsedQuotaSummary(quota: AccountQuota | null): {