opencode-openai-account-switcher 0.1.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 (56) hide show
  1. package/LICENSE +151 -0
  2. package/README.md +43 -0
  3. package/dist/auth-store.d.ts +13 -0
  4. package/dist/auth-store.js +43 -0
  5. package/dist/codex-auth-source.d.ts +16 -0
  6. package/dist/codex-auth-source.js +63 -0
  7. package/dist/codex-invalid-account.d.ts +32 -0
  8. package/dist/codex-invalid-account.js +106 -0
  9. package/dist/codex-network-retry.d.ts +2 -0
  10. package/dist/codex-network-retry.js +9 -0
  11. package/dist/codex-status-command.d.ts +56 -0
  12. package/dist/codex-status-command.js +341 -0
  13. package/dist/codex-status-fetcher.d.ts +71 -0
  14. package/dist/codex-status-fetcher.js +300 -0
  15. package/dist/codex-store.d.ts +49 -0
  16. package/dist/codex-store.js +267 -0
  17. package/dist/common-settings-actions.d.ts +15 -0
  18. package/dist/common-settings-actions.js +22 -0
  19. package/dist/common-settings-store.d.ts +17 -0
  20. package/dist/common-settings-store.js +72 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +1 -0
  23. package/dist/menu-runtime.d.ts +81 -0
  24. package/dist/menu-runtime.js +141 -0
  25. package/dist/network-retry-engine.d.ts +33 -0
  26. package/dist/network-retry-engine.js +62 -0
  27. package/dist/plugin-hooks.d.ts +49 -0
  28. package/dist/plugin-hooks.js +123 -0
  29. package/dist/plugin.d.ts +2 -0
  30. package/dist/plugin.js +127 -0
  31. package/dist/providers/codex-menu-adapter.d.ts +58 -0
  32. package/dist/providers/codex-menu-adapter.js +429 -0
  33. package/dist/providers/descriptor.d.ts +24 -0
  34. package/dist/providers/descriptor.js +16 -0
  35. package/dist/providers/registry.d.ts +15 -0
  36. package/dist/providers/registry.js +49 -0
  37. package/dist/retry/codex-policy.d.ts +5 -0
  38. package/dist/retry/codex-policy.js +75 -0
  39. package/dist/retry/common-policy.d.ts +37 -0
  40. package/dist/retry/common-policy.js +68 -0
  41. package/dist/store-paths.d.ts +4 -0
  42. package/dist/store-paths.js +22 -0
  43. package/dist/ui/ansi.d.ts +18 -0
  44. package/dist/ui/ansi.js +32 -0
  45. package/dist/ui/confirm.d.ts +1 -0
  46. package/dist/ui/confirm.js +14 -0
  47. package/dist/ui/menu.d.ts +168 -0
  48. package/dist/ui/menu.js +305 -0
  49. package/dist/ui/select.d.ts +36 -0
  50. package/dist/ui/select.js +350 -0
  51. package/dist/upstream/codex-loader-adapter.d.ts +99 -0
  52. package/dist/upstream/codex-loader-adapter.js +80 -0
  53. package/dist/upstream/codex-plugin.snapshot.d.ts +32 -0
  54. package/dist/upstream/codex-plugin.snapshot.js +638 -0
  55. package/package.json +40 -0
  56. package/scripts/sync-codex-upstream.mjs +348 -0
@@ -0,0 +1,72 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import { readFileSync } from "node:fs";
4
+ import { commonSettingsPath as defaultCommonSettingsPath } from "./store-paths.js";
5
+ export function normalizeCommonSettingsStore(input) {
6
+ const source = input ?? {};
7
+ const legacySlashCommandsEnabled = source.experimentalStatusSlashCommandEnabled;
8
+ return {
9
+ networkRetryEnabled: source.networkRetryEnabled === true,
10
+ experimentalSlashCommandsEnabled: source.experimentalSlashCommandsEnabled === true || source.experimentalSlashCommandsEnabled === false
11
+ ? source.experimentalSlashCommandsEnabled
12
+ : legacySlashCommandsEnabled !== false,
13
+ ...(source.experimentalStatusSlashCommandEnabled === true || source.experimentalStatusSlashCommandEnabled === false
14
+ ? { experimentalStatusSlashCommandEnabled: source.experimentalStatusSlashCommandEnabled }
15
+ : {}),
16
+ };
17
+ }
18
+ function parsePartialCommonSettingsStore(raw) {
19
+ const parsed = raw ? JSON.parse(raw) : {};
20
+ const partial = {};
21
+ if (parsed.networkRetryEnabled === true || parsed.networkRetryEnabled === false) {
22
+ partial.networkRetryEnabled = parsed.networkRetryEnabled;
23
+ }
24
+ if (parsed.experimentalSlashCommandsEnabled === true || parsed.experimentalSlashCommandsEnabled === false) {
25
+ partial.experimentalSlashCommandsEnabled = parsed.experimentalSlashCommandsEnabled;
26
+ }
27
+ if (parsed.experimentalStatusSlashCommandEnabled === true || parsed.experimentalStatusSlashCommandEnabled === false) {
28
+ partial.experimentalStatusSlashCommandEnabled = parsed.experimentalStatusSlashCommandEnabled;
29
+ }
30
+ return partial;
31
+ }
32
+ export function parseCommonSettingsStore(raw) {
33
+ return normalizeCommonSettingsStore(parsePartialCommonSettingsStore(raw));
34
+ }
35
+ export function commonSettingsPath() {
36
+ return defaultCommonSettingsPath();
37
+ }
38
+ export async function readCommonSettingsStore(options) {
39
+ const file = options?.filePath ?? commonSettingsPath();
40
+ const raw = await fs.readFile(file, "utf8").catch((error) => {
41
+ if (error.code === "ENOENT")
42
+ return "";
43
+ throw error;
44
+ });
45
+ return normalizeCommonSettingsStore(parsePartialCommonSettingsStore(raw));
46
+ }
47
+ export function readCommonSettingsStoreSync(options) {
48
+ const file = options?.filePath ?? commonSettingsPath();
49
+ let raw = "";
50
+ try {
51
+ raw = readFileSync(file, "utf8");
52
+ }
53
+ catch (error) {
54
+ const issue = error;
55
+ if (issue.code !== "ENOENT")
56
+ return undefined;
57
+ }
58
+ return normalizeCommonSettingsStore(parsePartialCommonSettingsStore(raw));
59
+ }
60
+ export async function writeCommonSettingsStore(store, options) {
61
+ const file = options?.filePath ?? commonSettingsPath();
62
+ const normalized = normalizeCommonSettingsStore(store);
63
+ const persisted = {
64
+ networkRetryEnabled: normalized.networkRetryEnabled,
65
+ experimentalSlashCommandsEnabled: normalized.experimentalSlashCommandsEnabled,
66
+ ...(normalized.experimentalStatusSlashCommandEnabled === true || normalized.experimentalStatusSlashCommandEnabled === false
67
+ ? { experimentalStatusSlashCommandEnabled: normalized.experimentalStatusSlashCommandEnabled }
68
+ : {}),
69
+ };
70
+ await fs.mkdir(path.dirname(file), { recursive: true });
71
+ await fs.writeFile(file, JSON.stringify(persisted, null, 2), { mode: 0o600 });
72
+ }
@@ -0,0 +1 @@
1
+ export { OpenAICodexAccountSwitcher } from "./plugin.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { OpenAICodexAccountSwitcher } from "./plugin.js";
@@ -0,0 +1,81 @@
1
+ type WriteMeta = {
2
+ reason: string;
3
+ source: string;
4
+ actionType?: string;
5
+ };
6
+ export type ProviderActionResult = boolean | {
7
+ handled: boolean;
8
+ persistHandled?: boolean;
9
+ result?: unknown;
10
+ };
11
+ export type ProviderActionOutput = {
12
+ name: string;
13
+ payload?: unknown;
14
+ result: unknown;
15
+ };
16
+ export type MenuAccountInfo = {
17
+ id?: string;
18
+ name: string;
19
+ workspaceName?: string;
20
+ index: number;
21
+ isCurrent?: boolean;
22
+ };
23
+ export type MenuActionAccount = {
24
+ id?: string;
25
+ name: string;
26
+ };
27
+ type SharedActionResult = boolean | {
28
+ changed: boolean;
29
+ persistHandled?: boolean;
30
+ };
31
+ export type MenuAction = {
32
+ type: "add";
33
+ } | {
34
+ type: "cancel";
35
+ } | {
36
+ type: "remove";
37
+ account: MenuActionAccount;
38
+ } | {
39
+ type: "remove-all";
40
+ } | {
41
+ type: "switch";
42
+ account: MenuActionAccount;
43
+ } | {
44
+ type: "provider";
45
+ name: string;
46
+ payload?: unknown;
47
+ };
48
+ export type ProviderMenuAdapter<TStore, TEntry> = {
49
+ key: string;
50
+ loadStore: () => Promise<TStore>;
51
+ writeStore: (store: TStore, meta: WriteMeta) => Promise<void>;
52
+ bootstrapAuthImport: (store: TStore) => Promise<boolean>;
53
+ authorizeNewAccount: (store: TStore) => Promise<TEntry | undefined>;
54
+ refreshSnapshots: (store: TStore) => Promise<void>;
55
+ toMenuInfo: (store: TStore) => Promise<MenuAccountInfo[]>;
56
+ getCurrentEntry: (store: TStore) => TEntry | undefined;
57
+ getRefreshConfig: (store: TStore) => {
58
+ enabled: boolean;
59
+ minutes: number;
60
+ };
61
+ getAccountByName: (store: TStore, name: string) => {
62
+ name: string;
63
+ entry: TEntry;
64
+ } | undefined;
65
+ addAccount?: (store: TStore, entry: TEntry) => Promise<SharedActionResult> | SharedActionResult;
66
+ removeAccount?: (store: TStore, name: string) => Promise<SharedActionResult> | SharedActionResult;
67
+ removeAllAccounts?: (store: TStore) => Promise<SharedActionResult> | SharedActionResult;
68
+ switchAccount: (store: TStore, name: string, entry: TEntry) => Promise<{
69
+ persistHandled?: boolean;
70
+ } | void>;
71
+ applyAction?: (store: TStore, action: Extract<MenuAction, {
72
+ type: "provider";
73
+ }>) => Promise<ProviderActionResult>;
74
+ };
75
+ export declare function runProviderMenu<TStore, TEntry>(input: {
76
+ adapter: ProviderMenuAdapter<TStore, TEntry>;
77
+ showMenu: (accounts: MenuAccountInfo[], store: TStore) => Promise<MenuAction>;
78
+ onProviderActionResult?: (output: ProviderActionOutput) => Promise<void> | void;
79
+ now?: () => number;
80
+ }): Promise<TEntry | undefined>;
81
+ export {};
@@ -0,0 +1,141 @@
1
+ function parseSharedActionResult(result) {
2
+ if (typeof result === "object" && result) {
3
+ return {
4
+ changed: result.changed === true,
5
+ persistHandled: result.persistHandled === true,
6
+ };
7
+ }
8
+ return {
9
+ changed: result === true,
10
+ persistHandled: false,
11
+ };
12
+ }
13
+ function providerActionReason(name) {
14
+ return `provider-action:${name}`;
15
+ }
16
+ function isNonPersistentProviderAction(name) {
17
+ void name;
18
+ return false;
19
+ }
20
+ export async function runProviderMenu(input) {
21
+ const now = input.now ?? Date.now;
22
+ const store = await input.adapter.loadStore();
23
+ if (await input.adapter.bootstrapAuthImport(store)) {
24
+ await input.adapter.writeStore(store, {
25
+ reason: "bootstrap-auth-import",
26
+ source: "menu-runtime",
27
+ actionType: "bootstrap-auth-import",
28
+ });
29
+ }
30
+ let nextRefreshAt = 0;
31
+ while (true) {
32
+ const refresh = input.adapter.getRefreshConfig(store);
33
+ if (refresh.enabled && now() >= nextRefreshAt) {
34
+ await input.adapter.refreshSnapshots(store);
35
+ await input.adapter.writeStore(store, {
36
+ reason: "auto-refresh",
37
+ source: "menu-runtime",
38
+ actionType: "auto-refresh",
39
+ });
40
+ nextRefreshAt = now() + refresh.minutes * 60_000;
41
+ }
42
+ const accounts = await input.adapter.toMenuInfo(store);
43
+ const action = await input.showMenu(accounts, store);
44
+ if (action.type === "cancel")
45
+ return input.adapter.getCurrentEntry(store);
46
+ if (action.type === "add") {
47
+ const entry = await input.adapter.authorizeNewAccount(store);
48
+ const result = !entry ? undefined : await input.adapter.addAccount?.(store, entry);
49
+ const parsed = parseSharedActionResult(result);
50
+ if (!entry || !parsed.changed)
51
+ continue;
52
+ if (!parsed.persistHandled) {
53
+ await input.adapter.writeStore(store, {
54
+ reason: "add-account",
55
+ source: "menu-runtime",
56
+ actionType: "add",
57
+ });
58
+ }
59
+ continue;
60
+ }
61
+ if (action.type === "remove-all") {
62
+ const result = await input.adapter.removeAllAccounts?.(store);
63
+ const parsed = parseSharedActionResult(result);
64
+ if (!parsed.changed)
65
+ continue;
66
+ if (!parsed.persistHandled) {
67
+ await input.adapter.writeStore(store, {
68
+ reason: "remove-all",
69
+ source: "menu-runtime",
70
+ actionType: "remove-all",
71
+ });
72
+ }
73
+ continue;
74
+ }
75
+ if (action.type === "remove") {
76
+ const accountName = action.account.id ?? action.account.name;
77
+ const result = await input.adapter.removeAccount?.(store, accountName);
78
+ const parsed = parseSharedActionResult(result);
79
+ if (!parsed.changed)
80
+ continue;
81
+ if (!parsed.persistHandled) {
82
+ await input.adapter.writeStore(store, {
83
+ reason: "remove-account",
84
+ source: "menu-runtime",
85
+ actionType: "remove",
86
+ });
87
+ }
88
+ continue;
89
+ }
90
+ if (action.type === "switch") {
91
+ const accountName = action.account.id ?? action.account.name;
92
+ const selected = input.adapter.getAccountByName(store, accountName);
93
+ if (!selected)
94
+ continue;
95
+ const switchResult = await input.adapter.switchAccount(store, selected.name, selected.entry);
96
+ if (!switchResult?.persistHandled) {
97
+ await input.adapter.writeStore(store, {
98
+ reason: "persist-account-switch",
99
+ source: "menu-runtime",
100
+ actionType: "switch",
101
+ });
102
+ }
103
+ continue;
104
+ }
105
+ if (!input.adapter.applyAction)
106
+ continue;
107
+ const providerActionResult = parseProviderActionResult(await input.adapter.applyAction(store, action));
108
+ if (!providerActionResult.handled)
109
+ continue;
110
+ if (providerActionResult.result !== undefined) {
111
+ await input.onProviderActionResult?.({
112
+ name: action.name,
113
+ payload: action.payload,
114
+ result: providerActionResult.result,
115
+ });
116
+ }
117
+ if (isNonPersistentProviderAction(action.name))
118
+ continue;
119
+ if (providerActionResult.persistHandled)
120
+ continue;
121
+ await input.adapter.writeStore(store, {
122
+ reason: providerActionReason(action.name),
123
+ source: "menu-runtime",
124
+ actionType: action.name,
125
+ });
126
+ }
127
+ }
128
+ function parseProviderActionResult(result) {
129
+ if (typeof result === "object" && result) {
130
+ return {
131
+ handled: result.handled === true,
132
+ persistHandled: result.persistHandled === true,
133
+ result: result.result,
134
+ };
135
+ }
136
+ return {
137
+ handled: result === true,
138
+ persistHandled: false,
139
+ result: undefined,
140
+ };
141
+ }
@@ -0,0 +1,33 @@
1
+ export type NetworkRetryRequest = {
2
+ url: string;
3
+ method?: string;
4
+ body?: string;
5
+ headers?: Record<string, string>;
6
+ };
7
+ export type NetworkRetryClassification = {
8
+ retryable: boolean;
9
+ category: string;
10
+ };
11
+ export type NetworkRetryPolicy = {
12
+ matchesRequest: (request: Request | URL | string) => boolean;
13
+ classifyFailure: (input: {
14
+ error: unknown;
15
+ request: NetworkRetryRequest;
16
+ }) => Promise<NetworkRetryClassification>;
17
+ handleResponse?: (input: {
18
+ response: Response;
19
+ request: NetworkRetryRequest;
20
+ }) => Promise<Response>;
21
+ normalizeFailure?: (input: {
22
+ error: unknown;
23
+ classification: NetworkRetryClassification;
24
+ request: NetworkRetryRequest;
25
+ }) => unknown;
26
+ buildRepairPlan: (input: {
27
+ request: NetworkRetryRequest;
28
+ classification: NetworkRetryClassification;
29
+ }) => Promise<unknown>;
30
+ };
31
+ export declare function createNetworkRetryEngine(input: {
32
+ policy: NetworkRetryPolicy;
33
+ }): (baseFetch: (request: Request | URL | string, init?: RequestInit) => Promise<Response>) => (request: Request | URL | string, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,62 @@
1
+ function toHeaderRecord(headers) {
2
+ if (!headers)
3
+ return undefined;
4
+ return Object.fromEntries(new Headers(headers).entries());
5
+ }
6
+ async function tryGetRequestBodyString(request, init) {
7
+ if (typeof init?.body === "string")
8
+ return init.body;
9
+ if (!(request instanceof Request))
10
+ return undefined;
11
+ try {
12
+ return await request.clone().text();
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ async function toNetworkRetryRequest(request, init) {
19
+ const url = request instanceof Request ? request.url : request instanceof URL ? request.href : String(request);
20
+ const method = init?.method ?? (request instanceof Request ? request.method : undefined);
21
+ const headers = toHeaderRecord(init?.headers) ?? (request instanceof Request ? toHeaderRecord(request.headers) : undefined);
22
+ const body = await tryGetRequestBodyString(request, init);
23
+ return { url, method, headers, body };
24
+ }
25
+ export function createNetworkRetryEngine(input) {
26
+ return function wrapNetworkFetch(baseFetch) {
27
+ return async function retryingNetworkFetch(request, init) {
28
+ if (!input.policy.matchesRequest(request)) {
29
+ return baseFetch(request, init);
30
+ }
31
+ const normalizedRequest = await toNetworkRetryRequest(request, init);
32
+ try {
33
+ const response = await baseFetch(request, init);
34
+ if (!input.policy.handleResponse)
35
+ return response;
36
+ return input.policy.handleResponse({
37
+ response,
38
+ request: normalizedRequest,
39
+ });
40
+ }
41
+ catch (error) {
42
+ const classification = await input.policy.classifyFailure({
43
+ error,
44
+ request: normalizedRequest,
45
+ });
46
+ if (!classification.retryable)
47
+ throw error;
48
+ await input.policy.buildRepairPlan({
49
+ request: normalizedRequest,
50
+ classification,
51
+ });
52
+ if (!input.policy.normalizeFailure)
53
+ throw error;
54
+ throw input.policy.normalizeFailure({
55
+ error,
56
+ classification,
57
+ request: normalizedRequest,
58
+ });
59
+ }
60
+ };
61
+ };
62
+ }
@@ -0,0 +1,49 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import { type FetchLike } from "./codex-network-retry.js";
3
+ import { handleCodexStatusCommand } from "./codex-status-command.js";
4
+ import { type CommonSettingsStore } from "./common-settings-store.js";
5
+ import { type CodexAuthState, type CodexProviderConfig, type OfficialCodexChatHeadersHook, type OfficialCodexConfig } from "./upstream/codex-loader-adapter.js";
6
+ type PluginHooks = Hooks;
7
+ type AuthHook = NonNullable<PluginHooks["auth"]>;
8
+ type CodexStatusCommandHandler = typeof handleCodexStatusCommand;
9
+ type CodexClient = {
10
+ auth?: {
11
+ set?: (value: unknown) => Promise<unknown>;
12
+ };
13
+ };
14
+ type LoadOfficialConfig = (input: {
15
+ getAuth: () => Promise<CodexAuthState | undefined>;
16
+ provider?: CodexProviderConfig;
17
+ baseFetch?: typeof fetch;
18
+ version?: string;
19
+ client?: CodexClient;
20
+ }) => Promise<OfficialCodexConfig | undefined>;
21
+ type LoadOfficialChatHeaders = (input: {
22
+ client?: object;
23
+ directory?: string;
24
+ baseFetch?: typeof fetch;
25
+ version?: string;
26
+ }) => Promise<OfficialCodexChatHeadersHook>;
27
+ type CreateRetryFetch = (fetch: FetchLike) => FetchLike;
28
+ type HookStore = CommonSettingsStore & {
29
+ accounts?: Record<string, unknown>;
30
+ };
31
+ export type BuildPluginHooksInput = {
32
+ auth: AuthHook;
33
+ loadStore?: () => Promise<HookStore | undefined>;
34
+ loadStoreSync?: () => HookStore | undefined;
35
+ loadCommonSettings?: () => Promise<CommonSettingsStore | undefined>;
36
+ loadCommonSettingsSync?: () => CommonSettingsStore | undefined;
37
+ loadOfficialConfig?: LoadOfficialConfig;
38
+ loadOfficialChatHeaders?: LoadOfficialChatHeaders;
39
+ createRetryFetch?: CreateRetryFetch;
40
+ client?: object;
41
+ directory?: string;
42
+ baseFetch?: typeof fetch;
43
+ version?: string;
44
+ handleCodexStatusCommandImpl?: CodexStatusCommandHandler;
45
+ authLoaderMode?: "codex" | "none";
46
+ enableModelRouting?: boolean;
47
+ };
48
+ export declare function buildPluginHooks(input: BuildPluginHooksInput): PluginHooks;
49
+ export {};
@@ -0,0 +1,123 @@
1
+ import { createCodexRetryingFetch } from "./codex-network-retry.js";
2
+ import { handleCodexStatusCommand } from "./codex-status-command.js";
3
+ import { readCommonSettingsStore, readCommonSettingsStoreSync, } from "./common-settings-store.js";
4
+ import { loadOfficialCodexChatHeaders, loadOfficialCodexConfig, } from "./upstream/codex-loader-adapter.js";
5
+ function areExperimentalSlashCommandsEnabled(store) {
6
+ if (store?.experimentalSlashCommandsEnabled === false)
7
+ return false;
8
+ if (store?.experimentalStatusSlashCommandEnabled === false)
9
+ return false;
10
+ return true;
11
+ }
12
+ function isNetworkRetryEnabled(store) {
13
+ return store?.networkRetryEnabled === true;
14
+ }
15
+ function mergeStoreWithCommonSettings(store, common) {
16
+ if (!store && !common)
17
+ return undefined;
18
+ return {
19
+ ...(store ?? {}),
20
+ ...(common?.networkRetryEnabled === true || common?.networkRetryEnabled === false
21
+ ? { networkRetryEnabled: common.networkRetryEnabled }
22
+ : {}),
23
+ ...(common?.experimentalSlashCommandsEnabled === true || common?.experimentalSlashCommandsEnabled === false
24
+ ? { experimentalSlashCommandsEnabled: common.experimentalSlashCommandsEnabled }
25
+ : {}),
26
+ ...(common?.experimentalStatusSlashCommandEnabled === true || common?.experimentalStatusSlashCommandEnabled === false
27
+ ? { experimentalStatusSlashCommandEnabled: common.experimentalStatusSlashCommandEnabled }
28
+ : {}),
29
+ };
30
+ }
31
+ function ensureCommandConfig(config) {
32
+ if (!config.command)
33
+ config.command = {};
34
+ return config.command;
35
+ }
36
+ export function buildPluginHooks(input) {
37
+ const authProvider = input.auth.provider ?? "openai";
38
+ const authLoaderMode = input.authLoaderMode ?? "codex";
39
+ const loadStore = input.loadStore;
40
+ const loadStoreSync = input.loadStoreSync;
41
+ const loadCommonSettings = input.loadCommonSettings ?? (loadStore ? undefined : readCommonSettingsStore);
42
+ const loadCommonSettingsSync = input.loadCommonSettingsSync ?? (loadStoreSync ? undefined : readCommonSettingsStoreSync);
43
+ const loadOfficialConfig = input.loadOfficialConfig ?? loadOfficialCodexConfig;
44
+ const loadOfficialChatHeaders = input.loadOfficialChatHeaders ?? loadOfficialCodexChatHeaders;
45
+ const createRetryFetch = input.createRetryFetch ?? createCodexRetryingFetch;
46
+ const handleCodexStatusCommandImpl = input.handleCodexStatusCommandImpl ?? handleCodexStatusCommand;
47
+ const client = input.client;
48
+ const loadMergedStore = async () => {
49
+ const [store, common] = await Promise.all([
50
+ loadStore?.().catch(() => undefined) ?? Promise.resolve(undefined),
51
+ loadCommonSettings?.().catch(() => undefined) ?? Promise.resolve(undefined),
52
+ ]);
53
+ return mergeStoreWithCommonSettings(store, common);
54
+ };
55
+ const loadMergedStoreSync = () => {
56
+ const store = loadStoreSync?.();
57
+ const common = loadCommonSettingsSync?.();
58
+ return mergeStoreWithCommonSettings(store, common);
59
+ };
60
+ const authLoader = authLoaderMode === "codex"
61
+ ? async (getAuth, provider) => {
62
+ const config = await loadOfficialConfig({
63
+ getAuth: getAuth,
64
+ provider: provider,
65
+ baseFetch: input.baseFetch,
66
+ version: input.version,
67
+ client,
68
+ }).catch(() => undefined);
69
+ if (!config || typeof config.fetch !== "function")
70
+ return {};
71
+ const store = await loadMergedStore();
72
+ if (isNetworkRetryEnabled(store)) {
73
+ return {
74
+ ...config,
75
+ fetch: createRetryFetch(config.fetch),
76
+ };
77
+ }
78
+ return config;
79
+ }
80
+ : undefined;
81
+ const officialChatHeaders = authLoaderMode === "codex"
82
+ ? loadOfficialChatHeaders({
83
+ client: input.client,
84
+ directory: input.directory,
85
+ baseFetch: input.baseFetch,
86
+ version: input.version,
87
+ })
88
+ : Promise.resolve(async () => { });
89
+ const chatHeaders = async (hookInput, output) => {
90
+ if (hookInput.model.providerID !== authProvider)
91
+ return;
92
+ await (await officialChatHeaders)(hookInput, output);
93
+ };
94
+ const configHook = async (config) => {
95
+ const commands = ensureCommandConfig(config);
96
+ const store = loadMergedStoreSync();
97
+ if (!areExperimentalSlashCommandsEnabled(store))
98
+ return;
99
+ commands["codex-status"] = {
100
+ template: "Show the current Codex status and usage snapshot via the experimental status path.",
101
+ description: "Experimental Codex status command",
102
+ };
103
+ };
104
+ const commandBefore = async (hookInput) => {
105
+ if (hookInput.command !== "codex-status")
106
+ return;
107
+ const store = await loadMergedStore();
108
+ if (!areExperimentalSlashCommandsEnabled(store))
109
+ return;
110
+ await handleCodexStatusCommandImpl({ client });
111
+ };
112
+ return {
113
+ auth: {
114
+ ...input.auth,
115
+ provider: authProvider,
116
+ methods: input.auth.methods,
117
+ loader: authLoader,
118
+ },
119
+ config: configHook,
120
+ "command.execute.before": commandBefore,
121
+ "chat.headers": chatHeaders,
122
+ };
123
+ }
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const OpenAICodexAccountSwitcher: Plugin;