better-opencode-openai-codex-auth 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.
- package/LICENSE +37 -0
- package/README.md +99 -0
- package/assets/opencode-logo-ornate-dark.svg +18 -0
- package/assets/readme-hero.svg +31 -0
- package/config/README.md +103 -0
- package/config/minimal-opencode.json +12 -0
- package/config/opencode-legacy.json +571 -0
- package/config/opencode-modern.json +239 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +334 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/account-pool.d.ts +17 -0
- package/dist/lib/account-pool.d.ts.map +1 -0
- package/dist/lib/account-pool.js +243 -0
- package/dist/lib/account-pool.js.map +1 -0
- package/dist/lib/auth/auth.d.ts +43 -0
- package/dist/lib/auth/auth.d.ts.map +1 -0
- package/dist/lib/auth/auth.js +162 -0
- package/dist/lib/auth/auth.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +17 -0
- package/dist/lib/auth/browser.d.ts.map +1 -0
- package/dist/lib/auth/browser.js +76 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/server.d.ts +10 -0
- package/dist/lib/auth/server.d.ts.map +1 -0
- package/dist/lib/auth/server.js +78 -0
- package/dist/lib/auth/server.js.map +1 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +53 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +67 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +67 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/logger.d.ts +21 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +77 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/oauth-success.html +712 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/codex-opencode-bridge.js +152 -0
- package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +27 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -0
- package/dist/lib/prompts/codex.js +252 -0
- package/dist/lib/prompts/codex.js.map +1 -0
- package/dist/lib/prompts/opencode-codex.d.ts +21 -0
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
- package/dist/lib/prompts/opencode-codex.js +91 -0
- package/dist/lib/prompts/opencode-codex.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +73 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/request/fetch-helpers.js +221 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -0
- package/dist/lib/request/helpers/input-utils.d.ts +6 -0
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
- package/dist/lib/request/helpers/input-utils.js +174 -0
- package/dist/lib/request/helpers/input-utils.js.map +1 -0
- package/dist/lib/request/helpers/model-map.d.ts +28 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
- package/dist/lib/request/helpers/model-map.js +126 -0
- package/dist/lib/request/helpers/model-map.js.map +1 -0
- package/dist/lib/request/request-transformer.d.ts +93 -0
- package/dist/lib/request/request-transformer.d.ts.map +1 -0
- package/dist/lib/request/request-transformer.js +418 -0
- package/dist/lib/request/request-transformer.js.map +1 -0
- package/dist/lib/request/response-handler.d.ts +14 -0
- package/dist/lib/request/response-handler.d.ts.map +1 -0
- package/dist/lib/request/response-handler.js +150 -0
- package/dist/lib/request/response-handler.js.map +1 -0
- package/dist/lib/types.d.ts +183 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +74 -0
- package/scripts/install-opencode-codex-auth.js +433 -0
- package/scripts/test-all-models.sh +259 -0
- package/scripts/validate-model-map.sh +97 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile, mkdir, copyFile, rm } from "node:fs/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { parse, modify, applyEdits, printParseErrorCode } from "jsonc-parser";
|
|
9
|
+
|
|
10
|
+
const PLUGIN_NAME = "better-opencode-openai-codex-auth";
|
|
11
|
+
const args = new Set(process.argv.slice(2));
|
|
12
|
+
|
|
13
|
+
if (args.has("--help") || args.has("-h")) {
|
|
14
|
+
console.log(`Usage: ${PLUGIN_NAME} [--modern|--legacy] [--uninstall] [--all] [--dry-run] [--no-cache-clear]\n\n` +
|
|
15
|
+
"Default behavior:\n" +
|
|
16
|
+
" - Installs/updates global config at ~/.config/opencode/opencode.jsonc (falls back to .json)\n" +
|
|
17
|
+
" - Uses modern config (variants) by default\n" +
|
|
18
|
+
" - Ensures plugin is unpinned (latest)\n" +
|
|
19
|
+
" - Clears OpenCode plugin cache\n\n" +
|
|
20
|
+
"Options:\n" +
|
|
21
|
+
" --modern Force modern config (default)\n" +
|
|
22
|
+
" --legacy Use legacy config (older OpenCode versions)\n" +
|
|
23
|
+
" --uninstall Remove plugin + OpenAI config entries from global config\n" +
|
|
24
|
+
" --all With --uninstall, also remove tokens, logs, and cached instructions\n" +
|
|
25
|
+
" --dry-run Show actions without writing\n" +
|
|
26
|
+
" --no-cache-clear Skip clearing OpenCode cache\n"
|
|
27
|
+
);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const useLegacy = args.has("--legacy");
|
|
32
|
+
const useModern = args.has("--modern") || !useLegacy;
|
|
33
|
+
const uninstallRequested = args.has("--uninstall") || args.has("--all");
|
|
34
|
+
const uninstallAll = args.has("--all");
|
|
35
|
+
const dryRun = args.has("--dry-run");
|
|
36
|
+
const skipCacheClear = args.has("--no-cache-clear");
|
|
37
|
+
|
|
38
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const repoRoot = resolve(scriptDir, "..");
|
|
40
|
+
const templatePath = join(
|
|
41
|
+
repoRoot,
|
|
42
|
+
"config",
|
|
43
|
+
useLegacy ? "opencode-legacy.json" : "opencode-modern.json"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const configDir = join(homedir(), ".config", "opencode");
|
|
47
|
+
const configPathJson = join(configDir, "opencode.json");
|
|
48
|
+
const configPathJsonc = join(configDir, "opencode.jsonc");
|
|
49
|
+
const cacheDir = join(homedir(), ".cache", "opencode");
|
|
50
|
+
const cacheNodeModules = join(cacheDir, "node_modules", PLUGIN_NAME);
|
|
51
|
+
const cacheBunLock = join(cacheDir, "bun.lock");
|
|
52
|
+
const cachePackageJson = join(cacheDir, "package.json");
|
|
53
|
+
const opencodeAuthPath = join(homedir(), ".opencode", "auth", "openai.json");
|
|
54
|
+
const pluginConfigPath = join(
|
|
55
|
+
homedir(),
|
|
56
|
+
".opencode",
|
|
57
|
+
"openai-codex-auth-config.json",
|
|
58
|
+
);
|
|
59
|
+
const pluginAccountsPath = join(homedir(), ".opencode", "openai-codex-accounts.json");
|
|
60
|
+
const pluginLogDir = join(homedir(), ".opencode", "logs", "codex-plugin");
|
|
61
|
+
const opencodeCacheDir = join(homedir(), ".opencode", "cache");
|
|
62
|
+
|
|
63
|
+
function log(message) {
|
|
64
|
+
console.log(message);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizePluginList(list) {
|
|
68
|
+
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
69
|
+
const filtered = entries.filter((entry) => {
|
|
70
|
+
if (typeof entry !== "string") return true;
|
|
71
|
+
return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
|
|
72
|
+
});
|
|
73
|
+
return [...filtered, PLUGIN_NAME];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function removePluginEntries(list) {
|
|
77
|
+
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
78
|
+
return entries.filter((entry) => {
|
|
79
|
+
if (typeof entry !== "string") return true;
|
|
80
|
+
if (entry === PLUGIN_NAME || entry.startsWith(`${PLUGIN_NAME}@`)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return !entry.includes(PLUGIN_NAME);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function mergeOpenAIConfig(existingOpenAI, templateOpenAI) {
|
|
88
|
+
const existing = existingOpenAI && typeof existingOpenAI === "object"
|
|
89
|
+
? existingOpenAI
|
|
90
|
+
: {};
|
|
91
|
+
const template = templateOpenAI && typeof templateOpenAI === "object"
|
|
92
|
+
? templateOpenAI
|
|
93
|
+
: {};
|
|
94
|
+
const existingOptions =
|
|
95
|
+
existing.options && typeof existing.options === "object"
|
|
96
|
+
? existing.options
|
|
97
|
+
: {};
|
|
98
|
+
const templateOptions =
|
|
99
|
+
template.options && typeof template.options === "object"
|
|
100
|
+
? template.options
|
|
101
|
+
: {};
|
|
102
|
+
const existingModels =
|
|
103
|
+
existing.models && typeof existing.models === "object"
|
|
104
|
+
? existing.models
|
|
105
|
+
: {};
|
|
106
|
+
const templateModels =
|
|
107
|
+
template.models && typeof template.models === "object"
|
|
108
|
+
? template.models
|
|
109
|
+
: {};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
...existing,
|
|
113
|
+
...template,
|
|
114
|
+
options: { ...existingOptions, ...templateOptions },
|
|
115
|
+
models: { ...existingModels, ...templateModels },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function getKnownModelIds() {
|
|
120
|
+
const legacyTemplate = await readJson(
|
|
121
|
+
join(repoRoot, "config", "opencode-legacy.json"),
|
|
122
|
+
);
|
|
123
|
+
const modernTemplate = await readJson(
|
|
124
|
+
join(repoRoot, "config", "opencode-modern.json"),
|
|
125
|
+
);
|
|
126
|
+
const legacyModels = Object.keys(
|
|
127
|
+
legacyTemplate?.provider?.openai?.models || {},
|
|
128
|
+
);
|
|
129
|
+
const modernModels = Object.keys(
|
|
130
|
+
modernTemplate?.provider?.openai?.models || {},
|
|
131
|
+
);
|
|
132
|
+
return new Set([...legacyModels, ...modernModels]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatJson(obj) {
|
|
136
|
+
return `${JSON.stringify(obj, null, 2)}\n`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const JSONC_PARSE_OPTIONS = { allowTrailingComma: true, disallowComments: false };
|
|
140
|
+
const JSONC_FORMAT_OPTIONS = { insertSpaces: true, tabSize: 2, eol: "\n" };
|
|
141
|
+
|
|
142
|
+
function resolveConfigPath() {
|
|
143
|
+
if (existsSync(configPathJsonc)) {
|
|
144
|
+
return configPathJsonc;
|
|
145
|
+
}
|
|
146
|
+
if (existsSync(configPathJson)) {
|
|
147
|
+
return configPathJson;
|
|
148
|
+
}
|
|
149
|
+
return configPathJsonc;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function readJson(filePath) {
|
|
153
|
+
const content = await readFile(filePath, "utf-8");
|
|
154
|
+
return JSON.parse(content);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function readJsonc(filePath) {
|
|
158
|
+
const content = await readFile(filePath, "utf-8");
|
|
159
|
+
const errors = [];
|
|
160
|
+
const data = parse(content, errors, JSONC_PARSE_OPTIONS);
|
|
161
|
+
if (errors.length) {
|
|
162
|
+
const formatted = errors
|
|
163
|
+
.map((error) => printParseErrorCode(error.error))
|
|
164
|
+
.join(", ");
|
|
165
|
+
throw new Error(`Invalid JSONC (${formatted})`);
|
|
166
|
+
}
|
|
167
|
+
return { content, data: data ?? {} };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function applyJsoncUpdates(content, updates) {
|
|
171
|
+
let next = content;
|
|
172
|
+
for (const update of updates) {
|
|
173
|
+
const edits = modify(next, update.path, update.value, {
|
|
174
|
+
formattingOptions: JSONC_FORMAT_OPTIONS,
|
|
175
|
+
});
|
|
176
|
+
next = applyEdits(next, edits);
|
|
177
|
+
}
|
|
178
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function backupConfig(sourcePath) {
|
|
182
|
+
const timestamp = new Date()
|
|
183
|
+
.toISOString()
|
|
184
|
+
.replace(/[:.]/g, "-")
|
|
185
|
+
.replace("T", "_")
|
|
186
|
+
.replace("Z", "");
|
|
187
|
+
const backupPath = `${sourcePath}.bak-${timestamp}`;
|
|
188
|
+
if (!dryRun) {
|
|
189
|
+
await copyFile(sourcePath, backupPath);
|
|
190
|
+
}
|
|
191
|
+
return backupPath;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function removePluginFromCachePackage() {
|
|
195
|
+
if (!existsSync(cachePackageJson)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let cacheData;
|
|
200
|
+
try {
|
|
201
|
+
cacheData = await readJson(cachePackageJson);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
log(`Warning: Could not parse ${cachePackageJson} (${error}). Skipping.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const sections = [
|
|
208
|
+
"dependencies",
|
|
209
|
+
"devDependencies",
|
|
210
|
+
"peerDependencies",
|
|
211
|
+
"optionalDependencies",
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
let changed = false;
|
|
215
|
+
for (const section of sections) {
|
|
216
|
+
const deps = cacheData?.[section];
|
|
217
|
+
if (deps && typeof deps === "object" && PLUGIN_NAME in deps) {
|
|
218
|
+
delete deps[PLUGIN_NAME];
|
|
219
|
+
changed = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!changed) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (dryRun) {
|
|
228
|
+
log(`[dry-run] Would update ${cachePackageJson} to remove ${PLUGIN_NAME}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await writeFile(cachePackageJson, formatJson(cacheData), "utf-8");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function clearCache() {
|
|
236
|
+
if (skipCacheClear) {
|
|
237
|
+
log("Skipping cache clear (--no-cache-clear).");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (dryRun) {
|
|
242
|
+
log(`[dry-run] Would remove ${cacheNodeModules}`);
|
|
243
|
+
log(`[dry-run] Would remove ${cacheBunLock}`);
|
|
244
|
+
} else {
|
|
245
|
+
await rm(cacheNodeModules, { recursive: true, force: true });
|
|
246
|
+
await rm(cacheBunLock, { force: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await removePluginFromCachePackage();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function clearPluginArtifacts() {
|
|
253
|
+
if (dryRun) {
|
|
254
|
+
log(`[dry-run] Would remove ${opencodeAuthPath}`);
|
|
255
|
+
log(`[dry-run] Would remove ${pluginConfigPath}`);
|
|
256
|
+
log(`[dry-run] Would remove ${pluginAccountsPath}`);
|
|
257
|
+
log(`[dry-run] Would remove ${pluginLogDir}`);
|
|
258
|
+
} else {
|
|
259
|
+
await rm(opencodeAuthPath, { force: true });
|
|
260
|
+
await rm(pluginConfigPath, { force: true });
|
|
261
|
+
await rm(pluginAccountsPath, { force: true });
|
|
262
|
+
await rm(pluginLogDir, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const cacheFiles = [
|
|
266
|
+
"codex-instructions.md",
|
|
267
|
+
"codex-instructions-meta.json",
|
|
268
|
+
"codex-max-instructions.md",
|
|
269
|
+
"codex-max-instructions-meta.json",
|
|
270
|
+
"gpt-5.1-instructions.md",
|
|
271
|
+
"gpt-5.1-instructions-meta.json",
|
|
272
|
+
"gpt-5.2-instructions.md",
|
|
273
|
+
"gpt-5.2-instructions-meta.json",
|
|
274
|
+
"gpt-5.2-codex-instructions.md",
|
|
275
|
+
"gpt-5.2-codex-instructions-meta.json",
|
|
276
|
+
"opencode-codex.txt",
|
|
277
|
+
"opencode-codex-meta.json",
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
for (const file of cacheFiles) {
|
|
281
|
+
const target = join(opencodeCacheDir, file);
|
|
282
|
+
if (dryRun) {
|
|
283
|
+
log(`[dry-run] Would remove ${target}`);
|
|
284
|
+
} else {
|
|
285
|
+
await rm(target, { force: true });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function main() {
|
|
291
|
+
if (!existsSync(templatePath)) {
|
|
292
|
+
throw new Error(`Config template not found at ${templatePath}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const configPath = resolveConfigPath();
|
|
296
|
+
const configExists = existsSync(configPath);
|
|
297
|
+
|
|
298
|
+
if (uninstallRequested) {
|
|
299
|
+
if (!configExists) {
|
|
300
|
+
log("No existing config found. Nothing to uninstall.");
|
|
301
|
+
} else {
|
|
302
|
+
const backupPath = await backupConfig(configPath);
|
|
303
|
+
log(`${dryRun ? "[dry-run] Would create backup" : "Backup created"}: ${backupPath}`);
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const { content, data } = await readJsonc(configPath);
|
|
307
|
+
const existing = data ?? {};
|
|
308
|
+
const pluginList = removePluginEntries(existing.plugin);
|
|
309
|
+
|
|
310
|
+
const provider =
|
|
311
|
+
existing.provider && typeof existing.provider === "object"
|
|
312
|
+
? { ...existing.provider }
|
|
313
|
+
: {};
|
|
314
|
+
const openai =
|
|
315
|
+
provider.openai && typeof provider.openai === "object"
|
|
316
|
+
? { ...provider.openai }
|
|
317
|
+
: {};
|
|
318
|
+
|
|
319
|
+
const knownModelIds = await getKnownModelIds();
|
|
320
|
+
const existingModels =
|
|
321
|
+
openai.models && typeof openai.models === "object"
|
|
322
|
+
? { ...openai.models }
|
|
323
|
+
: {};
|
|
324
|
+
for (const modelId of knownModelIds) {
|
|
325
|
+
delete existingModels[modelId];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (Object.keys(existingModels).length > 0) {
|
|
329
|
+
openai.models = existingModels;
|
|
330
|
+
} else {
|
|
331
|
+
delete openai.models;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (Object.keys(openai).length > 0) {
|
|
335
|
+
provider.openai = openai;
|
|
336
|
+
} else {
|
|
337
|
+
delete provider.openai;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const updates = [];
|
|
341
|
+
if (pluginList.length > 0) {
|
|
342
|
+
updates.push({ path: ["plugin"], value: pluginList });
|
|
343
|
+
} else {
|
|
344
|
+
updates.push({ path: ["plugin"], value: undefined });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (Object.keys(provider).length > 0) {
|
|
348
|
+
updates.push({ path: ["provider"], value: provider });
|
|
349
|
+
} else {
|
|
350
|
+
updates.push({ path: ["provider"], value: undefined });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (dryRun) {
|
|
354
|
+
log(`[dry-run] Would write ${configPath} (uninstall)`);
|
|
355
|
+
} else {
|
|
356
|
+
const nextContent = applyJsoncUpdates(content, updates);
|
|
357
|
+
await writeFile(configPath, nextContent, "utf-8");
|
|
358
|
+
log(`Updated ${configPath} (plugin removed)`);
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
log(`Warning: Could not parse existing config (${error}). Skipping config update.`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await clearCache();
|
|
366
|
+
if (uninstallAll) {
|
|
367
|
+
await clearPluginArtifacts();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
log("\nDone. Restart OpenCode.");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const template = await readJson(templatePath);
|
|
375
|
+
template.plugin = [PLUGIN_NAME];
|
|
376
|
+
|
|
377
|
+
let nextConfig = template;
|
|
378
|
+
let nextContent = null;
|
|
379
|
+
|
|
380
|
+
if (configExists) {
|
|
381
|
+
const backupPath = await backupConfig(configPath);
|
|
382
|
+
log(`${dryRun ? "[dry-run] Would create backup" : "Backup created"}: ${backupPath}`);
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
const { content, data } = await readJsonc(configPath);
|
|
386
|
+
const existing = data ?? {};
|
|
387
|
+
const merged = { ...existing };
|
|
388
|
+
merged.plugin = normalizePluginList(existing.plugin);
|
|
389
|
+
const provider =
|
|
390
|
+
existing.provider && typeof existing.provider === "object"
|
|
391
|
+
? { ...existing.provider }
|
|
392
|
+
: {};
|
|
393
|
+
provider.openai = mergeOpenAIConfig(provider.openai, template.provider.openai);
|
|
394
|
+
merged.provider = provider;
|
|
395
|
+
nextConfig = merged;
|
|
396
|
+
|
|
397
|
+
nextContent = applyJsoncUpdates(content, [
|
|
398
|
+
{ path: ["plugin"], value: merged.plugin },
|
|
399
|
+
{ path: ["provider", "openai"], value: merged.provider.openai },
|
|
400
|
+
]);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
log(`Warning: Could not parse existing config (${error}). Replacing with template.`);
|
|
403
|
+
nextConfig = template;
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
log("No existing config found. Creating new global config.");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (dryRun) {
|
|
410
|
+
log(`[dry-run] Would write ${configPath} using ${useLegacy ? "legacy" : "modern"} config`);
|
|
411
|
+
} else {
|
|
412
|
+
await mkdir(configDir, { recursive: true });
|
|
413
|
+
if (nextContent && configExists) {
|
|
414
|
+
await writeFile(configPath, nextContent, "utf-8");
|
|
415
|
+
} else {
|
|
416
|
+
await writeFile(configPath, formatJson(nextConfig), "utf-8");
|
|
417
|
+
}
|
|
418
|
+
log(`Wrote ${configPath} (${useLegacy ? "legacy" : "modern"} config)`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
await clearCache();
|
|
422
|
+
|
|
423
|
+
log("\nDone. Restart OpenCode to (re)install the plugin.");
|
|
424
|
+
log("Example: opencode");
|
|
425
|
+
if (useLegacy) {
|
|
426
|
+
log("Note: Legacy config requires OpenCode v1.0.209 or older.");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
main().catch((error) => {
|
|
431
|
+
console.error(`Installer failed: ${error instanceof Error ? error.message : error}`);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Test All Models - Verify API Configuration
|
|
4
|
+
# This script tests all model configurations and verifies the actual API requests
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Paths
|
|
16
|
+
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
17
|
+
OPENCODE_JSON="${REPO_DIR}/opencode.json"
|
|
18
|
+
LOG_DIR="${HOME}/.opencode/logs/codex-plugin"
|
|
19
|
+
RESULTS_FILE="${REPO_DIR}/test-results.md"
|
|
20
|
+
|
|
21
|
+
# Test counter
|
|
22
|
+
TOTAL_TESTS=0
|
|
23
|
+
PASSED_TESTS=0
|
|
24
|
+
FAILED_TESTS=0
|
|
25
|
+
|
|
26
|
+
echo -e "${BLUE}════════════════════════════════════════════════════════════════${NC}"
|
|
27
|
+
echo -e "${BLUE} Model Configuration Verification Test Suite${NC}"
|
|
28
|
+
echo -e "${BLUE}════════════════════════════════════════════════════════════════${NC}"
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
# Kill any running OpenCode server processes to force fresh plugin load
|
|
32
|
+
echo "Killing OpenCode server processes..."
|
|
33
|
+
pkill -f "opencode" 2>/dev/null || true
|
|
34
|
+
sleep 2
|
|
35
|
+
echo "✓ OpenCode servers stopped"
|
|
36
|
+
echo ""
|
|
37
|
+
|
|
38
|
+
# Initialize results file
|
|
39
|
+
cat > "${RESULTS_FILE}" << 'EOF'
|
|
40
|
+
# Model Configuration Verification Results
|
|
41
|
+
|
|
42
|
+
**Test Date:** $(date)
|
|
43
|
+
**Test Directory:** Repository local config
|
|
44
|
+
|
|
45
|
+
## Results Summary
|
|
46
|
+
|
|
47
|
+
| Model | Normalized | Family | Effort | Summary | Verbosity | Include | Status |
|
|
48
|
+
|-------|------------|--------|--------|---------|-----------|---------|--------|
|
|
49
|
+
EOF
|
|
50
|
+
|
|
51
|
+
# Function: Run a test for a specific model
|
|
52
|
+
test_model() {
|
|
53
|
+
local model_name="$1"
|
|
54
|
+
local expected_normalized="$2"
|
|
55
|
+
local expected_family="$3"
|
|
56
|
+
local expected_effort="$4"
|
|
57
|
+
local expected_summary="$5"
|
|
58
|
+
local expected_verbosity="$6"
|
|
59
|
+
|
|
60
|
+
((TOTAL_TESTS++))
|
|
61
|
+
|
|
62
|
+
echo -e "${YELLOW}Testing model: ${model_name}${NC}"
|
|
63
|
+
|
|
64
|
+
# Clear previous logs
|
|
65
|
+
rm -rf "${LOG_DIR}"/*
|
|
66
|
+
|
|
67
|
+
# Run opencode
|
|
68
|
+
cd "${REPO_DIR}"
|
|
69
|
+
if ENABLE_PLUGIN_REQUEST_LOGGING=1 DEBUG_CODEX_PLUGIN=1 opencode run "write hello to test-${TOTAL_TESTS}.txt" --model="openai/${model_name}" > /dev/null 2>&1; then
|
|
70
|
+
echo -e "${GREEN} ✓ Command executed successfully${NC}"
|
|
71
|
+
else
|
|
72
|
+
echo -e "${RED} ✗ Command failed${NC}"
|
|
73
|
+
echo "| ${model_name} | N/A | N/A | N/A | N/A | N/A | ❌ FAILED |" >> "${RESULTS_FILE}"
|
|
74
|
+
((FAILED_TESTS++))
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Find the after-transform log file that matches the expected model
|
|
79
|
+
# (opencode may use multiple models per session - e.g., nano for titles)
|
|
80
|
+
local log_file=""
|
|
81
|
+
for f in $(find "${LOG_DIR}" -name "*-after-transform.json" -type f -print0 | xargs -0 ls -t); do
|
|
82
|
+
local orig_model=$(jq -r '.originalModel // ""' "$f" 2>/dev/null)
|
|
83
|
+
if [ "${orig_model}" = "${model_name}" ]; then
|
|
84
|
+
log_file="$f"
|
|
85
|
+
break
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
if [ -z "${log_file}" ] || [ ! -f "${log_file}" ]; then
|
|
90
|
+
echo -e "${RED} ✗ Log file not found for model ${model_name}${NC}"
|
|
91
|
+
echo "| ${model_name} | N/A | N/A | N/A | N/A | N/A | ❌ NO LOG |" >> "${RESULTS_FILE}"
|
|
92
|
+
((FAILED_TESTS++))
|
|
93
|
+
return 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Parse log file with jq
|
|
97
|
+
local actual_normalized=$(jq -r '.normalizedModel // "N/A"' "${log_file}")
|
|
98
|
+
local actual_family=$(jq -r '.modelFamily // "N/A"' "${log_file}")
|
|
99
|
+
local actual_effort=$(jq -r '.reasoning.effort // "N/A"' "${log_file}")
|
|
100
|
+
local actual_summary=$(jq -r '.reasoning.summary // "N/A"' "${log_file}")
|
|
101
|
+
local actual_verbosity=$(jq -r '.body.text.verbosity // "N/A"' "${log_file}")
|
|
102
|
+
local actual_include=$(jq -r '.include[0] // "N/A"' "${log_file}")
|
|
103
|
+
|
|
104
|
+
echo " Actual: model=${actual_normalized}, family=${actual_family}, effort=${actual_effort}, summary=${actual_summary}, verbosity=${actual_verbosity}"
|
|
105
|
+
echo " Expected: model=${expected_normalized}, family=${expected_family}, effort=${expected_effort}, summary=${expected_summary}, verbosity=${expected_verbosity}"
|
|
106
|
+
|
|
107
|
+
# Verify values
|
|
108
|
+
local status="✅ PASS"
|
|
109
|
+
if [ "${actual_normalized}" != "${expected_normalized}" ] || \
|
|
110
|
+
[ "${actual_family}" != "${expected_family}" ] || \
|
|
111
|
+
[ "${actual_effort}" != "${expected_effort}" ] || \
|
|
112
|
+
[ "${actual_summary}" != "${expected_summary}" ] || \
|
|
113
|
+
[ "${actual_verbosity}" != "${expected_verbosity}" ]; then
|
|
114
|
+
status="❌ FAIL"
|
|
115
|
+
((FAILED_TESTS++))
|
|
116
|
+
echo -e "${RED} ✗ Verification failed${NC}"
|
|
117
|
+
else
|
|
118
|
+
((PASSED_TESTS++))
|
|
119
|
+
echo -e "${GREEN} ✓ Verification passed${NC}"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Add to results
|
|
123
|
+
echo "| ${model_name} | ${actual_normalized} | ${actual_family} | ${actual_effort} | ${actual_summary} | ${actual_verbosity} | ${actual_include} | ${status} |" >> "${RESULTS_FILE}"
|
|
124
|
+
|
|
125
|
+
# Cleanup
|
|
126
|
+
rm -f "${REPO_DIR}/test-${TOTAL_TESTS}.txt"
|
|
127
|
+
|
|
128
|
+
echo ""
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Function: Update opencode.json with config
|
|
132
|
+
update_config() {
|
|
133
|
+
local config_type="$1"
|
|
134
|
+
|
|
135
|
+
echo -e "${BLUE}─────────────────────────────────────────────────────────────────${NC}"
|
|
136
|
+
echo -e "${BLUE}Scenario: ${config_type}${NC}"
|
|
137
|
+
echo -e "${BLUE}─────────────────────────────────────────────────────────────────${NC}"
|
|
138
|
+
echo ""
|
|
139
|
+
|
|
140
|
+
case "${config_type}" in
|
|
141
|
+
"legacy")
|
|
142
|
+
cat "${REPO_DIR}/config/opencode-legacy.json" > "${OPENCODE_JSON}"
|
|
143
|
+
echo "✓ Updated opencode.json with legacy config (GPT 5.x)"
|
|
144
|
+
;;
|
|
145
|
+
"minimal")
|
|
146
|
+
cat "${REPO_DIR}/config/minimal-opencode.json" > "${OPENCODE_JSON}"
|
|
147
|
+
echo "✓ Updated opencode.json with minimal config"
|
|
148
|
+
;;
|
|
149
|
+
esac
|
|
150
|
+
|
|
151
|
+
# Replace npm package with local dist for testing
|
|
152
|
+
sed -i.bak -E 's|"better-opencode-openai-codex-auth(@[^"]*)?"|"file://'"${REPO_DIR}"'/dist"|' "${OPENCODE_JSON}"
|
|
153
|
+
rm -f "${OPENCODE_JSON}.bak"
|
|
154
|
+
echo "✓ Using local dist for plugin"
|
|
155
|
+
|
|
156
|
+
echo ""
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ============================================================================
|
|
160
|
+
# Scenario 1: Legacy Config - GPT 5.x Model Family
|
|
161
|
+
# ============================================================================
|
|
162
|
+
update_config "legacy"
|
|
163
|
+
|
|
164
|
+
# GPT 5.1 Codex presets
|
|
165
|
+
test_model "gpt-5.1-codex-low" "gpt-5.1-codex" "codex" "low" "auto" "medium"
|
|
166
|
+
test_model "gpt-5.1-codex-medium" "gpt-5.1-codex" "codex" "medium" "auto" "medium"
|
|
167
|
+
test_model "gpt-5.1-codex-high" "gpt-5.1-codex" "codex" "high" "detailed" "medium"
|
|
168
|
+
test_model "gpt-5.1-codex-max-low" "gpt-5.1-codex-max" "codex-max" "low" "detailed" "medium"
|
|
169
|
+
test_model "gpt-5.1-codex-max-medium" "gpt-5.1-codex-max" "codex-max" "medium" "detailed" "medium"
|
|
170
|
+
test_model "gpt-5.1-codex-max-high" "gpt-5.1-codex-max" "codex-max" "high" "detailed" "medium"
|
|
171
|
+
test_model "gpt-5.1-codex-max-xhigh" "gpt-5.1-codex-max" "codex-max" "xhigh" "detailed" "medium"
|
|
172
|
+
|
|
173
|
+
# GPT 5.2 presets (supports none/low/medium/high/xhigh per OpenAI API docs)
|
|
174
|
+
test_model "gpt-5.2-none" "gpt-5.2" "gpt-5.2" "none" "auto" "medium"
|
|
175
|
+
test_model "gpt-5.2-low" "gpt-5.2" "gpt-5.2" "low" "auto" "medium"
|
|
176
|
+
test_model "gpt-5.2-medium" "gpt-5.2" "gpt-5.2" "medium" "auto" "medium"
|
|
177
|
+
test_model "gpt-5.2-high" "gpt-5.2" "gpt-5.2" "high" "detailed" "medium"
|
|
178
|
+
test_model "gpt-5.2-xhigh" "gpt-5.2" "gpt-5.2" "xhigh" "detailed" "medium"
|
|
179
|
+
|
|
180
|
+
# GPT 5.2 Codex presets
|
|
181
|
+
test_model "gpt-5.2-codex-low" "gpt-5.2-codex" "gpt-5.2-codex" "low" "auto" "medium"
|
|
182
|
+
test_model "gpt-5.2-codex-medium" "gpt-5.2-codex" "gpt-5.2-codex" "medium" "auto" "medium"
|
|
183
|
+
test_model "gpt-5.2-codex-high" "gpt-5.2-codex" "gpt-5.2-codex" "high" "detailed" "medium"
|
|
184
|
+
test_model "gpt-5.2-codex-xhigh" "gpt-5.2-codex" "gpt-5.2-codex" "xhigh" "detailed" "medium"
|
|
185
|
+
|
|
186
|
+
# GPT 5.1 Codex Mini presets (medium/high only)
|
|
187
|
+
test_model "gpt-5.1-codex-mini-medium" "gpt-5.1-codex-mini" "codex" "medium" "auto" "medium"
|
|
188
|
+
test_model "gpt-5.1-codex-mini-high" "gpt-5.1-codex-mini" "codex" "high" "detailed" "medium"
|
|
189
|
+
|
|
190
|
+
# GPT 5.1 general-purpose presets (supports none/low/medium/high per OpenAI API docs)
|
|
191
|
+
test_model "gpt-5.1-none" "gpt-5.1" "gpt-5.1" "none" "auto" "medium"
|
|
192
|
+
test_model "gpt-5.1-low" "gpt-5.1" "gpt-5.1" "low" "auto" "low"
|
|
193
|
+
test_model "gpt-5.1-medium" "gpt-5.1" "gpt-5.1" "medium" "auto" "medium"
|
|
194
|
+
test_model "gpt-5.1-high" "gpt-5.1" "gpt-5.1" "high" "detailed" "high"
|
|
195
|
+
|
|
196
|
+
# # ============================================================================
|
|
197
|
+
# # Scenario 2: Minimal Config - Default Models (No Custom Config)
|
|
198
|
+
# # ============================================================================
|
|
199
|
+
# update_config "minimal"
|
|
200
|
+
|
|
201
|
+
# test_model "gpt-5" "gpt-5" "medium" "auto" "medium"
|
|
202
|
+
# test_model "gpt-5-codex" "gpt-5-codex" "medium" "auto" "medium"
|
|
203
|
+
# test_model "gpt-5-mini" "gpt-5" "minimal" "auto" "medium"
|
|
204
|
+
# test_model "gpt-5-nano" "gpt-5" "minimal" "auto" "medium"
|
|
205
|
+
|
|
206
|
+
# ============================================================================
|
|
207
|
+
# Scenario 3: Backwards Compatibility
|
|
208
|
+
# ============================================================================
|
|
209
|
+
# update_config "backwards-compat"
|
|
210
|
+
|
|
211
|
+
# # GPT 5 Codex presets
|
|
212
|
+
# test_model "gpt-5-codex-low" "gpt-5-codex" "low" "auto" "medium"
|
|
213
|
+
# test_model "gpt-5-codex-medium" "gpt-5-codex" "medium" "auto" "medium"
|
|
214
|
+
# test_model "gpt-5-codex-high" "gpt-5-codex" "high" "detailed" "medium"
|
|
215
|
+
|
|
216
|
+
# GPT 5 Codex Mini presets
|
|
217
|
+
# test_model "gpt-5-codex-mini" "codex-mini-latest" "medium" "auto" "medium"
|
|
218
|
+
# test_model "gpt-5-codex-mini-medium" "codex-mini-latest" "medium" "auto" "medium"
|
|
219
|
+
# test_model "gpt-5-codex-mini-high" "codex-mini-latest" "high" "detailed" "medium"
|
|
220
|
+
|
|
221
|
+
# GPT 5 general-purpose presets
|
|
222
|
+
# test_model "gpt-5" "gpt-5" "medium" "auto" "medium"
|
|
223
|
+
# test_model "gpt-5-medium" "gpt-5" "medium" "auto" "medium"
|
|
224
|
+
# test_model "gpt-5-high" "gpt-5" "high" "detailed" "high"
|
|
225
|
+
# test_model "gpt-5-mini" "gpt-5" "minimal" "auto" "medium"
|
|
226
|
+
|
|
227
|
+
# ============================================================================
|
|
228
|
+
# Summary
|
|
229
|
+
# ============================================================================
|
|
230
|
+
echo -e "${BLUE}════════════════════════════════════════════════════════════════${NC}"
|
|
231
|
+
echo -e "${BLUE} Test Results Summary${NC}"
|
|
232
|
+
echo -e "${BLUE}════════════════════════════════════════════════════════════════${NC}"
|
|
233
|
+
echo ""
|
|
234
|
+
echo -e "Total Tests: ${TOTAL_TESTS}"
|
|
235
|
+
echo -e "${GREEN}Passed: ${PASSED_TESTS}${NC}"
|
|
236
|
+
if [ ${FAILED_TESTS} -gt 0 ]; then
|
|
237
|
+
echo -e "${RED}Failed: ${FAILED_TESTS}${NC}"
|
|
238
|
+
else
|
|
239
|
+
echo -e "Failed: ${FAILED_TESTS}"
|
|
240
|
+
fi
|
|
241
|
+
echo ""
|
|
242
|
+
echo -e "Results saved to: ${RESULTS_FILE} (will be removed)"
|
|
243
|
+
echo ""
|
|
244
|
+
|
|
245
|
+
# Restore original config
|
|
246
|
+
if [ -f "${REPO_DIR}/config/opencode-legacy.json" ]; then
|
|
247
|
+
cat "${REPO_DIR}/config/opencode-legacy.json" > "${OPENCODE_JSON}"
|
|
248
|
+
echo "✓ Restored original legacy config to opencode.json"
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# Cleanup results file to avoid polluting the repo
|
|
252
|
+
rm -f "${RESULTS_FILE}"
|
|
253
|
+
|
|
254
|
+
# Exit with appropriate code
|
|
255
|
+
if [ ${FAILED_TESTS} -gt 0 ]; then
|
|
256
|
+
exit 1
|
|
257
|
+
else
|
|
258
|
+
exit 0
|
|
259
|
+
fi
|