@cortexkit/opencode-openai-auth 0.7.2 → 0.9.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.
Files changed (118) hide show
  1. package/README.md +9 -5
  2. package/dist/index-301y4qa1.js +2 -0
  3. package/dist/index-338z7171.js +211 -0
  4. package/dist/index-5y4vzdf8.js +2 -0
  5. package/dist/{index-zxqb5ydj.js → index-9ehhb051.js} +1 -1
  6. package/dist/{index-zg1x8sty.js → index-jwn4r3sw.js} +1 -1
  7. package/dist/{index-9jpc8yct.js → index-sdt2d2ra.js} +1 -1
  8. package/dist/index-w42s0g6m.js +2 -0
  9. package/dist/index.d.ts +213 -36
  10. package/dist/index.js +59 -154
  11. package/dist/internal-Y7QNVDJT.d.ts +908 -0
  12. package/dist/quota-normalize-3qffywcm.js +1 -0
  13. package/dist/rpc/port-file.d.ts +11 -9
  14. package/dist/rpc/port-file.js +1 -1
  15. package/dist/rpc/protocol.d.ts +2 -21
  16. package/dist/rpc/rpc-client.d.ts +7 -4
  17. package/dist/rpc/rpc-client.js +1 -1
  18. package/dist/rpc/rpc-dir.d.ts +7 -5
  19. package/dist/rpc/rpc-dir.js +1 -1
  20. package/dist/sidebar-state-B98sERYt.d.ts +219 -0
  21. package/dist/sidebar-state.d.ts +2 -171
  22. package/dist/sidebar-state.js +1 -1
  23. package/dist/{token-bvp0y166.js → token-9823y0pc.js} +1 -1
  24. package/dist/token-util-ypgz7rge.js +1 -0
  25. package/dist/tui-preferences.d.ts +27 -26
  26. package/dist/tui-preferences.js +1 -1
  27. package/dist/tui.d.ts +42 -19
  28. package/package.json +8 -14
  29. package/src/tui/entry.mjs +2 -2
  30. package/src/tui-compiled/{core/account-paths.ts → raw/core-account-paths.ts} +29 -18
  31. package/src/tui-compiled/raw/core-custody-state.ts +243 -0
  32. package/src/tui-compiled/raw/logger.ts +53 -0
  33. package/src/tui-compiled/{rpc/port-file.ts → raw/rpc-port-file.ts} +1 -1
  34. package/src/tui-compiled/raw/rpc-protocol.ts +12 -0
  35. package/src/{rpc/rpc-client.ts → tui-compiled/raw/rpc-rpc-client.ts} +2 -2
  36. package/src/{sidebar-state.ts → tui-compiled/raw/sidebar-state.ts} +142 -13
  37. package/src/{tui/command-dialogs.tsx → tui-compiled/raw/tui-command-dialogs.tsx} +94 -44
  38. package/src/{tui.tsx → tui-compiled/raw/tui.tsx} +149 -28
  39. package/src/{core/account-paths.ts → tui-compiled/runtime/core-account-paths.ts} +29 -18
  40. package/src/tui-compiled/runtime/core-custody-state.ts +243 -0
  41. package/src/tui-compiled/runtime/logger.ts +53 -0
  42. package/src/{rpc/port-file.ts → tui-compiled/runtime/rpc-port-file.ts} +1 -1
  43. package/src/tui-compiled/runtime/rpc-protocol.ts +12 -0
  44. package/src/tui-compiled/{rpc/rpc-client.ts → runtime/rpc-rpc-client.ts} +2 -2
  45. package/src/tui-compiled/{sidebar-state.ts → runtime/sidebar-state.ts} +142 -13
  46. package/src/tui-compiled/{tui/command-dialogs.tsx → runtime/tui-command-dialogs.tsx} +73 -37
  47. package/src/tui-compiled/{tui.tsx → runtime/tui.tsx} +251 -153
  48. package/src/tui-compiled/shared/internal.ts +6 -0
  49. package/src/{logger.ts → tui-compiled/shared/logger.ts} +91 -23
  50. package/src/tui-compiled/shared/paths.ts +33 -0
  51. package/src/{core → tui-compiled/shared}/refresh-file-lock.ts +7 -4
  52. package/dist/cli.d.ts +0 -2
  53. package/dist/cli.js +0 -15
  54. package/dist/codex-http.d.ts +0 -3
  55. package/dist/commands.d.ts +0 -88
  56. package/dist/config.d.ts +0 -47
  57. package/dist/core/account-paths.d.ts +0 -25
  58. package/dist/core/accounts.d.ts +0 -330
  59. package/dist/core/atomic-write.d.ts +0 -1
  60. package/dist/core/background-quota-refresh.d.ts +0 -46
  61. package/dist/core/backoff.d.ts +0 -53
  62. package/dist/core/cachekeep.d.ts +0 -118
  63. package/dist/core/oauth.d.ts +0 -138
  64. package/dist/core/provider.d.ts +0 -70
  65. package/dist/core/quota-manager.d.ts +0 -192
  66. package/dist/core/refresh-all-quota.d.ts +0 -86
  67. package/dist/core/refresh-file-lock.d.ts +0 -12
  68. package/dist/core/reset-credits.d.ts +0 -116
  69. package/dist/core/sticky-routing.d.ts +0 -43
  70. package/dist/dump.d.ts +0 -20
  71. package/dist/hosted-web-search.d.ts +0 -5
  72. package/dist/index-87145jkw.js +0 -2
  73. package/dist/index-8f1200qx.js +0 -6
  74. package/dist/index-cfjed1st.js +0 -89
  75. package/dist/index-nrvjr97q.js +0 -3
  76. package/dist/index-qaggaze1.js +0 -2
  77. package/dist/index-sdcpzbnk.js +0 -2
  78. package/dist/logger.d.ts +0 -22
  79. package/dist/model-costs.d.ts +0 -29
  80. package/dist/prompt-context.d.ts +0 -19
  81. package/dist/quota-normalize-dvwtmy7f.js +0 -1
  82. package/dist/quota-normalize.d.ts +0 -43
  83. package/dist/raw-ws-bun.d.ts +0 -40
  84. package/dist/raw-ws-node.d.ts +0 -37
  85. package/dist/raw-ws-upgrade.d.ts +0 -2
  86. package/dist/raw-ws.d.ts +0 -3
  87. package/dist/response-stream-error.d.ts +0 -12
  88. package/dist/rpc/notifications.d.ts +0 -5
  89. package/dist/rpc/rpc-server.d.ts +0 -17
  90. package/dist/token-util-jcj7k7pa.js +0 -1
  91. package/dist/tui/command-dialogs.d.ts +0 -21
  92. package/dist/util/error.d.ts +0 -1
  93. package/dist/util/open-url.d.ts +0 -6
  94. package/dist/util/proxy-env.d.ts +0 -4
  95. package/dist/util/record.d.ts +0 -1
  96. package/dist/util/stable-json.d.ts +0 -1
  97. package/dist/util/uuid-v7.d.ts +0 -1
  98. package/dist/version.d.ts +0 -1
  99. package/dist/ws-pool.d.ts +0 -57
  100. package/dist/ws.d.ts +0 -71
  101. package/src/rpc/notifications.ts +0 -54
  102. package/src/rpc/protocol.ts +0 -33
  103. package/src/rpc/rpc-server.ts +0 -172
  104. package/src/tui-compiled/core/refresh-file-lock.ts +0 -381
  105. package/src/tui-compiled/logger.ts +0 -196
  106. package/src/tui-compiled/rpc/protocol.ts +0 -33
  107. package/src/util/error.ts +0 -9
  108. package/src/util/open-url.ts +0 -30
  109. package/src/util/proxy-env.ts +0 -42
  110. package/src/util/record.ts +0 -3
  111. package/src/util/stable-json.ts +0 -15
  112. package/src/util/uuid-v7.ts +0 -18
  113. /package/src/{rpc/rpc-dir.ts → tui-compiled/raw/rpc-rpc-dir.ts} +0 -0
  114. /package/src/tui-compiled/{tui-preferences.ts → raw/tui-preferences.ts} +0 -0
  115. /package/src/tui-compiled/{rpc/rpc-dir.ts → runtime/rpc-rpc-dir.ts} +0 -0
  116. /package/src/{tui-preferences.ts → tui-compiled/runtime/tui-preferences.ts} +0 -0
  117. /package/src/tui-compiled/{util → shared/util}/error.ts +0 -0
  118. /package/src/tui-compiled/{util → shared/util}/open-url.ts +0 -0
package/dist/tui.d.ts CHANGED
@@ -1,35 +1,58 @@
1
- /** @jsxImportSource @opentui/solid */
2
- import type { TuiPluginModule } from '@opencode-ai/plugin/tui';
3
- import type { ApplyRequest, CommandModalName } from './rpc/protocol.js';
4
- import { type AccountQuota, type QuotaPacing, type QuotaWindow, type SidebarState } from './sidebar-state.js';
1
+ import { a as CommandModalName, r as ApplyRequest } from "./internal-Y7QNVDJT.js";
2
+ import { b as SidebarState, c as QuotaPacing, l as QuotaWindow, r as AccountQuota, x as SpendControlReading } from "./sidebar-state-B98sERYt.js";
3
+ import "./rpc/protocol.js";
4
+ import { TuiPluginModule } from "@opencode-ai/plugin/tui";
5
+ //#region src/tui.d.ts
5
6
  export declare function buildApplyRequest(command: CommandModalName, arguments_: string, sessionId?: string): ApplyRequest;
6
7
  type Tone = 'ok' | 'warn' | 'err' | 'muted' | 'accent' | 'text';
7
8
  export declare function formatResetIn(resetsAt: string | undefined): string;
8
9
  export interface QuotaDisplayRow {
9
- key: 'primary' | 'secondary';
10
- label: string;
11
- window: QuotaWindow;
12
- pacing: QuotaPacing | null;
10
+ key: 'primary' | 'secondary' | 'spendControl';
11
+ label: string;
12
+ labelWidth: number;
13
+ window: QuotaWindow;
14
+ pacing: QuotaPacing | null;
13
15
  }
14
- export declare function buildQuotaRowsForDisplay(quota: AccountQuota | null, now: number, pacingEnabled: boolean): QuotaDisplayRow[];
16
+ /**
17
+ * Every quota the sidebar will draw, in one list.
18
+ *
19
+ * The label column is shared by all of them, so the width has to be measured
20
+ * over this exact set — measuring a subset is what made stacked accounts
21
+ * disagree about where the bar starts. Named rather than inlined at each call
22
+ * site because the set is the part that can be got wrong, and a call site
23
+ * cannot be tested without the host's rendering runtime.
24
+ */
25
+ export declare function renderedQuotas(state: {
26
+ main?: {
27
+ quota?: AccountQuota | null;
28
+ } | null;
29
+ fallbacks?: ReadonlyArray<{
30
+ enabled?: boolean;
31
+ quota?: AccountQuota | null;
32
+ }>;
33
+ }): ReadonlyArray<AccountQuota | null | undefined>;
34
+ export declare function computeQuotaLabelWidth(quotas: ReadonlyArray<AccountQuota | null | undefined>): number;
35
+ export declare function buildQuotaRowsForDisplay(quota: AccountQuota | null, now: number, pacingEnabled: boolean, labelWidth?: number): QuotaDisplayRow[];
15
36
  export declare function isQuotaLoaded(quota: AccountQuota | null): boolean;
16
37
  export declare function getQuotaMetadataRows(state: SidebarState): Array<{
17
- label: string;
18
- value: string;
38
+ label: string;
39
+ value: string;
19
40
  }>;
20
- export declare function getAccountMetadataRows(resetCredits: number | undefined): Array<{
21
- label: string;
22
- value: string;
41
+ export declare function formatSpendControlAmounts(spendControl: SpendControlReading): string;
42
+ export declare function getAccountMetadataRows(resetCredits: number | undefined, spendControl?: SpendControlReading): Array<{
43
+ label: string;
44
+ value: string;
23
45
  }>;
24
46
  export declare function readStateFromFile(): Promise<SidebarState>;
25
47
  export declare function resolveQuotaDialogActiveId(state: SidebarState, sessionId: string | undefined, now?: number): string | undefined;
26
48
  export interface RoutingDisplayRow {
27
- label: string;
28
- value: string;
29
- tone: Tone;
49
+ label: string;
50
+ value: string;
51
+ tone: Tone;
30
52
  }
31
53
  export declare function buildRoutingRowsForDisplay(state: SidebarState, sessionId: string | undefined, now?: number): RoutingDisplayRow[];
32
54
  declare const plugin: TuiPluginModule & {
33
- id: string;
55
+ id: string;
34
56
  };
35
- export default plugin;
57
+ //#endregion
58
+ export { plugin as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-openai-auth",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,9 +10,6 @@
10
10
  },
11
11
  "main": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts",
13
- "bin": {
14
- "openai-auth": "./dist/cli.js"
15
- },
16
13
  "exports": {
17
14
  ".": {
18
15
  "import": "./dist/index.js",
@@ -36,22 +33,15 @@
36
33
  },
37
34
  "files": [
38
35
  "dist",
39
- "src/tui.tsx",
40
- "src/tui",
36
+ "src/tui/entry.mjs",
41
37
  "src/tui-compiled",
42
- "src/sidebar-state.ts",
43
- "src/tui-preferences.ts",
44
- "src/logger.ts",
45
- "src/core/account-paths.ts",
46
- "src/core/refresh-file-lock.ts",
47
- "src/util",
48
- "src/rpc",
49
38
  "README.md",
50
39
  "LICENSE"
51
40
  ],
52
41
  "scripts": {
53
- "build": "rm -rf dist && bun build src/index.ts src/cli.ts src/sidebar-state.ts src/tui-preferences.ts src/rpc/rpc-client.ts src/rpc/port-file.ts src/rpc/protocol.ts src/rpc/rpc-dir.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --minify && tsc -p tsconfig.build.json --emitDeclarationOnly && bun run build:tui",
42
+ "build": "rm -rf dist && bun build src/index.ts src/sidebar-state.ts src/tui-preferences.ts src/rpc/rpc-client.ts src/rpc/port-file.ts src/rpc/protocol.ts src/rpc/rpc-dir.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --minify && bun run build:types && bun run build:tui",
54
43
  "build:tui": "bun scripts/build-tui.ts",
44
+ "build:types": "cd ../core && bun run build && cd ../opencode && tsdown --config tsdown.config.ts",
55
45
  "build:dev": "rm -rf dist && tsc -p tsconfig.build.json",
56
46
  "test": "bun test src/tests",
57
47
  "typecheck": "tsc",
@@ -61,7 +51,11 @@
61
51
  "peerDependencies": {
62
52
  "@opencode-ai/plugin": "*"
63
53
  },
54
+ "devDependencies": {
55
+ "@cortexkit/openai-auth-core": "workspace:*"
56
+ },
64
57
  "dependencies": {
58
+ "@cortexkit/subc-client": "^0.8.1",
65
59
  "@opentui/core": ">=0.5.11",
66
60
  "@opentui/solid": ">=0.5.11",
67
61
  "ai": "^7.0.99",
package/src/tui/entry.mjs CHANGED
@@ -22,12 +22,12 @@ try {
22
22
  }
23
23
  // Older hosts and bare Bun do not provide the virtual registry. Their source
24
24
  // loader still applies the Solid transform, so retain the raw TSX fallback.
25
- mod = await import('../tui.tsx')
25
+ mod = await import('../tui-compiled/raw/tui.tsx')
26
26
  }
27
27
 
28
28
  if (!mod) {
29
29
  try {
30
- mod = await import('../tui-compiled/tui.tsx')
30
+ mod = await import('../tui-compiled/runtime/tui.tsx')
31
31
  } catch (error) {
32
32
  console.error('OpenAI Auth compiled TUI failed to load', error)
33
33
  throw error
@@ -1,23 +1,28 @@
1
1
  import { realpathSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import { basename, dirname, join, resolve } from 'node:path'
4
+ import {
5
+ ACCOUNT_FILE_NAME,
6
+ ACCOUNT_STATE_FILE_NAME,
7
+ type AccountPaths,
8
+ deriveStatePath,
9
+ } from '../shared/internal.ts'
4
10
 
5
11
  /**
6
- * Where the account store lives.
12
+ * Where OpenCode's account store lives.
7
13
  *
8
- * This is the ONLY definition. It used to be duplicated verbatim in both
9
- * `accounts.ts` and `refresh-file-lock.ts`, because `accounts.ts` imports the
10
- * lock and the reverse import would have been a cycle. Two copies of a path
11
- * resolver is a bad trade for that: the lock and the write must agree on the
12
- * exact file, and if the copies ever drifted, a writer would take a lock on one
13
- * path while writing another — mutual exclusion silently lost on a file holding
14
- * credentials. Nothing would fail a test, since each copy works on its own.
14
+ * This is the ONLY definition of this host's resolution rules. The store code
15
+ * itself takes both paths as arguments and resolves nothing, so the lock and
16
+ * the write it guards can never disagree about which file they mean — a
17
+ * disagreement would lose mutual exclusion on a file holding credentials while
18
+ * every test still passed, since each side works on its own.
15
19
  *
16
- * This module imports nothing from either side, so both can depend on it.
20
+ * The file names come from the core so the two hosts cannot drift apart on what
21
+ * the store is called.
17
22
  */
18
23
 
19
- export const ACCOUNT_FILE_NAME = 'openai-auth.json'
20
- export const ACCOUNT_STATE_FILE_NAME = 'openai-auth-state.json'
24
+ export type { AccountPaths }
25
+ export { ACCOUNT_FILE_NAME, ACCOUNT_STATE_FILE_NAME, deriveStatePath }
21
26
 
22
27
  function getConfigDir() {
23
28
  if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
@@ -36,13 +41,6 @@ export function getAccountStoragePath() {
36
41
  )
37
42
  }
38
43
 
39
- /** Derive the state-file path from the config path without reading env vars. */
40
- export function deriveStatePath(configPath: string): string {
41
- return basename(configPath) === ACCOUNT_FILE_NAME
42
- ? join(dirname(configPath), ACCOUNT_STATE_FILE_NAME)
43
- : `${configPath}.state.json`
44
- }
45
-
46
44
  function normalizePathForComparison(path: string, platform: NodeJS.Platform) {
47
45
  const resolved = resolve(path)
48
46
  return platform === 'win32' || platform === 'darwin'
@@ -101,3 +99,16 @@ export function getAccountStatePath(configPath = getAccountStoragePath()) {
101
99
  }
102
100
  return deriveStatePath(configPath)
103
101
  }
102
+
103
+ /**
104
+ * Both file paths a store call needs, resolved the way this host resolves them.
105
+ *
106
+ * Every call site that used to pass a single config path and let the store
107
+ * derive the state path now passes this pair instead, so the state-file
108
+ * override keeps being honoured in exactly the places it was before.
109
+ */
110
+ export function getAccountPaths(
111
+ configPath = getAccountStoragePath(),
112
+ ): AccountPaths {
113
+ return { configPath, statePath: getAccountStatePath(configPath) }
114
+ }
@@ -0,0 +1,243 @@
1
+ export type CustodyMode = 'local' | 'claustrum'
2
+
3
+ export type ManifestCustodyState = 'absent' | 'present' | 'unreadable'
4
+
5
+ export type LocalCustodyState =
6
+ | 'real'
7
+ | 'tombstone'
8
+ | 'empty'
9
+ | 'gone'
10
+ | 'slot-absent'
11
+
12
+ export type VaultCustodyState =
13
+ | 'serves'
14
+ | 'cold'
15
+ | 'needs_reauth'
16
+ | 'identity_mismatch'
17
+ | 'no_handle'
18
+
19
+ export const CUSTODY_INERT_REASONS = [
20
+ 'needs-login',
21
+ 'mode-mismatch',
22
+ 'corrupt-under-binding',
23
+ 'manifest-unreadable',
24
+ 'unbound-under-claustrum',
25
+ 'orphan-tombstone',
26
+ 'vault-cold',
27
+ 'vault-reauth',
28
+ 'identity-mismatch',
29
+ 'no-handle',
30
+ 'takeover-incomplete',
31
+ 'new-local-family-under-claustrum',
32
+ 'takeover-incomplete/vault-unavailable',
33
+ 'takeover-incomplete/slot-absent',
34
+ ] as const
35
+
36
+ export type CustodyInertReason = (typeof CUSTODY_INERT_REASONS)[number]
37
+
38
+ export type CustodyVerdict =
39
+ | { kind: 'LOCAL' }
40
+ | {
41
+ kind: 'VAULT'
42
+ canonicalize?: boolean
43
+ installTombstone?: boolean
44
+ }
45
+ | {
46
+ kind: 'INERT'
47
+ reason: CustodyInertReason
48
+ installTombstone?: boolean
49
+ canonicalize?: boolean
50
+ }
51
+ | { kind: 'NEEDS_LOGIN'; reason?: 'corrupt' }
52
+
53
+ export interface EvaluateCustodyStartupInput {
54
+ mode: CustodyMode
55
+ manifest: ManifestCustodyState
56
+ local: LocalCustodyState
57
+ vault: () => VaultCustodyState
58
+ manifestFailureReason?: string
59
+ fingerprintMatch?: boolean
60
+ verifiedInProcessLogin: boolean
61
+ isMain?: boolean
62
+ }
63
+
64
+ function unreachable(value: never): never {
65
+ return value
66
+ }
67
+
68
+ function withCanonicalization<
69
+ T extends Extract<CustodyVerdict, { kind: 'VAULT' | 'INERT' }>,
70
+ >(local: LocalCustodyState, verdict: T): T | (T & { canonicalize: true }) {
71
+ return local === 'empty' ? { ...verdict, canonicalize: true } : verdict
72
+ }
73
+
74
+ function evaluateLocal(local: LocalCustodyState): CustodyVerdict {
75
+ switch (local) {
76
+ case 'real':
77
+ return { kind: 'LOCAL' }
78
+ case 'tombstone':
79
+ case 'empty':
80
+ case 'slot-absent':
81
+ return { kind: 'NEEDS_LOGIN' }
82
+ case 'gone':
83
+ return { kind: 'NEEDS_LOGIN', reason: 'corrupt' }
84
+ }
85
+ return unreachable(local)
86
+ }
87
+
88
+ function evaluateLocalWithBinding(
89
+ local: LocalCustodyState,
90
+ verifiedInProcessLogin: boolean,
91
+ ): CustodyVerdict {
92
+ switch (local) {
93
+ case 'real':
94
+ return verifiedInProcessLogin
95
+ ? { kind: 'LOCAL' }
96
+ : { kind: 'INERT', reason: 'needs-login' }
97
+ case 'tombstone':
98
+ case 'empty':
99
+ case 'slot-absent':
100
+ return { kind: 'INERT', reason: 'mode-mismatch' }
101
+ case 'gone':
102
+ return { kind: 'INERT', reason: 'corrupt-under-binding' }
103
+ }
104
+ return unreachable(local)
105
+ }
106
+
107
+ function evaluateUnboundClaustrum(local: LocalCustodyState): CustodyVerdict {
108
+ switch (local) {
109
+ case 'tombstone':
110
+ case 'empty':
111
+ return { kind: 'INERT', reason: 'orphan-tombstone' }
112
+ case 'real':
113
+ case 'gone':
114
+ case 'slot-absent':
115
+ return { kind: 'INERT', reason: 'unbound-under-claustrum' }
116
+ }
117
+ return unreachable(local)
118
+ }
119
+
120
+ function evaluateTombstone(
121
+ local: Extract<LocalCustodyState, 'tombstone' | 'empty'>,
122
+ vault: VaultCustodyState,
123
+ ): CustodyVerdict {
124
+ switch (vault) {
125
+ case 'serves':
126
+ return withCanonicalization(local, { kind: 'VAULT' })
127
+ case 'cold':
128
+ return withCanonicalization(local, {
129
+ kind: 'INERT',
130
+ reason: 'vault-cold',
131
+ })
132
+ case 'needs_reauth':
133
+ return withCanonicalization(local, {
134
+ kind: 'INERT',
135
+ reason: 'vault-reauth',
136
+ })
137
+ case 'identity_mismatch':
138
+ return withCanonicalization(local, {
139
+ kind: 'INERT',
140
+ reason: 'identity-mismatch',
141
+ })
142
+ case 'no_handle':
143
+ return withCanonicalization(local, { kind: 'INERT', reason: 'no-handle' })
144
+ }
145
+ return unreachable(vault)
146
+ }
147
+
148
+ function evaluateReal(
149
+ vault: VaultCustodyState,
150
+ fingerprintMatch: boolean | undefined,
151
+ ): CustodyVerdict {
152
+ switch (vault) {
153
+ case 'serves':
154
+ return fingerprintMatch === false
155
+ ? { kind: 'INERT', reason: 'new-local-family-under-claustrum' }
156
+ : { kind: 'INERT', reason: 'takeover-incomplete' }
157
+ case 'cold':
158
+ case 'needs_reauth':
159
+ return { kind: 'INERT', reason: 'takeover-incomplete/vault-unavailable' }
160
+ case 'identity_mismatch':
161
+ return { kind: 'INERT', reason: 'identity-mismatch' }
162
+ case 'no_handle':
163
+ return { kind: 'INERT', reason: 'no-handle' }
164
+ }
165
+ return unreachable(vault)
166
+ }
167
+
168
+ function evaluateGone(vault: VaultCustodyState): CustodyVerdict {
169
+ switch (vault) {
170
+ case 'serves':
171
+ return { kind: 'VAULT', installTombstone: true }
172
+ case 'cold':
173
+ return { kind: 'INERT', reason: 'vault-cold', installTombstone: true }
174
+ case 'needs_reauth':
175
+ return { kind: 'INERT', reason: 'vault-reauth', installTombstone: true }
176
+ case 'identity_mismatch':
177
+ return { kind: 'INERT', reason: 'identity-mismatch' }
178
+ case 'no_handle':
179
+ return { kind: 'INERT', reason: 'no-handle' }
180
+ }
181
+ return unreachable(vault)
182
+ }
183
+
184
+ function evaluateSlotAbsent(vault: VaultCustodyState): CustodyVerdict {
185
+ switch (vault) {
186
+ case 'serves':
187
+ case 'cold':
188
+ case 'needs_reauth':
189
+ return { kind: 'INERT', reason: 'takeover-incomplete/slot-absent' }
190
+ case 'identity_mismatch':
191
+ return { kind: 'INERT', reason: 'identity-mismatch' }
192
+ case 'no_handle':
193
+ return { kind: 'INERT', reason: 'no-handle' }
194
+ }
195
+ return unreachable(vault)
196
+ }
197
+
198
+ function evaluatePresentClaustrum(
199
+ local: LocalCustodyState,
200
+ vault: VaultCustodyState,
201
+ fingerprintMatch: boolean | undefined,
202
+ ): CustodyVerdict {
203
+ switch (local) {
204
+ case 'tombstone':
205
+ case 'empty':
206
+ return evaluateTombstone(local, vault)
207
+ case 'real':
208
+ return evaluateReal(vault, fingerprintMatch)
209
+ case 'gone':
210
+ return evaluateGone(vault)
211
+ case 'slot-absent':
212
+ return evaluateSlotAbsent(vault)
213
+ }
214
+ return unreachable(local)
215
+ }
216
+
217
+ export function evaluateCustodyStartup(
218
+ input: EvaluateCustodyStartupInput,
219
+ ): CustodyVerdict {
220
+ if (input.manifest === 'unreadable') {
221
+ return { kind: 'INERT', reason: 'manifest-unreadable' }
222
+ }
223
+
224
+ switch (input.mode) {
225
+ case 'local':
226
+ return input.manifest === 'present'
227
+ ? evaluateLocalWithBinding(input.local, input.verifiedInProcessLogin)
228
+ : evaluateLocal(input.local)
229
+ case 'claustrum':
230
+ if (input.manifest === 'absent') {
231
+ return evaluateUnboundClaustrum(input.local)
232
+ }
233
+ if (input.manifest === 'present') {
234
+ return evaluatePresentClaustrum(
235
+ input.local,
236
+ input.vault(),
237
+ input.fingerprintMatch,
238
+ )
239
+ }
240
+ return unreachable(input.manifest)
241
+ }
242
+ return unreachable(input.mode)
243
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * OpenCode's log destination.
3
+ *
4
+ * The logger itself lives in the shared core so both hosts get the same
5
+ * redaction rules. What stays here is the part that is specific to this host:
6
+ * which file the lines go to, which level floor applies, and when the buffer is
7
+ * drained at process exit. Those are the only three things that read this
8
+ * host's environment variables, and the core reads none.
9
+ *
10
+ * The init runs at module load rather than from one call site because every
11
+ * entry point in this package — the plugin loader, the RPC server, the separate
12
+ * TUI process — reaches the logger through this module, and a core logger no
13
+ * host has initialised silently drops every line. Both values are passed as
14
+ * functions so the destination and level keep resolving on each write, which is
15
+ * what they do today.
16
+ */
17
+ import { tmpdir } from 'node:os'
18
+ import { join } from 'node:path'
19
+ import {
20
+ flushLogs,
21
+ initLogger,
22
+ type Level,
23
+ } from '../shared/internal.ts'
24
+
25
+ export {
26
+ createLogger,
27
+ flushForTest,
28
+ flushLogs,
29
+ type Level,
30
+ redact,
31
+ redactStrings,
32
+ setLogLevel,
33
+ } from '../shared/internal.ts'
34
+
35
+ const LEVELS: readonly Level[] = ['error', 'warn', 'info', 'debug', 'trace']
36
+
37
+ export function getLogFile(): string {
38
+ return (
39
+ process.env.OPENCODE_OPENAI_AUTH_LOG_FILE ??
40
+ join(tmpdir(), 'opencode-openai-auth.log')
41
+ )
42
+ }
43
+
44
+ export function getEnvLogLevel(): Level | undefined {
45
+ const env = process.env.OPENCODE_OPENAI_AUTH_LOG_LEVEL as Level | undefined
46
+ return env && LEVELS.includes(env) ? env : undefined
47
+ }
48
+
49
+ initLogger({ file: getLogFile, level: getEnvLogLevel })
50
+
51
+ // Under `bun test` the buffer is drained explicitly by the tests that assert on
52
+ // it, and registering a process-wide exit handler per test file is noise.
53
+ if (process.env.NODE_ENV !== 'test') process.on('exit', flushLogs)
@@ -10,7 +10,7 @@ import {
10
10
  writeFile,
11
11
  } from 'node:fs/promises'
12
12
  import { join, resolve } from 'node:path'
13
- import { createLogger } from '../logger'
13
+ import { createLogger } from './logger.ts'
14
14
 
15
15
  const log = createLogger('rpc')
16
16
 
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The RPC payload types now live in the shared core, because both hosts build
3
+ * the same payloads. This file stays on disk as a re-export so the existing
4
+ * build entry and every importer that names `rpc/protocol` keep working.
5
+ */
6
+ export type {
7
+ ApplyRequest,
8
+ ApplyResult,
9
+ CommandModalName,
10
+ OpenDialogPayload,
11
+ RpcNotification,
12
+ } from '../shared/internal.ts'
@@ -1,5 +1,5 @@
1
- import { discoverPortFile, type PortFileEntry } from './port-file'
2
- import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
1
+ import { discoverPortFile, type PortFileEntry } from './rpc-port-file.ts'
2
+ import type { ApplyRequest, ApplyResult, RpcNotification } from './rpc-protocol.ts'
3
3
 
4
4
  export interface RpcClient {
5
5
  pending: (