@cortexkit/opencode-openai-auth 0.7.1 → 0.8.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/README.md +9 -5
- package/dist/{index-9jpc8yct.js → index-02h7g5f9.js} +1 -1
- package/dist/index-2xppkvkx.js +2 -0
- package/dist/index-3w4vmckx.js +2 -0
- package/dist/{index-zxqb5ydj.js → index-507jh8bv.js} +1 -1
- package/dist/{index-zg1x8sty.js → index-944gsj1s.js} +1 -1
- package/dist/index-nshbgkz1.js +198 -0
- package/dist/{index-87145jkw.js → index-xyg86jgt.js} +1 -1
- package/dist/index.d.ts +381 -35
- package/dist/index.js +66 -161
- package/dist/protocol-Cv9ZmU0p.d.ts +24 -0
- package/dist/quota-normalize-wap0rne8.js +1 -0
- package/dist/rpc/port-file.d.ts +11 -9
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -21
- package/dist/rpc/rpc-client.d.ts +6 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +7 -5
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state.d.ts +84 -83
- package/dist/sidebar-state.js +1 -1
- package/dist/{token-bvp0y166.js → token-jdvpwk60.js} +1 -1
- package/dist/token-util-zzm7paz9.js +1 -0
- package/dist/tui-preferences.d.ts +27 -26
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +18 -17
- package/package.json +10 -17
- package/src/tui/entry.mjs +2 -2
- package/src/tui-compiled/{core/account-paths.ts → raw/core-account-paths.ts} +29 -18
- package/src/tui-compiled/raw/logger.ts +53 -0
- package/src/tui-compiled/{rpc/port-file.ts → raw/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/raw/rpc-protocol.ts +12 -0
- package/src/{rpc/rpc-client.ts → tui-compiled/raw/rpc-rpc-client.ts} +2 -2
- package/src/{sidebar-state.ts → tui-compiled/raw/sidebar-state.ts} +2 -2
- package/src/{tui/command-dialogs.tsx → tui-compiled/raw/tui-command-dialogs.tsx} +5 -5
- package/src/{tui.tsx → tui-compiled/raw/tui.tsx} +11 -10
- package/src/{core/account-paths.ts → tui-compiled/runtime/core-account-paths.ts} +29 -18
- package/src/tui-compiled/runtime/logger.ts +53 -0
- package/src/{rpc/port-file.ts → tui-compiled/runtime/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/runtime/rpc-protocol.ts +12 -0
- package/src/tui-compiled/{rpc/rpc-client.ts → runtime/rpc-rpc-client.ts} +2 -2
- package/src/tui-compiled/{sidebar-state.ts → runtime/sidebar-state.ts} +2 -2
- package/src/tui-compiled/{tui/command-dialogs.tsx → runtime/tui-command-dialogs.tsx} +3 -4
- package/src/tui-compiled/{tui.tsx → runtime/tui.tsx} +10 -10
- package/src/tui-compiled/shared/internal.ts +6 -0
- package/src/{logger.ts → tui-compiled/shared/logger.ts} +82 -21
- package/src/tui-compiled/shared/paths.ts +33 -0
- package/src/{core → tui-compiled/shared}/refresh-file-lock.ts +7 -4
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -15
- package/dist/codex-http.d.ts +0 -3
- package/dist/commands.d.ts +0 -88
- package/dist/config.d.ts +0 -47
- package/dist/core/account-paths.d.ts +0 -25
- package/dist/core/accounts.d.ts +0 -330
- package/dist/core/atomic-write.d.ts +0 -1
- package/dist/core/background-quota-refresh.d.ts +0 -46
- package/dist/core/backoff.d.ts +0 -53
- package/dist/core/cachekeep.d.ts +0 -118
- package/dist/core/oauth.d.ts +0 -138
- package/dist/core/provider.d.ts +0 -70
- package/dist/core/quota-manager.d.ts +0 -192
- package/dist/core/refresh-all-quota.d.ts +0 -86
- package/dist/core/refresh-file-lock.d.ts +0 -12
- package/dist/core/reset-credits.d.ts +0 -116
- package/dist/core/sticky-routing.d.ts +0 -43
- package/dist/dump.d.ts +0 -20
- package/dist/hosted-web-search.d.ts +0 -5
- package/dist/index-8f1200qx.js +0 -6
- package/dist/index-nrvjr97q.js +0 -3
- package/dist/index-qaggaze1.js +0 -2
- package/dist/index-sdcpzbnk.js +0 -2
- package/dist/index-zf3axndm.js +0 -89
- package/dist/logger.d.ts +0 -22
- package/dist/model-costs.d.ts +0 -29
- package/dist/prompt-context.d.ts +0 -19
- package/dist/quota-normalize-dvwtmy7f.js +0 -1
- package/dist/quota-normalize.d.ts +0 -43
- package/dist/raw-ws-bun.d.ts +0 -40
- package/dist/raw-ws-node.d.ts +0 -37
- package/dist/raw-ws-upgrade.d.ts +0 -2
- package/dist/raw-ws.d.ts +0 -3
- package/dist/response-stream-error.d.ts +0 -12
- package/dist/rpc/notifications.d.ts +0 -5
- package/dist/rpc/rpc-server.d.ts +0 -17
- package/dist/token-util-jcj7k7pa.js +0 -1
- package/dist/tui/command-dialogs.d.ts +0 -21
- package/dist/util/error.d.ts +0 -1
- package/dist/util/open-url.d.ts +0 -6
- package/dist/util/proxy-env.d.ts +0 -4
- package/dist/util/record.d.ts +0 -1
- package/dist/util/stable-json.d.ts +0 -1
- package/dist/util/uuid-v7.d.ts +0 -1
- package/dist/version.d.ts +0 -1
- package/dist/ws-pool.d.ts +0 -57
- package/dist/ws.d.ts +0 -53
- package/src/rpc/notifications.ts +0 -54
- package/src/rpc/protocol.ts +0 -33
- package/src/rpc/rpc-server.ts +0 -172
- package/src/tui-compiled/core/refresh-file-lock.ts +0 -381
- package/src/tui-compiled/logger.ts +0 -196
- package/src/tui-compiled/rpc/protocol.ts +0 -33
- package/src/util/error.ts +0 -9
- package/src/util/open-url.ts +0 -30
- package/src/util/proxy-env.ts +0 -42
- package/src/util/record.ts +0 -3
- package/src/util/stable-json.ts +0 -15
- package/src/util/uuid-v7.ts +0 -18
- /package/src/{rpc/rpc-dir.ts → tui-compiled/raw/rpc-rpc-dir.ts} +0 -0
- /package/src/tui-compiled/{tui-preferences.ts → raw/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{rpc/rpc-dir.ts → runtime/rpc-rpc-dir.ts} +0 -0
- /package/src/{tui-preferences.ts → tui-compiled/runtime/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{util → shared/util}/error.ts +0 -0
- /package/src/tui-compiled/{util → shared/util}/open-url.ts +0 -0
|
@@ -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
|
|
12
|
+
* Where OpenCode's account store lives.
|
|
7
13
|
*
|
|
8
|
-
* This is the ONLY definition
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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
|
|
20
|
-
export
|
|
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,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)
|
|
@@ -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: (
|
|
@@ -155,8 +155,8 @@ import { createHash, randomUUID } from 'node:crypto'
|
|
|
155
155
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
156
156
|
import { tmpdir } from 'node:os'
|
|
157
157
|
import { dirname, join } from 'node:path'
|
|
158
|
-
import { acquireRefreshFileLock } from '
|
|
159
|
-
import { createLogger } from './logger'
|
|
158
|
+
import { acquireRefreshFileLock } from '../shared/internal.ts'
|
|
159
|
+
import { createLogger } from './logger.ts'
|
|
160
160
|
|
|
161
161
|
const logSb = createLogger('sidebar')
|
|
162
162
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
|
|
3
|
+
import { errorMessage, openUrl } from '../shared/internal.ts'
|
|
2
4
|
import type { TuiPluginApi } from '@opencode-ai/plugin/tui'
|
|
3
|
-
import { createLogger } from '
|
|
4
|
-
import type { OpenDialogPayload } from '
|
|
5
|
+
import { createLogger } from './logger.ts'
|
|
6
|
+
import type { OpenDialogPayload } from './rpc-protocol.ts'
|
|
5
7
|
import {
|
|
6
8
|
type AccountQuota,
|
|
7
9
|
getCollapsedQuotaSummary,
|
|
8
10
|
getSidebarState,
|
|
9
11
|
resolveSessionSidebarRouting,
|
|
10
12
|
type SidebarState,
|
|
11
|
-
} from '
|
|
12
|
-
import { errorMessage } from '../util/error'
|
|
13
|
-
import { openUrl } from '../util/open-url'
|
|
13
|
+
} from './sidebar-state.ts'
|
|
14
14
|
|
|
15
15
|
const log = createLogger('rpc-tui')
|
|
16
16
|
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
onCleanup,
|
|
17
17
|
Show,
|
|
18
18
|
} from 'solid-js'
|
|
19
|
-
import { createLogger } from './logger.
|
|
20
|
-
import type { ApplyRequest, CommandModalName } from './rpc
|
|
21
|
-
import { createRpcClient } from './rpc
|
|
22
|
-
import { resolveRpcDir } from './rpc
|
|
19
|
+
import { createLogger } from './logger.ts'
|
|
20
|
+
import type { ApplyRequest, CommandModalName } from './rpc-protocol.ts'
|
|
21
|
+
import { createRpcClient } from './rpc-rpc-client.ts'
|
|
22
|
+
import { resolveRpcDir } from './rpc-rpc-dir.ts'
|
|
23
23
|
import {
|
|
24
24
|
type AccountQuota,
|
|
25
25
|
computeQuotaPacing,
|
|
@@ -33,8 +33,8 @@ import {
|
|
|
33
33
|
resolveSessionSidebarRouting,
|
|
34
34
|
resolveSessionStickyAccount,
|
|
35
35
|
type SidebarState,
|
|
36
|
-
} from './sidebar-state.
|
|
37
|
-
import { openCommandDialog } from './tui
|
|
36
|
+
} from './sidebar-state.ts'
|
|
37
|
+
import { openCommandDialog } from './tui-command-dialogs.tsx'
|
|
38
38
|
import {
|
|
39
39
|
type AppearancePrefs,
|
|
40
40
|
computeEffectiveOrder,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
readTuiPreferencesFile,
|
|
46
46
|
resolveOpenaiAuthPrefs,
|
|
47
47
|
watchTuiPreferences,
|
|
48
|
-
} from './tui-preferences.
|
|
48
|
+
} from './tui-preferences.ts'
|
|
49
49
|
|
|
50
50
|
const RPC_POLL_MS = 500
|
|
51
51
|
let rpcPollStarted = false
|
|
@@ -61,14 +61,15 @@ export function buildApplyRequest(
|
|
|
61
61
|
return { command, arguments: arguments_, sessionId }
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// Read package metadata from
|
|
65
|
-
//
|
|
66
|
-
//
|
|
64
|
+
// Read package metadata from the development source or either generated TUI
|
|
65
|
+
// variant. Avoid a JSON import because package.json sits outside the declaration
|
|
66
|
+
// build's rootDir.
|
|
67
67
|
const PLUGIN_VERSION: string = (() => {
|
|
68
68
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
69
69
|
for (const packageFile of [
|
|
70
70
|
join(here, '..', 'package.json'),
|
|
71
71
|
join(here, '..', '..', 'package.json'),
|
|
72
|
+
join(here, '..', '..', '..', 'package.json'),
|
|
72
73
|
]) {
|
|
73
74
|
try {
|
|
74
75
|
const raw = readFileSync(packageFile, 'utf8')
|
|
@@ -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
|
|
12
|
+
* Where OpenCode's account store lives.
|
|
7
13
|
*
|
|
8
|
-
* This is the ONLY definition
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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
|
|
20
|
-
export
|
|
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,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)
|
|
@@ -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: (
|
|
@@ -155,8 +155,8 @@ import { createHash, randomUUID } from 'node:crypto'
|
|
|
155
155
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
156
156
|
import { tmpdir } from 'node:os'
|
|
157
157
|
import { dirname, join } from 'node:path'
|
|
158
|
-
import { acquireRefreshFileLock } from '
|
|
159
|
-
import { createLogger } from './logger'
|
|
158
|
+
import { acquireRefreshFileLock } from '../shared/internal.ts'
|
|
159
|
+
import { createLogger } from './logger.ts'
|
|
160
160
|
|
|
161
161
|
const logSb = createLogger('sidebar')
|
|
162
162
|
|
|
@@ -7,10 +7,9 @@ import { createElement as _$createElement } from "opentui:runtime-module:%40open
|
|
|
7
7
|
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
8
8
|
/** @jsxImportSource @opentui/solid */
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { openUrl } from "../util/open-url";
|
|
10
|
+
import { errorMessage, openUrl } from "../shared/internal.ts";
|
|
11
|
+
import { createLogger } from "./logger.ts";
|
|
12
|
+
import { getCollapsedQuotaSummary, getSidebarState, resolveSessionSidebarRouting } from "./sidebar-state.ts";
|
|
14
13
|
const log = createLogger('rpc-tui');
|
|
15
14
|
export function buildCachekeepDialogOptions(payload) {
|
|
16
15
|
const enabled = payload.knobs.enabled === true;
|
|
@@ -12,12 +12,12 @@ import { readFileSync } from "node:fs";
|
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { createEffect, createSignal, For, onCleanup, Show } from "opentui:runtime-module:solid-js";
|
|
15
|
-
import { createLogger } from "./logger.
|
|
16
|
-
import { createRpcClient } from "./rpc
|
|
17
|
-
import { resolveRpcDir } from "./rpc
|
|
18
|
-
import { computeQuotaPacing, DEFAULT_SIDEBAR_STATE, getCollapsedQuotaSummary, getPresentQuotaWindows, getSidebarState, resolveActiveAccount, resolveSessionSidebarRouting, resolveSessionStickyAccount } from "./sidebar-state.
|
|
19
|
-
import { openCommandDialog } from "./tui
|
|
20
|
-
import { computeEffectiveOrder, DEFAULT_SLOT_ORDER, PLUGIN_KEY, queueTuiPreferenceUpdate, readTuiPreferencesFile, resolveOpenaiAuthPrefs, watchTuiPreferences } from "./tui-preferences.
|
|
15
|
+
import { createLogger } from "./logger.ts";
|
|
16
|
+
import { createRpcClient } from "./rpc-rpc-client.ts";
|
|
17
|
+
import { resolveRpcDir } from "./rpc-rpc-dir.ts";
|
|
18
|
+
import { computeQuotaPacing, DEFAULT_SIDEBAR_STATE, getCollapsedQuotaSummary, getPresentQuotaWindows, getSidebarState, resolveActiveAccount, resolveSessionSidebarRouting, resolveSessionStickyAccount } from "./sidebar-state.ts";
|
|
19
|
+
import { openCommandDialog } from "./tui-command-dialogs.tsx";
|
|
20
|
+
import { computeEffectiveOrder, DEFAULT_SLOT_ORDER, PLUGIN_KEY, queueTuiPreferenceUpdate, readTuiPreferencesFile, resolveOpenaiAuthPrefs, watchTuiPreferences } from "./tui-preferences.ts";
|
|
21
21
|
const RPC_POLL_MS = 500;
|
|
22
22
|
let rpcPollStarted = false;
|
|
23
23
|
const log = createLogger('rpc-tui');
|
|
@@ -30,12 +30,12 @@ export function buildApplyRequest(command, arguments_, sessionId) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Read package metadata from
|
|
34
|
-
//
|
|
35
|
-
//
|
|
33
|
+
// Read package metadata from the development source or either generated TUI
|
|
34
|
+
// variant. Avoid a JSON import because package.json sits outside the declaration
|
|
35
|
+
// build's rootDir.
|
|
36
36
|
const PLUGIN_VERSION = (() => {
|
|
37
37
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
38
|
-
for (const packageFile of [join(here, '..', 'package.json'), join(here, '..', '..', 'package.json')]) {
|
|
38
|
+
for (const packageFile of [join(here, '..', 'package.json'), join(here, '..', '..', 'package.json'), join(here, '..', '..', '..', 'package.json')]) {
|
|
39
39
|
try {
|
|
40
40
|
const raw = readFileSync(packageFile, 'utf8');
|
|
41
41
|
const version = JSON.parse(raw).version;
|
|
@@ -5,10 +5,8 @@ import {
|
|
|
5
5
|
renameSync,
|
|
6
6
|
statSync,
|
|
7
7
|
} from 'node:fs'
|
|
8
|
-
import { tmpdir } from 'node:os'
|
|
9
|
-
import { join } from 'node:path'
|
|
10
8
|
|
|
11
|
-
type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace'
|
|
9
|
+
export type Level = 'error' | 'warn' | 'info' | 'debug' | 'trace'
|
|
12
10
|
const ORDER: Record<Level, number> = {
|
|
13
11
|
error: 0,
|
|
14
12
|
warn: 1,
|
|
@@ -16,23 +14,63 @@ const ORDER: Record<Level, number> = {
|
|
|
16
14
|
debug: 3,
|
|
17
15
|
trace: 4,
|
|
18
16
|
}
|
|
19
|
-
const isTestEnv = process.env.NODE_ENV === 'test'
|
|
20
17
|
const MAX_BYTES = 5 * 1024 * 1024
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Where lines are written, and the level floor, as the host supplied them.
|
|
21
|
+
*
|
|
22
|
+
* Both start unset. A host decides where its log lives — that decision reads
|
|
23
|
+
* host environment variables and host directories, neither of which belongs in
|
|
24
|
+
* shared code — and calls `initLogger` before it runs any command. Until then
|
|
25
|
+
* every `log.*` call is a silent no-op: throwing would turn the first command a
|
|
26
|
+
* host forgot to wire into a crash, and buffering would hold credential-bearing
|
|
27
|
+
* lines for an init that may never arrive.
|
|
28
|
+
*/
|
|
29
|
+
let logFileSource: string | (() => string) | undefined
|
|
30
|
+
let initLevelSource: Level | (() => Level | undefined) | undefined
|
|
28
31
|
let runtimeLevel: Level | undefined
|
|
32
|
+
|
|
33
|
+
export interface InitLoggerOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Path of the file lines are appended to, or a function returning it.
|
|
36
|
+
*
|
|
37
|
+
* A function is what a host passes when its destination can move while the
|
|
38
|
+
* process runs — an operator or a test changing the variable that names the
|
|
39
|
+
* log file expects the next line to land in the new file, and a value
|
|
40
|
+
* captured once at init would keep writing to the old one.
|
|
41
|
+
*/
|
|
42
|
+
file: string | (() => string)
|
|
43
|
+
/** Level floor applied when no `setLogLevel` call has overridden it. */
|
|
44
|
+
level?: Level | (() => Level | undefined)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Point the logger at a host's file and level. Idempotent: calling it again
|
|
49
|
+
* replaces both. A runtime level installed by `setLogLevel` is deliberately
|
|
50
|
+
* left alone, because it is the operator's explicit choice and outranks the
|
|
51
|
+
* floor a host computed at start-up.
|
|
52
|
+
*/
|
|
53
|
+
export function initLogger(options: InitLoggerOptions): void {
|
|
54
|
+
logFileSource = options.file
|
|
55
|
+
initLevelSource = options.level
|
|
56
|
+
}
|
|
57
|
+
|
|
29
58
|
export function setLogLevel(l: Level | undefined) {
|
|
30
59
|
if (l === undefined || l in ORDER) runtimeLevel = l
|
|
31
60
|
}
|
|
61
|
+
|
|
62
|
+
function logFilePath(): string | undefined {
|
|
63
|
+
if (logFileSource === undefined) return undefined
|
|
64
|
+
const resolved =
|
|
65
|
+
typeof logFileSource === 'function' ? logFileSource() : logFileSource
|
|
66
|
+
return resolved || undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
32
69
|
function configuredLevel(): Level {
|
|
33
70
|
if (runtimeLevel) return runtimeLevel
|
|
34
|
-
const
|
|
35
|
-
|
|
71
|
+
const floor =
|
|
72
|
+
typeof initLevelSource === 'function' ? initLevelSource() : initLevelSource
|
|
73
|
+
if (floor && floor in ORDER) return floor
|
|
36
74
|
return 'info'
|
|
37
75
|
}
|
|
38
76
|
|
|
@@ -123,9 +161,8 @@ function chmodPrivate(path: string) {
|
|
|
123
161
|
/* never throw */
|
|
124
162
|
}
|
|
125
163
|
}
|
|
126
|
-
function rotateIfNeeded() {
|
|
164
|
+
function rotateIfNeeded(f: string) {
|
|
127
165
|
try {
|
|
128
|
-
const f = logFile()
|
|
129
166
|
if (!(existsSync(f) && statSync(f).size > MAX_BYTES)) return
|
|
130
167
|
for (let i = ROTATE_KEEP - 1; i >= 1; i--) {
|
|
131
168
|
if (existsSync(`${f}.${i}`)) {
|
|
@@ -141,13 +178,19 @@ function rotateIfNeeded() {
|
|
|
141
178
|
/* never throw */
|
|
142
179
|
}
|
|
143
180
|
}
|
|
144
|
-
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Write whatever is buffered. Safe to call synchronously from a process-exit
|
|
184
|
+
* handler, which is how each host drains the buffer on shutdown.
|
|
185
|
+
*/
|
|
186
|
+
export function flushLogs() {
|
|
145
187
|
if (!buffer.length) return
|
|
188
|
+
const file = logFilePath()
|
|
146
189
|
const text = buffer.join('')
|
|
147
190
|
buffer = []
|
|
191
|
+
if (!file) return
|
|
148
192
|
try {
|
|
149
|
-
rotateIfNeeded()
|
|
150
|
-
const file = logFile()
|
|
193
|
+
rotateIfNeeded(file)
|
|
151
194
|
if (existsSync(file)) chmodPrivate(file)
|
|
152
195
|
appendFileSync(file, text, { encoding: 'utf8', mode: 0o600 })
|
|
153
196
|
} catch {
|
|
@@ -158,10 +201,9 @@ function schedule() {
|
|
|
158
201
|
if (!timer)
|
|
159
202
|
timer = setTimeout(() => {
|
|
160
203
|
timer = undefined
|
|
161
|
-
|
|
204
|
+
flushLogs()
|
|
162
205
|
}, 500)
|
|
163
206
|
}
|
|
164
|
-
if (!isTestEnv) process.on('exit', flush)
|
|
165
207
|
|
|
166
208
|
function safeSerialize(data: unknown): string {
|
|
167
209
|
try {
|
|
@@ -172,13 +214,16 @@ function safeSerialize(data: unknown): string {
|
|
|
172
214
|
}
|
|
173
215
|
|
|
174
216
|
function emit(channel: string, level: Level, message: string, data?: unknown) {
|
|
217
|
+
// No destination means no host has initialised the logger. Drop the line
|
|
218
|
+
// rather than buffering it: see the note on logFileSource above.
|
|
219
|
+
if (logFileSource === undefined) return
|
|
175
220
|
if (ORDER[level] > ORDER[configuredLevel()]) return
|
|
176
221
|
const line =
|
|
177
222
|
`[${new Date().toISOString()}] ${level.toUpperCase()} [${channel}] ${message}` +
|
|
178
223
|
(data === undefined ? '' : safeSerialize(data)) +
|
|
179
224
|
'\n'
|
|
180
225
|
buffer.push(line)
|
|
181
|
-
if (buffer.length >= 50)
|
|
226
|
+
if (buffer.length >= 50) flushLogs()
|
|
182
227
|
else schedule()
|
|
183
228
|
}
|
|
184
229
|
|
|
@@ -192,5 +237,21 @@ export function createLogger(channel: string) {
|
|
|
192
237
|
}
|
|
193
238
|
}
|
|
194
239
|
export async function flushForTest() {
|
|
195
|
-
|
|
240
|
+
flushLogs()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Return the logger to its uninitialised state. Only a test needs this: a
|
|
245
|
+
* single process runs every test file, so a file path left over from one test
|
|
246
|
+
* would keep a later "logger was never initialised" case writing lines.
|
|
247
|
+
*/
|
|
248
|
+
export function resetLoggerForTest() {
|
|
249
|
+
buffer = []
|
|
250
|
+
if (timer) {
|
|
251
|
+
clearTimeout(timer)
|
|
252
|
+
timer = undefined
|
|
253
|
+
}
|
|
254
|
+
logFileSource = undefined
|
|
255
|
+
initLevelSource = undefined
|
|
256
|
+
runtimeLevel = undefined
|
|
196
257
|
}
|