@openchambery/web 2.0.0-beta.26 → 2.0.0-beta.29
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 +14 -0
- package/dist/assets/{AgentAvatar-FyTdEOMr.js → AgentAvatar-O2x_1cFU.js} +1 -1
- package/dist/assets/{AssistantView-DvOO2Kf5.js → AssistantView-Bphg-QIn.js} +3 -3
- package/dist/assets/{DiagramView-BqjeSOIg.js → DiagramView-CRD_GFch.js} +1 -1
- package/dist/assets/{FadeInOnReveal-DHDffzHs.js → FadeInOnReveal-CMBd5BTd.js} +1 -1
- package/dist/assets/{Icon-D7NdMmbi.js → Icon-BN87CyS4.js} +1 -1
- package/dist/assets/{MarkdownRenderer-kpkRqS42.js → MarkdownRenderer-D5KEGdBn.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-CzBos-Gt.js → MarkdownRendererImpl-C143YQsV.js} +2 -2
- package/dist/assets/{MarkstreamRendererImpl-RY_rAgKD.js → MarkstreamRendererImpl-C4rgOuOP.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-DgtRh0zu.js → MobileDetailNavigation-D5tcF2Kg.js} +1 -1
- package/dist/assets/{MobileShareBridge-BM3cN-gG.js → MobileShareBridge-Bm0JZKHc.js} +3 -3
- package/dist/assets/MobileSurface-BzzBCVkG.js +2 -0
- package/dist/assets/{MultiRunWindow-Bq4gz78C.js → MultiRunWindow-B6Sb3aEG.js} +1 -1
- package/dist/assets/{OnboardingScreen-DMbPMwlp.js → OnboardingScreen-BLjZIBOs.js} +1 -1
- package/dist/assets/{OverlayScrollbar-mI9WVa7E.js → OverlayScrollbar-BOqRklf0.js} +1 -1
- package/dist/assets/SettingsView-Ds6x2z0F.js +25 -0
- package/dist/assets/{SettingsWindow-CHPjbEnI.js → SettingsWindow-D6CN5ahJ.js} +1 -1
- package/dist/assets/{TerminalView-DCJuL2nP.js → TerminalView-DkzaJJfh.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Cg_Q1t3X.js → ToolOutputDialog-BegFpg1h.js} +1 -1
- package/dist/assets/{appThemeRegistry-BxuflaQt.js → appThemeRegistry-BMzuqD8_.js} +1 -1
- package/dist/assets/{button-e8fYWIMM.js → button-Ck8ipzBm.js} +1 -1
- package/dist/assets/{collapsible-B6wLXHi3.js → collapsible-nU99prkD.js} +1 -1
- package/dist/assets/{context-menu-Bq7W1d7y.js → context-menu-DMzZN1oi.js} +1 -1
- package/dist/assets/{desktopHosts-C8vf9vRY.js → desktopHosts-DuftEj8B.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DDJtljeu.js → desktopRelayRestore-g378BdZf.js} +1 -1
- package/dist/assets/{diagram-BBL-LfsC.js → diagram-CvO4P8ds.js} +1 -1
- package/dist/assets/{dialog-MRsJpHzu.js → dialog-ByGUkS3c.js} +1 -1
- package/dist/assets/{es-DvPwAj0y.js → es-DCjl24dV.js} +3 -3
- package/dist/assets/fileMentionTouchSelection-Cuk9C_jN.js +2 -0
- package/dist/assets/{fileQueries-CBu-ytXS.js → fileQueries-DuF89nFO.js} +1 -1
- package/dist/assets/{fr-XQsoaNKY.js → fr-DfJ5j0t5.js} +3 -3
- package/dist/assets/{gitApi-CuIUGt04.js → gitApi-BlWYyRdx.js} +3 -3
- package/dist/assets/{input-DbOe1mdT.js → input-CXhFmHcq.js} +1 -1
- package/dist/assets/insertionBoundaries-lAwXo-R-.js +1 -0
- package/dist/assets/{ja-DslzO96s.js → ja-B_9_w2Xf.js} +3 -3
- package/dist/assets/{ko-C_9XOAL0.js → ko-DlGbo3kV.js} +3 -3
- package/dist/assets/{main-Sd4Auh_x.js → main-BmiKRvkB.js} +2 -2
- package/dist/assets/main-D5SdJE5d.js +169 -0
- package/dist/assets/main-qSeeJRW1.css +1 -0
- package/dist/assets/{markdownHeightCache-B5VSLl79.js → markdownHeightCache-g_UsWzbA.js} +1 -1
- package/dist/assets/{mcpQueries-BYNrkehM.js → mcpQueries-0Oe-8vHC.js} +1 -1
- package/dist/assets/{miniChat-Hp9bNmS0.js → miniChat-CSwFHa9K.js} +2 -2
- package/dist/assets/mobile-DzJO7usb.js +2 -0
- package/dist/assets/multirun-Bdd0yE3M.js +3 -0
- package/dist/assets/persistence-Dnj1QOdX.js +3 -0
- package/dist/assets/{pl-Dst9Wk53.js → pl-DjUsS8Ei.js} +3 -3
- package/dist/assets/platform-pfYy32vr.js +6 -0
- package/dist/assets/projectMeta-2dbON46t.js +1 -0
- package/dist/assets/{pt-BR-Dq394BPb.js → pt-BR-B8sV7qo1.js} +3 -3
- package/dist/assets/renderElectronMiniChatApp-Bjf5yS-2.js +2 -0
- package/dist/assets/renderMobileApp-Bv0VxQo1.js +28 -0
- package/dist/assets/{runtimeConfig-pTYamMLy.js → runtimeConfig-BOk04YEP.js} +1 -1
- package/dist/assets/runtimeEndpointReset-DsuP5FPT.css +1 -0
- package/dist/assets/runtimeEndpointReset-Oo4f7T5P.js +148 -0
- package/dist/assets/{sessionChangeSummary-DcnZyuWt.js → sessionChangeSummary-Bf5wJLSv.js} +2 -2
- package/dist/assets/{sessionLookup-B3t-FerN.js → sessionLookup-pga3-Etf.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-B0sZ7mm6.js → sortable-tabs-strip-CHqMkTI3.js} +1 -1
- package/dist/assets/{store-D8fmTWK3.js → store-Cuktref-.js} +3 -3
- package/dist/assets/{terminalApi-CJyWCuc8.js → terminalApi-BgGraKxs.js} +1 -1
- package/dist/assets/{textarea-B5GMntHk.js → textarea-B0Od84ZM.js} +1 -1
- package/dist/assets/{tooltip-C64SdRvR.js → tooltip-BKL7J5hx.js} +1 -1
- package/dist/assets/{uk-8XAR75Qh.js → uk-Bh7HXyLp.js} +3 -3
- package/dist/assets/{url-DF1NFwt_.js → url-DNRMq_0R.js} +1 -1
- package/dist/assets/{useAppFontEffects-BQklk6XB.js → useAppFontEffects-gMl7z7An.js} +53 -53
- package/dist/assets/{useAssistantUIStore-B75cQm_z.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-GOgauAay.js +1 -0
- package/dist/assets/{useEffectiveDirectory-Ddx7sZ5A.js → useEffectiveDirectory-DgAB7HWq.js} +1 -1
- package/dist/assets/{useFontPreferences-CrteQiAp.js → useFontPreferences-Djxu_uRy.js} +1 -1
- package/dist/assets/useMobileNavigationStore-D7uAoBXe.js +415 -0
- package/dist/assets/useSessionAutoCleanup-Cg7KJFGa.js +2 -0
- package/dist/assets/{useUIStore-Brsa2mKs.js → useUIStore-Dfx6TKvi.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-VOtVUGkO.js → useWorkerHighlightedLines-DKa2EWBH.js} +1 -1
- package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-DzRqcymP.js} +1 -1
- package/dist/assets/{zh-CN-B-ILfrqC.js → zh-CN-Uzia0gk4.js} +3 -3
- package/dist/assets/{zh-TW-5HeTVP4z.js → zh-TW-CiI7DLdB.js} +3 -3
- package/dist/index.html +10 -10
- package/dist/mini-chat.html +10 -10
- package/dist/mobile.html +10 -10
- package/package.json +3 -3
- package/server/index.js +8 -20
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +7 -4
- package/server/lib/git/routes.test.js +34 -0
- package/server/lib/git/service.js +3 -1
- package/server/lib/llm/DOCUMENTATION.md +6 -4
- package/server/lib/llm/generate.js +8 -9
- package/server/lib/llm/generate.test.js +33 -6
- package/server/lib/opencode/DOCUMENTATION.md +24 -20
- package/server/lib/opencode/agents.js +13 -22
- package/server/lib/opencode/auth-state-runtime.js +3 -3
- package/server/lib/opencode/auth-state-runtime.test.js +59 -0
- package/server/lib/opencode/auth.d.ts +13 -0
- package/server/lib/opencode/auth.js +74 -64
- package/server/lib/opencode/auth.test.js +57 -0
- package/server/lib/opencode/commands.js +1 -1
- package/server/lib/opencode/config-entities-layering.test.js +72 -0
- package/server/lib/opencode/config-entity-routes.js +9 -122
- package/server/lib/opencode/config-entity-routes.test.js +7 -11
- package/server/lib/opencode/config-files.d.ts +17 -0
- package/server/lib/opencode/config-files.js +187 -0
- package/server/lib/opencode/config-files.test.js +201 -0
- package/server/lib/opencode/core-routes.js +0 -1
- package/server/lib/opencode/core-routes.test.js +0 -15
- package/server/lib/opencode/credential-boundary.d.ts +1 -0
- package/server/lib/opencode/credential-boundary.js +11 -0
- package/server/lib/opencode/ensure-cli.js +4 -1
- package/server/lib/opencode/env-config.js +4 -1
- package/server/lib/opencode/env-config.test.js +28 -0
- package/server/lib/opencode/env-runtime.test.js +2 -1
- package/server/lib/opencode/feature-routes-runtime.js +9 -7
- package/server/lib/opencode/hmr-state-runtime.js +4 -4
- package/server/lib/opencode/lifecycle.js +1 -49
- package/server/lib/opencode/lifecycle.test.js +30 -4
- package/server/lib/opencode/location-release.d.ts +8 -0
- package/server/lib/opencode/location-release.js +32 -0
- package/server/lib/opencode/location-release.test.js +43 -0
- package/server/lib/opencode/mcp.js +1 -1
- package/server/lib/opencode/opencode2-pin.js +2 -1
- package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
- package/server/lib/opencode/plugin-routes.test.js +4 -2
- package/server/lib/opencode/plugins.js +12 -60
- package/server/lib/opencode/plugins.test.js +5 -3
- package/server/lib/opencode/providers.js +6 -3
- package/server/lib/opencode/proxy.js +7 -0
- package/server/lib/opencode/proxy.test.js +13 -0
- package/server/lib/opencode/routes.js +5 -5
- package/server/lib/opencode/routes.test.js +18 -18
- package/server/lib/opencode/runtime-contract.js +3 -2
- package/server/lib/opencode/runtime-contract.test.js +12 -12
- package/server/lib/opencode/shared.js +6 -175
- package/server/lib/opencode/upgrade-screen.js +4 -11
- package/server/lib/opencode/upgrade-screen.test.js +25 -25
- package/server/lib/opencode/version-alignment.test.js +40 -0
- package/server/lib/quota/DOCUMENTATION.md +9 -0
- package/server/lib/quota/provider-credentials.test.js +29 -0
- package/server/lib/quota/providers/claude.js +4 -4
- package/server/lib/quota/providers/codex.js +4 -4
- package/server/lib/quota/providers/codex.test.js +1 -1
- package/server/lib/quota/providers/copilot.js +5 -5
- package/server/lib/quota/providers/google/auth.js +3 -3
- package/server/lib/quota/providers/google/index.js +2 -2
- package/server/lib/quota/providers/index.js +3 -7
- package/server/lib/quota/providers/index.test.js +2 -2
- package/server/lib/quota/providers/kimi.js +4 -4
- package/server/lib/quota/providers/minimax-shared.js +4 -4
- package/server/lib/quota/providers/nanogpt.js +4 -4
- package/server/lib/quota/providers/openai.js +4 -4
- package/server/lib/quota/providers/openrouter.js +4 -4
- package/server/lib/quota/providers/wafer.js +4 -4
- package/server/lib/quota/providers/zai.js +4 -4
- package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
- package/server/lib/quota/routes.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +1 -0
- package/server/lib/scheduled-tasks/runtime.js +2 -3
- package/server/lib/session-index/DOCUMENTATION.md +7 -0
- package/server/lib/session-index/event-ingest.js +13 -0
- package/server/lib/session-index/event-ingest.test.js +7 -0
- package/server/lib/session-index/service.js +45 -0
- package/server/lib/session-index/service.test.js +47 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +6 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
- package/server/lib/session-metadata/session-projection.d.ts +1 -0
- package/server/lib/session-metadata/session-projection.js +23 -0
- package/server/lib/session-metadata/session-projection.test.js +35 -0
- package/server/lib/session-title/DOCUMENTATION.md +36 -8
- package/server/lib/session-title/native-runtime.test.js +42 -0
- package/server/lib/session-title/runtime.js +18 -8
- package/server/lib/session-title/session-access.js +108 -3
- package/server/lib/session-title/session-access.test.js +125 -7
- package/server/lib/tts/DOCUMENTATION.md +7 -5
- package/server/lib/tts/credential-source.test.js +23 -0
- package/server/lib/tts/routes.js +2 -2
- package/server/lib/tts/service.js +16 -37
- package/dist/assets/MobileSurface-ocVWenHA.js +0 -2
- package/dist/assets/SettingsView-CIZz40Jg.js +0 -25
- package/dist/assets/fileMentionTouchSelection-Bj67pPo7.js +0 -3
- package/dist/assets/insertionBoundaries-nzs1Q3YR.js +0 -1
- package/dist/assets/main-D2BzpnUt.css +0 -1
- package/dist/assets/main-DjilfYN7.js +0 -169
- package/dist/assets/mobile-fBLEOx9_.js +0 -2
- package/dist/assets/multirun-D7W7oCj4.js +0 -4
- package/dist/assets/persistence-CMWO3jFo.js +0 -3
- package/dist/assets/platform-B0p43squ.js +0 -6
- package/dist/assets/projectMeta-DrUKrYt0.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-D1xipCO5.js +0 -2
- package/dist/assets/renderMobileApp-DSk7Qs-_.js +0 -28
- package/dist/assets/runtimeEndpointReset-B5HEMuiB.js +0 -149
- package/dist/assets/useDesktopWindowControlsLayout-CrzYwZDQ.js +0 -1
- package/dist/assets/useMobileNavigationStore-i4cXNoOf.js +0 -414
- package/dist/assets/useSessionAutoCleanup-Dzd0fGSe.js +0 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +0 -12
- package/server/lib/opencode/snippets.js +0 -242
- package/server/lib/opencode/snippets.test.js +0 -97
|
@@ -2,18 +2,18 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getGlobalConfigDirectory, readConfigFile, isPlainObject, readConfigLayers,
|
|
7
|
+
readConfig, getConfigForPath, writeConfig, getJsonEntrySource, getJsonWriteTarget,
|
|
8
|
+
} from './config-files.js';
|
|
6
9
|
|
|
7
10
|
// ============== PATH CONSTANTS ==============
|
|
8
11
|
|
|
9
|
-
const OPENCODE_CONFIG_DIR =
|
|
12
|
+
const OPENCODE_CONFIG_DIR = getGlobalConfigDirectory();
|
|
10
13
|
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
|
|
11
14
|
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
|
|
12
15
|
const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skills');
|
|
13
|
-
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, '
|
|
14
|
-
const CUSTOM_CONFIG_FILE = process.env.OPENCODE_CONFIG
|
|
15
|
-
? path.resolve(process.env.OPENCODE_CONFIG)
|
|
16
|
-
: null;
|
|
16
|
+
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
|
|
17
17
|
const PROMPT_FILE_PATTERN = /^\{file:(.+)\}$/i;
|
|
18
18
|
|
|
19
19
|
// ============== SCOPE TYPE CONSTANTS ==============
|
|
@@ -89,175 +89,6 @@ function writeMdFile(filePath, frontmatter, body) {
|
|
|
89
89
|
|
|
90
90
|
// ============== CONFIG FILE OPERATIONS ==============
|
|
91
91
|
|
|
92
|
-
function getProjectConfigCandidates(workingDirectory) {
|
|
93
|
-
if (!workingDirectory) return [];
|
|
94
|
-
return [
|
|
95
|
-
path.join(workingDirectory, 'opencode.json'),
|
|
96
|
-
path.join(workingDirectory, 'opencode.jsonc'),
|
|
97
|
-
path.join(workingDirectory, '.opencode', 'opencode.json'),
|
|
98
|
-
path.join(workingDirectory, '.opencode', 'opencode.jsonc'),
|
|
99
|
-
];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function getProjectConfigPath(workingDirectory) {
|
|
103
|
-
if (!workingDirectory) return null;
|
|
104
|
-
|
|
105
|
-
const candidates = getProjectConfigCandidates(workingDirectory);
|
|
106
|
-
|
|
107
|
-
for (const candidate of candidates) {
|
|
108
|
-
if (fs.existsSync(candidate)) {
|
|
109
|
-
return candidate;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return candidates[0];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function getConfigPaths(workingDirectory) {
|
|
117
|
-
return {
|
|
118
|
-
userPaths: [
|
|
119
|
-
path.join(OPENCODE_CONFIG_DIR, 'config.json'),
|
|
120
|
-
path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
|
|
121
|
-
path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
|
|
122
|
-
],
|
|
123
|
-
projectPath: getProjectConfigPath(workingDirectory),
|
|
124
|
-
customPath: CUSTOM_CONFIG_FILE
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getPrimaryUserConfigPath(userPaths) {
|
|
129
|
-
for (const userPath of userPaths) {
|
|
130
|
-
if (fs.existsSync(userPath)) {
|
|
131
|
-
return userPath;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return CONFIG_FILE;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function readConfigFile(filePath) {
|
|
139
|
-
if (!filePath || !fs.existsSync(filePath)) {
|
|
140
|
-
return {};
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
144
|
-
const normalized = content.trim();
|
|
145
|
-
if (!normalized) {
|
|
146
|
-
return {};
|
|
147
|
-
}
|
|
148
|
-
return parseJsonc(normalized, [], { allowTrailingComma: true });
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error(`Failed to read config file: ${filePath}`, error);
|
|
151
|
-
throw new Error('Failed to read OpenCode configuration');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function isPlainObject(value) {
|
|
156
|
-
return value && typeof value === 'object' && !Array.isArray(value);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function mergeConfigs(base, override) {
|
|
160
|
-
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
161
|
-
return override;
|
|
162
|
-
}
|
|
163
|
-
const result = { ...base };
|
|
164
|
-
for (const [key, value] of Object.entries(override)) {
|
|
165
|
-
if (key in result) {
|
|
166
|
-
const baseValue = result[key];
|
|
167
|
-
if (isPlainObject(baseValue) && isPlainObject(value)) {
|
|
168
|
-
result[key] = mergeConfigs(baseValue, value);
|
|
169
|
-
} else {
|
|
170
|
-
result[key] = value;
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
result[key] = value;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function readConfigLayers(workingDirectory) {
|
|
180
|
-
const { userPaths, projectPath, customPath } = getConfigPaths(workingDirectory);
|
|
181
|
-
const userPath = getPrimaryUserConfigPath(userPaths);
|
|
182
|
-
const userConfig = readConfigFile(userPath);
|
|
183
|
-
const projectConfig = readConfigFile(projectPath);
|
|
184
|
-
const customConfig = readConfigFile(customPath);
|
|
185
|
-
const mergedConfig = mergeConfigs(mergeConfigs(userConfig, projectConfig), customConfig);
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
userConfig,
|
|
189
|
-
projectConfig,
|
|
190
|
-
customConfig,
|
|
191
|
-
mergedConfig,
|
|
192
|
-
paths: { userPath, projectPath, customPath }
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function readConfig(workingDirectory) {
|
|
197
|
-
return readConfigLayers(workingDirectory).mergedConfig;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function getConfigForPath(layers, targetPath) {
|
|
201
|
-
if (!targetPath) {
|
|
202
|
-
return layers.userConfig;
|
|
203
|
-
}
|
|
204
|
-
if (layers.paths.customPath && targetPath === layers.paths.customPath) {
|
|
205
|
-
return layers.customConfig;
|
|
206
|
-
}
|
|
207
|
-
if (layers.paths.projectPath && targetPath === layers.paths.projectPath) {
|
|
208
|
-
return layers.projectConfig;
|
|
209
|
-
}
|
|
210
|
-
return layers.userConfig;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function writeConfig(config, filePath = CONFIG_FILE) {
|
|
214
|
-
try {
|
|
215
|
-
if (fs.existsSync(filePath)) {
|
|
216
|
-
const backupFile = `${filePath}.openchamber.backup`;
|
|
217
|
-
fs.copyFileSync(filePath, backupFile);
|
|
218
|
-
console.log(`Created config backup: ${backupFile}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
222
|
-
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf8');
|
|
223
|
-
console.log(`Successfully wrote config file: ${filePath}`);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error(`Failed to write config file: ${filePath}`, error);
|
|
226
|
-
throw new Error('Failed to write OpenCode configuration');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function getJsonEntrySource(layers, sectionKey, entryName) {
|
|
231
|
-
const { userConfig, projectConfig, customConfig, paths } = layers;
|
|
232
|
-
const customSection = customConfig?.[sectionKey]?.[entryName];
|
|
233
|
-
if (customSection !== undefined) {
|
|
234
|
-
return { section: customSection, config: customConfig, path: paths.customPath, exists: true };
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const projectSection = projectConfig?.[sectionKey]?.[entryName];
|
|
238
|
-
if (projectSection !== undefined) {
|
|
239
|
-
return { section: projectSection, config: projectConfig, path: paths.projectPath, exists: true };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const userSection = userConfig?.[sectionKey]?.[entryName];
|
|
243
|
-
if (userSection !== undefined) {
|
|
244
|
-
return { section: userSection, config: userConfig, path: paths.userPath, exists: true };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return { section: null, config: null, path: null, exists: false };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function getJsonWriteTarget(layers, preferredScope) {
|
|
251
|
-
const { userConfig, projectConfig, customConfig, paths } = layers;
|
|
252
|
-
if (paths.customPath) {
|
|
253
|
-
return { config: customConfig, path: paths.customPath };
|
|
254
|
-
}
|
|
255
|
-
if (preferredScope === AGENT_SCOPE.PROJECT && paths.projectPath) {
|
|
256
|
-
return { config: projectConfig, path: paths.projectPath };
|
|
257
|
-
}
|
|
258
|
-
return { config: userConfig, path: paths.userPath };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
92
|
// ============== GIT/WORKTREE HELPERS ==============
|
|
262
93
|
|
|
263
94
|
function getAncestors(startDir, stopDir) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Startup upgrade screen for OpenCode below
|
|
2
|
+
* Startup upgrade screen for OpenCode below the runtime contract minimum.
|
|
3
3
|
*
|
|
4
4
|
* This module does not own process lifecycle. It classifies the version the
|
|
5
5
|
* existing startup path already detected, and installs through the owned-cache
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
PINNED_OPENCODE2_VERSION,
|
|
12
|
+
MINIMUM_OPENCODE2_VERSION,
|
|
12
13
|
isAcceptableOpenCode2HealthVersion,
|
|
13
14
|
isOpenCode1xVersion,
|
|
14
15
|
isOpenCode2VersionAtLeast,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
} from './opencode2-pin.js';
|
|
17
18
|
|
|
18
19
|
/** Oldest OpenCode this OpenChamber build will enter a session against. */
|
|
19
|
-
export const REQUIRED_OPENCODE_VERSION =
|
|
20
|
+
export const REQUIRED_OPENCODE_VERSION = MINIMUM_OPENCODE2_VERSION;
|
|
20
21
|
|
|
21
22
|
const normalizeVersion = (value) => {
|
|
22
23
|
if (typeof value !== 'string') return null;
|
|
@@ -68,14 +69,6 @@ export function platformSupportsUpgradeScreenInstall(platform = process.platform
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const pickDetectedVersion = (serveVersion, cliVersion) => {
|
|
72
|
-
const serve = normalizeVersion(serveVersion);
|
|
73
|
-
const cli = normalizeVersion(cliVersion);
|
|
74
|
-
if (serve && !isUpgradeScreenSatisfied(serve)) return serve;
|
|
75
|
-
if (serve) return serve;
|
|
76
|
-
return cli;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
72
|
/**
|
|
80
73
|
* @param {{
|
|
81
74
|
* version?: string | null,
|
|
@@ -156,7 +149,7 @@ async function readUpgradeScreenStatus(deps) {
|
|
|
156
149
|
? await Promise.resolve(deps.readCliVersion()).catch(() => null)
|
|
157
150
|
: null;
|
|
158
151
|
return describeUpgradeScreen({
|
|
159
|
-
version:
|
|
152
|
+
version: serveVersion || (typeof deps.readServeVersion === 'function' ? null : cliVersion),
|
|
160
153
|
installation,
|
|
161
154
|
platformCanInstall: typeof deps.platformCanInstall === 'boolean'
|
|
162
155
|
? deps.platformCanInstall
|
|
@@ -11,26 +11,26 @@ import {
|
|
|
11
11
|
} from './upgrade-screen.js';
|
|
12
12
|
|
|
13
13
|
describe('upgrade screen version gate', () => {
|
|
14
|
-
it('uses
|
|
15
|
-
expect(REQUIRED_OPENCODE_VERSION).toBe('2.0.
|
|
16
|
-
expect(resolveUpgradeScreenTarget('2.0.12')).toBe('2.0.
|
|
17
|
-
expect(resolveUpgradeScreenTarget('2.0.
|
|
14
|
+
it('uses the floor even when the runtime pin is older', () => {
|
|
15
|
+
expect(REQUIRED_OPENCODE_VERSION).toBe('2.0.20');
|
|
16
|
+
expect(resolveUpgradeScreenTarget('2.0.12')).toBe('2.0.20');
|
|
17
|
+
expect(resolveUpgradeScreenTarget('2.0.23')).toBe('2.0.23');
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it('does not open the gate at or above 2.0.
|
|
20
|
+
it('does not open the gate at or above 2.0.20', () => {
|
|
21
21
|
expect(describeUpgradeScreen({
|
|
22
|
-
version: '2.0.
|
|
22
|
+
version: '2.0.20',
|
|
23
23
|
installation: 'managed',
|
|
24
24
|
platformCanInstall: true,
|
|
25
|
-
})).toMatchObject({ state: 'compatible', canInstall: false, minimumVersion: '2.0.
|
|
25
|
+
})).toMatchObject({ state: 'compatible', canInstall: false, minimumVersion: '2.0.20' });
|
|
26
26
|
expect(describeUpgradeScreen({
|
|
27
|
-
version: 'v2.0.
|
|
27
|
+
version: 'v2.0.23',
|
|
28
28
|
installation: 'managed',
|
|
29
29
|
platformCanInstall: true,
|
|
30
30
|
}).state).toBe('compatible');
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('opens the gate below 2.0.
|
|
33
|
+
it('opens the gate below 2.0.20 and offers install only when this runtime can switch', () => {
|
|
34
34
|
expect(describeUpgradeScreen({
|
|
35
35
|
version: '2.0.14',
|
|
36
36
|
installation: 'managed',
|
|
@@ -68,17 +68,17 @@ describe('upgrade screen version gate', () => {
|
|
|
68
68
|
|
|
69
69
|
it('does not report an unverified install as upgraded', () => {
|
|
70
70
|
expect(evaluateUpgradeScreenInstallResult({
|
|
71
|
-
targetVersion: '2.0.
|
|
71
|
+
targetVersion: '2.0.23',
|
|
72
72
|
serveVersion: '2.0.14',
|
|
73
73
|
})).toMatchObject({ ok: false, upgraded: false, version: '2.0.14' });
|
|
74
74
|
expect(evaluateUpgradeScreenInstallResult({
|
|
75
|
-
targetVersion: '2.0.
|
|
75
|
+
targetVersion: '2.0.23',
|
|
76
76
|
serveVersion: null,
|
|
77
77
|
}).upgraded).toBe(false);
|
|
78
78
|
expect(evaluateUpgradeScreenInstallResult({
|
|
79
|
-
targetVersion: '2.0.
|
|
80
|
-
serveVersion: '2.0.
|
|
81
|
-
})).toMatchObject({ ok: true, upgraded: true, version: '2.0.
|
|
79
|
+
targetVersion: '2.0.23',
|
|
80
|
+
serveVersion: '2.0.23',
|
|
81
|
+
})).toMatchObject({ ok: true, upgraded: true, version: '2.0.23' });
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -94,7 +94,7 @@ describe('installRequiredOpenCode', () => {
|
|
|
94
94
|
calls.push('install');
|
|
95
95
|
return '/cache/opencode';
|
|
96
96
|
}),
|
|
97
|
-
readBinaryVersion: vi.fn(() => '2.0.
|
|
97
|
+
readBinaryVersion: vi.fn(() => '2.0.23'),
|
|
98
98
|
persistBinary: vi.fn(async () => {
|
|
99
99
|
calls.push('persist');
|
|
100
100
|
}),
|
|
@@ -114,20 +114,20 @@ describe('installRequiredOpenCode', () => {
|
|
|
114
114
|
return { deps, calls };
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
-
it('installs 2.0.
|
|
117
|
+
it('installs 2.0.23 and only succeeds after the running serve is verified', async () => {
|
|
118
118
|
const { deps, calls } = createDeps({
|
|
119
119
|
readServeVersion: vi.fn()
|
|
120
120
|
.mockResolvedValueOnce('2.0.14')
|
|
121
|
-
.mockResolvedValueOnce('2.0.
|
|
121
|
+
.mockResolvedValueOnce('2.0.23'),
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
await expect(installRequiredOpenCode(deps)).resolves.toMatchObject({
|
|
125
125
|
ok: true,
|
|
126
126
|
upgraded: true,
|
|
127
|
-
version: '2.0.
|
|
128
|
-
targetVersion: '2.0.
|
|
127
|
+
version: '2.0.23',
|
|
128
|
+
targetVersion: '2.0.23',
|
|
129
129
|
});
|
|
130
|
-
expect(deps.install).toHaveBeenCalledWith({ version: '2.0.
|
|
130
|
+
expect(deps.install).toHaveBeenCalledWith({ version: '2.0.23' });
|
|
131
131
|
expect(calls).toEqual(['install', 'persist', 'force', 'restart', 'ready']);
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -137,7 +137,7 @@ describe('installRequiredOpenCode', () => {
|
|
|
137
137
|
.mockResolvedValueOnce('1.18.30')
|
|
138
138
|
.mockResolvedValueOnce('1.18.30'),
|
|
139
139
|
readCliVersion: () => '1.18.30',
|
|
140
|
-
readBinaryVersion: () => '2.0.
|
|
140
|
+
readBinaryVersion: () => '2.0.23',
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
await expect(installRequiredOpenCode(deps)).rejects.toThrow(/still 1\.18\.30/);
|
|
@@ -161,11 +161,11 @@ describe('installRequiredOpenCode', () => {
|
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
describe('upgrade screen routes', () => {
|
|
164
|
-
it('hides the gate for 2.0.
|
|
164
|
+
it('hides the gate for 2.0.20 and reports install failure without upgraded:true', async () => {
|
|
165
165
|
const app = express();
|
|
166
166
|
app.use(express.json());
|
|
167
167
|
const readServeVersion = vi.fn()
|
|
168
|
-
.mockResolvedValueOnce('2.0.
|
|
168
|
+
.mockResolvedValueOnce('2.0.20')
|
|
169
169
|
.mockResolvedValueOnce('2.0.12')
|
|
170
170
|
.mockResolvedValueOnce('2.0.12');
|
|
171
171
|
registerUpgradeScreenRoutes(app, {
|
|
@@ -176,14 +176,14 @@ describe('upgrade screen routes', () => {
|
|
|
176
176
|
install: vi.fn(async () => {
|
|
177
177
|
throw new Error('registry unreachable');
|
|
178
178
|
}),
|
|
179
|
-
readBinaryVersion: () => '2.0.
|
|
179
|
+
readBinaryVersion: () => '2.0.23',
|
|
180
180
|
persistBinary: vi.fn(),
|
|
181
181
|
forceBinary: vi.fn(),
|
|
182
182
|
restart: vi.fn(),
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
const compatible = await request(app).get('/api/opencode/compatibility').expect(200);
|
|
186
|
-
expect(compatible.body).toMatchObject({ state: 'compatible', version: '2.0.
|
|
186
|
+
expect(compatible.body).toMatchObject({ state: 'compatible', version: '2.0.20', minimumVersion: '2.0.20' });
|
|
187
187
|
|
|
188
188
|
const failed = await request(app).post('/api/opencode/install-required').expect(500);
|
|
189
189
|
expect(failed.body).toMatchObject({
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { MINIMUM_OPENCODE2_VERSION, PINNED_OPENCODE2_VERSION } from './opencode2-pin.js';
|
|
3
|
+
import { evaluateRuntimeContract } from './runtime-contract.js';
|
|
4
|
+
import { describeUpgradeScreen, installRequiredOpenCode, resolveUpgradeScreenTarget } from './upgrade-screen.js';
|
|
5
|
+
import { ensurePinnedOpenCode2Cli } from './ensure-cli.js';
|
|
6
|
+
|
|
7
|
+
describe('v2 version alignment', () => {
|
|
8
|
+
it.each(['1.18.30', '2.0.19', null, 'unknown'])('blocks execution and startup for %s', (version) => {
|
|
9
|
+
expect(evaluateRuntimeContract({ serveVersion: version, cliVersion: '2.0.23', reachable: true, authenticated: true }).executionAllowed).toBe(false);
|
|
10
|
+
expect(describeUpgradeScreen({ version }).state).not.toBe('compatible');
|
|
11
|
+
});
|
|
12
|
+
it.each(['2.0.20', '2.0.23', '2.1.0'])('admits running %s independently of CLI pin', (version) => {
|
|
13
|
+
expect(evaluateRuntimeContract({ serveVersion: version, cliVersion: '2.0.15', reachable: true, authenticated: true })).toMatchObject({ executionAllowed: true, versionMismatch: true, minVerifiedVersion: '2.0.20' });
|
|
14
|
+
expect(describeUpgradeScreen({ version }).state).toBe('compatible');
|
|
15
|
+
});
|
|
16
|
+
it('installs the audited pin on first install and preserves newer discovered CLIs', async () => {
|
|
17
|
+
expect(MINIMUM_OPENCODE2_VERSION).toBe('2.0.20');
|
|
18
|
+
expect(PINNED_OPENCODE2_VERSION).toBe('2.0.23');
|
|
19
|
+
expect(resolveUpgradeScreenTarget()).toBe('2.0.23');
|
|
20
|
+
const install = vi.fn(async () => '/owned/opencode');
|
|
21
|
+
await expect(ensurePinnedOpenCode2Cli({ readVersion: (path) => path === '/owned/opencode' ? '2.0.23' : '', install })).resolves.toMatchObject({ version: '2.0.23', installed: true });
|
|
22
|
+
expect(install).toHaveBeenCalledWith(expect.objectContaining({ version: '2.0.23' }));
|
|
23
|
+
install.mockClear();
|
|
24
|
+
await expect(ensurePinnedOpenCode2Cli({ discoveredPath: '/global/opencode', readVersion: () => '2.1.0', install })).resolves.toMatchObject({ version: '2.1.0', installed: false });
|
|
25
|
+
expect(install).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
it('does not infer a successful installation from an unreadable binary', async () => {
|
|
28
|
+
await expect(ensurePinnedOpenCode2Cli({ readVersion: () => '', install: async () => '/owned/opencode' })).rejects.toThrow('got unknown');
|
|
29
|
+
});
|
|
30
|
+
it('verifies the live service after explicit install, and rolls selection back on failure', async () => {
|
|
31
|
+
const persistBinary = vi.fn();
|
|
32
|
+
const restart = vi.fn();
|
|
33
|
+
const deps = { resolveOwnership: async () => ({ ownership: 'shared-service' }), readServeVersion: vi.fn().mockResolvedValueOnce('2.0.19').mockResolvedValueOnce('2.0.23'), readCliVersion: () => '2.0.23', platformCanInstall: true, install: async () => '/owned/opencode', readBinaryVersion: () => '2.0.23', persistBinary, restart, isSharedService: () => true };
|
|
34
|
+
await expect(installRequiredOpenCode(deps)).resolves.toMatchObject({ upgraded: true, version: '2.0.23' });
|
|
35
|
+
expect(restart).toHaveBeenCalledWith({ binaryPath: '/owned/opencode' });
|
|
36
|
+
deps.readServeVersion.mockResolvedValue('2.0.19');
|
|
37
|
+
await expect(installRequiredOpenCode(deps)).rejects.toMatchObject({ code: 'UPGRADE_SCREEN_NOT_VERIFIED' });
|
|
38
|
+
expect(persistBinary).toHaveBeenLastCalledWith('');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -38,6 +38,15 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
|
|
|
38
38
|
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
|
|
39
39
|
|
|
40
40
|
## Response contract
|
|
41
|
+
Provider credentials are read asynchronously from the current OpenCode service via
|
|
42
|
+
`opencode/auth.js`; only the selected credential is used. Account switching and
|
|
43
|
+
logout are observed on the next request, without file fallback or persisted secret
|
|
44
|
+
cache. Credential-list transport/auth/schema failure rejects the configured-provider
|
|
45
|
+
list instead of returning an authoritative empty list; per-provider quota requests
|
|
46
|
+
return `ok: false` with a sanitized error. VS Code uses the same credential adapter.
|
|
47
|
+
OpenChamber-owned quota cookies and Grok/Antigravity-specific stores retain their
|
|
48
|
+
separate explicit sources; they are not legacy OpenCode `auth.json` fallbacks.
|
|
49
|
+
|
|
41
50
|
All providers should return results via shared helpers to preserve API shape:
|
|
42
51
|
- Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
|
|
43
52
|
- Optional field: `error`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
2
|
+
import { configureOpenCodeCredentials } from '../opencode/auth.js';
|
|
3
|
+
import { fetchQuota as fetchCodexQuota } from './providers/codex.js';
|
|
4
|
+
import { fetchQuotaForProvider, listConfiguredQuotaProviders } from './providers/index.js';
|
|
5
|
+
|
|
6
|
+
afterEach(() => { configureOpenCodeCredentials(null); vi.unstubAllGlobals(); });
|
|
7
|
+
|
|
8
|
+
it('quota requests follow the current active account, then stop sending after logout', async () => {
|
|
9
|
+
let access = 'first-account';
|
|
10
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => access ? [{ id: 'selected', integrationID: 'openai', active: true, value: { type: 'oauth', access, refresh: 'refresh', expires: 123 } }] : [] } }));
|
|
11
|
+
const fetchMock = vi.fn(async () => Response.json({ rate_limit: { primary_window: { used_percent: 20 } } }));
|
|
12
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
13
|
+
await fetchCodexQuota();
|
|
14
|
+
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer first-account');
|
|
15
|
+
access = 'second-account';
|
|
16
|
+
await fetchCodexQuota();
|
|
17
|
+
expect(fetchMock.mock.calls[1][1].headers.Authorization).toBe('Bearer second-account');
|
|
18
|
+
access = '';
|
|
19
|
+
await expect(fetchCodexQuota()).resolves.toMatchObject({ ok: false, configured: false });
|
|
20
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('quota source failure remains an error with no upstream secret in the response', async () => {
|
|
24
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => { throw new Error('secret-from-upstream'); } } }));
|
|
25
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
26
|
+
const result = await fetchQuotaForProvider('codex');
|
|
27
|
+
expect(result).toMatchObject({ ok: false, error: 'Unable to read OpenCode credentials' });
|
|
28
|
+
expect(JSON.stringify(result)).not.toContain('secret-from-upstream');
|
|
29
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ export const providerId = 'claude';
|
|
|
12
12
|
export const providerName = 'Claude';
|
|
13
13
|
const aliases = ['anthropic', 'claude'];
|
|
14
14
|
|
|
15
|
-
export const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
export const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'codex';
|
|
|
14
14
|
export const providerName = 'Codex';
|
|
15
15
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.access || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const accessToken = entry?.access ?? entry?.token;
|
|
27
27
|
const accountId = entry?.accountId;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
vi.mock('../../opencode/auth.js', () => ({
|
|
4
|
-
|
|
4
|
+
readOpenCodeCredentials: async () => ({ openai: { access: 'test-token' } }),
|
|
5
5
|
}));
|
|
6
6
|
|
|
7
7
|
import { fetchQuota } from './codex.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -42,14 +42,14 @@ export const providerId = 'github-copilot';
|
|
|
42
42
|
export const providerName = 'GitHub Copilot';
|
|
43
43
|
const aliases = ['github-copilot', 'copilot'];
|
|
44
44
|
|
|
45
|
-
export const isConfigured = () => {
|
|
46
|
-
const auth =
|
|
45
|
+
export const isConfigured = async () => {
|
|
46
|
+
const auth = await readOpenCodeCredentials();
|
|
47
47
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
48
48
|
return Boolean(entry?.access || entry?.token);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export const fetchQuota = async () => {
|
|
52
|
-
const auth =
|
|
52
|
+
const auth = await readOpenCodeCredentials();
|
|
53
53
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
54
54
|
const accessToken = entry?.access ?? entry?.token;
|
|
55
55
|
|
|
@@ -107,7 +107,7 @@ export const providerIdAddon = 'github-copilot-addon';
|
|
|
107
107
|
export const providerNameAddon = 'GitHub Copilot Add-on';
|
|
108
108
|
|
|
109
109
|
export const fetchQuotaAddon = async () => {
|
|
110
|
-
const auth =
|
|
110
|
+
const auth = await readOpenCodeCredentials();
|
|
111
111
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
112
112
|
const accessToken = entry?.access ?? entry?.token;
|
|
113
113
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
asNonEmptyString,
|
|
15
15
|
toTimestamp
|
|
16
16
|
} from '../../utils/index.js';
|
|
17
|
-
import {
|
|
17
|
+
import { readOpenCodeCredentials } from '../../../opencode/auth.js';
|
|
18
18
|
import { parseGoogleRefreshToken } from './transforms.js';
|
|
19
19
|
|
|
20
20
|
const ANTIGRAVITY_GOOGLE_CLIENT_ID =
|
|
@@ -90,8 +90,8 @@ const resolveAntigravityAuth = () => {
|
|
|
90
90
|
return null;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
export const resolveGoogleAuthSources = () => {
|
|
94
|
-
const auth =
|
|
93
|
+
export const resolveGoogleAuthSources = async () => {
|
|
94
|
+
const auth = await readOpenCodeCredentials();
|
|
95
95
|
const sources = [];
|
|
96
96
|
|
|
97
97
|
const geminiAuth = resolveGeminiCliAuth(auth);
|
|
@@ -17,10 +17,10 @@ export const providerId = 'google';
|
|
|
17
17
|
export const providerName = 'Google';
|
|
18
18
|
export const aliases = ['google', 'google.oauth'];
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => resolveGoogleAuthSources().length > 0;
|
|
20
|
+
export const isConfigured = async () => (await resolveGoogleAuthSources()).length > 0;
|
|
21
21
|
|
|
22
22
|
export const fetchGoogleQuota = async () => {
|
|
23
|
-
const authSources = resolveGoogleAuthSources();
|
|
23
|
+
const authSources = await resolveGoogleAuthSources();
|
|
24
24
|
if (!authSources.length) {
|
|
25
25
|
return buildResult({
|
|
26
26
|
providerId,
|
|
@@ -130,16 +130,12 @@ const registry = {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
export const listConfiguredQuotaProviders = () => {
|
|
133
|
+
export const listConfiguredQuotaProviders = async () => {
|
|
134
134
|
const configured = [];
|
|
135
135
|
|
|
136
136
|
for (const [id, provider] of Object.entries(registry)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
configured.push(id);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
// Ignore provider-specific config errors in list API.
|
|
137
|
+
if (await provider.isConfigured()) {
|
|
138
|
+
configured.push(id);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -11,7 +11,7 @@ describe('quota provider registry', () => {
|
|
|
11
11
|
expect(typeof google.resolveGoogleAuthSources).toBe('function');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
15
|
-
expect(
|
|
14
|
+
it('reports unavailable credentials instead of an empty configured-provider list', async () => {
|
|
15
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'kimi-for-coding';
|
|
|
14
14
|
export const providerName = 'Kimi for Coding';
|
|
15
15
|
const aliases = ['kimi-for-coding', 'kimi'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|