oh-my-codex 0.21.18 → 0.21.20
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/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/dist/agents/__tests__/native-config.test.js +468 -2
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +63 -7
- package/dist/agents/native-config.js.map +1 -1
- package/dist/cli/__tests__/doctor-spark-routing.test.js +23 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-cache-live-pin-3790.test.js +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -2
- package/dist/cli/doctor.js.map +1 -1
- package/dist/config/models.d.ts +37 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +98 -0
- package/dist/config/models.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +78 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +60 -9
- package/dist/team/tmux-session.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
package/Cargo.lock
CHANGED
|
@@ -32,7 +32,7 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
|
|
32
32
|
|
|
33
33
|
[[package]]
|
|
34
34
|
name = "omx-api"
|
|
35
|
-
version = "0.21.
|
|
35
|
+
version = "0.21.20"
|
|
36
36
|
dependencies = [
|
|
37
37
|
"serde",
|
|
38
38
|
"serde_json",
|
|
@@ -40,14 +40,14 @@ dependencies = [
|
|
|
40
40
|
|
|
41
41
|
[[package]]
|
|
42
42
|
name = "omx-explore-harness"
|
|
43
|
-
version = "0.21.
|
|
43
|
+
version = "0.21.20"
|
|
44
44
|
dependencies = [
|
|
45
45
|
"libc",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
[[package]]
|
|
49
49
|
name = "omx-mux"
|
|
50
|
-
version = "0.21.
|
|
50
|
+
version = "0.21.20"
|
|
51
51
|
dependencies = [
|
|
52
52
|
"serde",
|
|
53
53
|
"serde_json",
|
|
@@ -55,7 +55,7 @@ dependencies = [
|
|
|
55
55
|
|
|
56
56
|
[[package]]
|
|
57
57
|
name = "omx-runtime"
|
|
58
|
-
version = "0.21.
|
|
58
|
+
version = "0.21.20"
|
|
59
59
|
dependencies = [
|
|
60
60
|
"fs2",
|
|
61
61
|
"libc",
|
|
@@ -67,7 +67,7 @@ dependencies = [
|
|
|
67
67
|
|
|
68
68
|
[[package]]
|
|
69
69
|
name = "omx-runtime-core"
|
|
70
|
-
version = "0.21.
|
|
70
|
+
version = "0.21.20"
|
|
71
71
|
dependencies = [
|
|
72
72
|
"fs2",
|
|
73
73
|
"serde",
|
|
@@ -76,7 +76,7 @@ dependencies = [
|
|
|
76
76
|
|
|
77
77
|
[[package]]
|
|
78
78
|
name = "omx-sparkshell"
|
|
79
|
-
version = "0.21.
|
|
79
|
+
version = "0.21.20"
|
|
80
80
|
dependencies = [
|
|
81
81
|
"omx-mux",
|
|
82
82
|
]
|
package/Cargo.toml
CHANGED
|
@@ -306,6 +306,39 @@ describe("agents/native-config", () => {
|
|
|
306
306
|
assert.doesNotMatch(toml, /resolved_model: gpt-5\.6-terra/, `${role} must not inherit exact-Terra pins`);
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
|
+
it("does not bake exactModel for planner/architect/researcher when custom provider is configured (fixes #3823)", async () => {
|
|
310
|
+
const codexHome = join(tmpdir(), `omx-custom-provider-test-${process.pid}`);
|
|
311
|
+
try {
|
|
312
|
+
// Create a config.toml with a custom provider
|
|
313
|
+
await mkdir(codexHome, { recursive: true });
|
|
314
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
315
|
+
'model = "glm-5.3-flash"',
|
|
316
|
+
'model_provider = "custom-provider"',
|
|
317
|
+
].join("\n"));
|
|
318
|
+
// With custom provider, planner should NOT bake gpt-6-astra, should inherit from config
|
|
319
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
320
|
+
codexHomeOverride: codexHome,
|
|
321
|
+
});
|
|
322
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider");
|
|
323
|
+
// When model is not baked, agents inherit from config.toml at runtime
|
|
324
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive exactModel guidance with custom provider");
|
|
325
|
+
// With custom provider, architect should NOT bake gpt-6-astra, should inherit from config
|
|
326
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
327
|
+
codexHomeOverride: codexHome,
|
|
328
|
+
});
|
|
329
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider");
|
|
330
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive exactModel guidance with custom provider");
|
|
331
|
+
// With custom provider, researcher should NOT bake gpt-6-astra, should inherit from config
|
|
332
|
+
const researcherToml = generateAgentToml(AGENT_DEFINITIONS.researcher, "researcher prompt", {
|
|
333
|
+
codexHomeOverride: codexHome,
|
|
334
|
+
});
|
|
335
|
+
assert.doesNotMatch(researcherToml, /^model\s*=/m, "researcher should not bake model field with custom provider");
|
|
336
|
+
assert.doesNotMatch(researcherToml, /exact gpt-6-astra model/, "researcher should not receive exactModel guidance with custom provider");
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
340
|
+
}
|
|
341
|
+
});
|
|
309
342
|
it("applies exact-model Terra guidance only for resolved gpt-5.6-terra standard roles", () => {
|
|
310
343
|
const agent = {
|
|
311
344
|
name: "debugger",
|
|
@@ -487,9 +520,10 @@ describe("agents/native-config", () => {
|
|
|
487
520
|
OMX_DEFAULT_SPARK_MODEL: "gpt-5.6-luna",
|
|
488
521
|
},
|
|
489
522
|
}));
|
|
523
|
+
// Use lowercase "openai" (built-in) instead of "OpenAI" (custom) to match expected behavior
|
|
490
524
|
await writeFile(join(codexHome, "config.toml"), [
|
|
491
525
|
'model = "gpt-5.6-sol"',
|
|
492
|
-
'model_provider = "
|
|
526
|
+
'model_provider = "openai"', // Built-in provider
|
|
493
527
|
'',
|
|
494
528
|
].join('\n'));
|
|
495
529
|
await writeFile(join(promptsDir, "explore.md"), "explore prompt");
|
|
@@ -498,7 +532,7 @@ describe("agents/native-config", () => {
|
|
|
498
532
|
catalogManifest: manifestWithAgents(["explore"]),
|
|
499
533
|
});
|
|
500
534
|
const exploreToml = await readFile(join(outDir, "explore.toml"), "utf8");
|
|
501
|
-
// Model resolved from env
|
|
535
|
+
// Model resolved from env default for Spark-lane agents (gpt-5.6-luna), not from root config
|
|
502
536
|
assert.match(exploreToml, /resolved_model: gpt-5\.6-luna/);
|
|
503
537
|
assert.doesNotMatch(exploreToml, /model_provider = /);
|
|
504
538
|
}
|
|
@@ -641,5 +675,437 @@ describe("agents/native-config", () => {
|
|
|
641
675
|
await rm(root, { recursive: true, force: true });
|
|
642
676
|
}
|
|
643
677
|
});
|
|
678
|
+
it("does not bake exactModel pins for planner/architect/researcher when using custom provider (fixes #3823)", async () => {
|
|
679
|
+
const root = await mkdtemp(join(tmpdir(), "omx-issue-3823-custom-provider-"));
|
|
680
|
+
const codexHome = join(root, ".codex");
|
|
681
|
+
const promptsDir = join(root, "prompts");
|
|
682
|
+
const outDir = join(codexHome, "agents");
|
|
683
|
+
const previousCodexHome = process.env.CODEX_HOME;
|
|
684
|
+
const previousStandardModel = process.env.OMX_DEFAULT_STANDARD_MODEL;
|
|
685
|
+
try {
|
|
686
|
+
delete process.env.OMX_DEFAULT_STANDARD_MODEL; // Isolate test from env defaults
|
|
687
|
+
process.env.CODEX_HOME = codexHome;
|
|
688
|
+
await mkdir(promptsDir, { recursive: true });
|
|
689
|
+
await mkdir(codexHome, { recursive: true });
|
|
690
|
+
// Test 1: With custom provider, exactModel pins should NOT be baked
|
|
691
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
692
|
+
'model = "glm-5.3-flash"',
|
|
693
|
+
'model_provider = "custom"',
|
|
694
|
+
'',
|
|
695
|
+
'[model_providers.custom]',
|
|
696
|
+
'name = "Custom Third-Party"',
|
|
697
|
+
'base_url = "https://custom-provider.com/v1"',
|
|
698
|
+
'wire_api = "responses"',
|
|
699
|
+
'',
|
|
700
|
+
].join("\n"));
|
|
701
|
+
await writeFile(join(promptsDir, "planner.md"), "planner prompt");
|
|
702
|
+
await writeFile(join(promptsDir, "architect.md"), "architect prompt");
|
|
703
|
+
await writeFile(join(promptsDir, "researcher.md"), "researcher prompt");
|
|
704
|
+
await installNativeAgentConfigs(root, {
|
|
705
|
+
agentsDir: outDir,
|
|
706
|
+
catalogManifest: manifestWithAgents(["planner", "architect", "researcher"]),
|
|
707
|
+
});
|
|
708
|
+
// Planner should NOT have model baked, inherits from config.toml
|
|
709
|
+
let plannerToml = await readFile(join(outDir, "planner.toml"), "utf8");
|
|
710
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model when using custom provider");
|
|
711
|
+
assert.match(plannerToml, /resolved_model: glm-5\.3-flash/, "planner should resolve to custom provider model from config");
|
|
712
|
+
// Architect should NOT have model baked, inherits from config.toml
|
|
713
|
+
let architectToml = await readFile(join(outDir, "architect.toml"), "utf8");
|
|
714
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model when using custom provider");
|
|
715
|
+
assert.match(architectToml, /resolved_model: glm-5\.3-flash/, "architect should resolve to custom provider model from config");
|
|
716
|
+
// Researcher should NOT have model baked, inherits from config.toml
|
|
717
|
+
let researcherToml = await readFile(join(outDir, "researcher.toml"), "utf8");
|
|
718
|
+
assert.doesNotMatch(researcherToml, /^model\s*=/m, "researcher should not bake model when using custom provider");
|
|
719
|
+
assert.match(researcherToml, /resolved_model: glm-5\.3-flash/, "researcher should resolve to custom provider model from config");
|
|
720
|
+
// Test 2: With default provider, exactModel pins SHOULD be baked
|
|
721
|
+
await rm(outDir, { recursive: true, force: true });
|
|
722
|
+
await writeFile(join(codexHome, "config.toml"), 'model = "gpt-5.6-sol"\n');
|
|
723
|
+
await installNativeAgentConfigs(root, {
|
|
724
|
+
agentsDir: outDir,
|
|
725
|
+
catalogManifest: manifestWithAgents(["planner", "architect", "researcher"]),
|
|
726
|
+
});
|
|
727
|
+
// Planner SHOULD have exactModel baked with default provider
|
|
728
|
+
plannerToml = await readFile(join(outDir, "planner.toml"), "utf8");
|
|
729
|
+
assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel when using default provider");
|
|
730
|
+
assert.match(plannerToml, /resolved_model: gpt-6-astra/, "planner should resolve to gpt-6-astra with default provider");
|
|
731
|
+
// Architect SHOULD have exactModel baked with default provider
|
|
732
|
+
architectToml = await readFile(join(outDir, "architect.toml"), "utf8");
|
|
733
|
+
assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel when using default provider");
|
|
734
|
+
// Researcher SHOULD have exactModel baked with default provider
|
|
735
|
+
researcherToml = await readFile(join(outDir, "researcher.toml"), "utf8");
|
|
736
|
+
assert.match(researcherToml, /model = "gpt-6-astra"/, "researcher should bake exactModel when using default provider");
|
|
737
|
+
}
|
|
738
|
+
finally {
|
|
739
|
+
if (typeof previousCodexHome === "string")
|
|
740
|
+
process.env.CODEX_HOME = previousCodexHome;
|
|
741
|
+
else
|
|
742
|
+
delete process.env.CODEX_HOME;
|
|
743
|
+
if (typeof previousStandardModel === "string")
|
|
744
|
+
process.env.OMX_DEFAULT_STANDARD_MODEL = previousStandardModel;
|
|
745
|
+
else
|
|
746
|
+
delete process.env.OMX_DEFAULT_STANDARD_MODEL;
|
|
747
|
+
await rm(root, { recursive: true, force: true });
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
it("resolves profile-selected custom provider and skips exactModel pins (P1 fix for #3824)", async () => {
|
|
751
|
+
const codexHome = join(tmpdir(), `omx-profile-provider-test-${process.pid}`);
|
|
752
|
+
try {
|
|
753
|
+
// Create a config.toml with a profile-selected custom provider
|
|
754
|
+
await mkdir(codexHome, { recursive: true });
|
|
755
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
756
|
+
'profile = "custom-lane"',
|
|
757
|
+
'',
|
|
758
|
+
'[profiles.custom-lane]',
|
|
759
|
+
'model_provider = "profile-custom-provider"',
|
|
760
|
+
'model = "llm-5.0-custom"',
|
|
761
|
+
].join("\n"));
|
|
762
|
+
// With profile-selected custom provider, planner should NOT bake exactModel
|
|
763
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
764
|
+
codexHomeOverride: codexHome,
|
|
765
|
+
});
|
|
766
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model when custom provider is in active profile");
|
|
767
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive astra guidance when custom provider is in active profile");
|
|
768
|
+
// With profile-selected custom provider, architect should NOT bake exactModel
|
|
769
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
770
|
+
codexHomeOverride: codexHome,
|
|
771
|
+
});
|
|
772
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model when custom provider is in active profile");
|
|
773
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive astra guidance when custom provider is in active profile");
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
it("preserves exactModel pins for built-in openai-chatgpt provider (P2.1 fix for #3824)", async () => {
|
|
780
|
+
const codexHome = join(tmpdir(), `omx-builtin-provider-test-${process.pid}`);
|
|
781
|
+
const origCodexHome = process.env.CODEX_HOME;
|
|
782
|
+
try {
|
|
783
|
+
// Create config with built-in openai-chatgpt provider
|
|
784
|
+
// This would normally be set by omx auth add
|
|
785
|
+
await mkdir(codexHome, { recursive: true });
|
|
786
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
787
|
+
'model = "gpt-6-astra"',
|
|
788
|
+
'model_provider = "openai-chatgpt"',
|
|
789
|
+
].join("\n"));
|
|
790
|
+
// With built-in openai-chatgpt provider, planner SHOULD bake exactModel
|
|
791
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
792
|
+
codexHomeOverride: codexHome,
|
|
793
|
+
});
|
|
794
|
+
assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel with openai-chatgpt provider");
|
|
795
|
+
assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance with openai-chatgpt provider");
|
|
796
|
+
// With built-in openai-chatgpt provider, architect SHOULD bake exactModel
|
|
797
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
798
|
+
codexHomeOverride: codexHome,
|
|
799
|
+
});
|
|
800
|
+
assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel with openai-chatgpt provider");
|
|
801
|
+
assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance with openai-chatgpt provider");
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
if (typeof origCodexHome === "string") {
|
|
805
|
+
process.env.CODEX_HOME = origCodexHome;
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
delete process.env.CODEX_HOME;
|
|
809
|
+
}
|
|
810
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
it("suppresses astra guidance for custom provider without root model (P2.2 fix for #3824)", async () => {
|
|
814
|
+
const codexHome = join(tmpdir(), `omx-custom-no-model-test-${process.pid}`);
|
|
815
|
+
try {
|
|
816
|
+
// Create a config.toml with a custom provider but no root model
|
|
817
|
+
// This is the configuration shape from tmux-session.test.ts:3683-3692
|
|
818
|
+
await mkdir(codexHome, { recursive: true });
|
|
819
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
820
|
+
'model_provider = "custom_provider"',
|
|
821
|
+
'',
|
|
822
|
+
'[model_providers.custom_provider]',
|
|
823
|
+
'name = "custom_provider"',
|
|
824
|
+
'base_url = "http://localhost:3000/v1"',
|
|
825
|
+
'wire_api = "responses"',
|
|
826
|
+
'requires_openai_auth = true',
|
|
827
|
+
'env_key = "CUSTOM_PROVIDER_API_KEY"',
|
|
828
|
+
].join("\n"));
|
|
829
|
+
// With custom provider and no root model, planner should NOT emit astra guidance
|
|
830
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
831
|
+
codexHomeOverride: codexHome,
|
|
832
|
+
});
|
|
833
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider and no root model");
|
|
834
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not emit astra guidance with custom provider and no root model");
|
|
835
|
+
// With custom provider and no root model, architect should NOT emit astra guidance
|
|
836
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
837
|
+
codexHomeOverride: codexHome,
|
|
838
|
+
});
|
|
839
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider and no root model");
|
|
840
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance with custom provider and no root model");
|
|
841
|
+
}
|
|
842
|
+
finally {
|
|
843
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
it("preserves exactModel pins for profile-scoped built-in provider model (Codex finding 4309255932)", async () => {
|
|
847
|
+
const codexHome = join(tmpdir(), `omx-profile-builtin-model-test-${process.pid}`);
|
|
848
|
+
try {
|
|
849
|
+
// Create a config.toml with profile-scoped built-in provider and model, but no root model
|
|
850
|
+
// This reproduces the scenario from finding 4309255932
|
|
851
|
+
await mkdir(codexHome, { recursive: true });
|
|
852
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
853
|
+
'profile = "work-lane"',
|
|
854
|
+
'',
|
|
855
|
+
'[profiles.work-lane]',
|
|
856
|
+
'model_provider = "openai-chatgpt"',
|
|
857
|
+
'model = "gpt-6-luna"',
|
|
858
|
+
].join("\n"));
|
|
859
|
+
// With profile-scoped built-in provider and model (no root model),
|
|
860
|
+
// planner SHOULD bake exactModel because:
|
|
861
|
+
// - It has exactModel (gpt-6-astra)
|
|
862
|
+
// - The effective provider is built-in (openai-chatgpt from profile)
|
|
863
|
+
// - The effective model is configured (gpt-6-luna in profile)
|
|
864
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
865
|
+
codexHomeOverride: codexHome,
|
|
866
|
+
});
|
|
867
|
+
assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel with profile-scoped built-in provider and model");
|
|
868
|
+
assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance with profile-scoped built-in provider and model");
|
|
869
|
+
// Same for architect
|
|
870
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
871
|
+
codexHomeOverride: codexHome,
|
|
872
|
+
});
|
|
873
|
+
assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel with profile-scoped built-in provider and model");
|
|
874
|
+
assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance with profile-scoped built-in provider and model");
|
|
875
|
+
}
|
|
876
|
+
finally {
|
|
877
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
it("preserves exact guidance for custom provider with per-agent override (Codex finding 4309255938)", async () => {
|
|
881
|
+
const codexHome = join(tmpdir(), `omx-custom-override-test-${process.pid}`);
|
|
882
|
+
try {
|
|
883
|
+
// Create a config.toml with custom provider, no root model, but explicit agentModels override
|
|
884
|
+
// This reproduces the scenario from finding 4309255938
|
|
885
|
+
await mkdir(codexHome, { recursive: true });
|
|
886
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
887
|
+
'model_provider = "custom_provider"',
|
|
888
|
+
'',
|
|
889
|
+
'[model_providers.custom_provider]',
|
|
890
|
+
'name = "custom_provider"',
|
|
891
|
+
'base_url = "http://localhost:3000/v1"',
|
|
892
|
+
'wire_api = "responses"',
|
|
893
|
+
'requires_openai_auth = true',
|
|
894
|
+
'env_key = "CUSTOM_PROVIDER_API_KEY"',
|
|
895
|
+
].join("\n"));
|
|
896
|
+
await writeFile(join(codexHome, ".omx-config.json"), JSON.stringify({
|
|
897
|
+
agentModels: { planner: "gpt-5.6-terra" }
|
|
898
|
+
}));
|
|
899
|
+
// With custom provider, no root model, but explicit planner override to gpt-5.6-terra,
|
|
900
|
+
// planner SHOULD bake the override model AND receive terra guidance because:
|
|
901
|
+
// - There IS an explicit per-agent override (planner: "gpt-5.6-terra")
|
|
902
|
+
// - The overlay should NOT be suppressed when there's an override
|
|
903
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
904
|
+
codexHomeOverride: codexHome,
|
|
905
|
+
});
|
|
906
|
+
assert.match(plannerToml, /model = "gpt-5\.6-terra"/, "planner should bake override model with per-agent override");
|
|
907
|
+
assert.match(plannerToml, /exact gpt-5\.6-terra model/, "planner should receive terra guidance with per-agent override");
|
|
908
|
+
// Architect should NOT have override or guidance (no override specified)
|
|
909
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
910
|
+
codexHomeOverride: codexHome,
|
|
911
|
+
});
|
|
912
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model without override");
|
|
913
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance without override");
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
it("resolves profile model before falling back to env vars (Codex P2 finding)", async () => {
|
|
920
|
+
const codexHome = join(tmpdir(), `omx-profile-env-priority-test-${process.pid}`);
|
|
921
|
+
const origFrontierModel = process.env.OMX_DEFAULT_FRONTIER_MODEL;
|
|
922
|
+
try {
|
|
923
|
+
// Create a config.toml with profile-scoped built-in provider and model, but no root model
|
|
924
|
+
// Also set OMX_DEFAULT_FRONTIER_MODEL env var to a different model
|
|
925
|
+
// The profile model should take precedence and planner should return exactModel
|
|
926
|
+
await mkdir(codexHome, { recursive: true });
|
|
927
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
928
|
+
'profile = "work-lane"',
|
|
929
|
+
'',
|
|
930
|
+
'[profiles.work-lane]',
|
|
931
|
+
'model_provider = "openai-chatgpt"',
|
|
932
|
+
'model = "gpt-6-luna"',
|
|
933
|
+
].join("\n"));
|
|
934
|
+
// Set env var to a different model
|
|
935
|
+
process.env.OMX_DEFAULT_FRONTIER_MODEL = "gpt-5.6-terra";
|
|
936
|
+
// With profile-scoped built-in provider and model + env var set,
|
|
937
|
+
// planner should return exactModel (gpt-6-astra), not the env var default
|
|
938
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
939
|
+
codexHomeOverride: codexHome,
|
|
940
|
+
});
|
|
941
|
+
assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel, not env var default, when profile model is set");
|
|
942
|
+
assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance, not fall back to env var");
|
|
943
|
+
// Same for architect
|
|
944
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
945
|
+
codexHomeOverride: codexHome,
|
|
946
|
+
});
|
|
947
|
+
assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel, not env var default");
|
|
948
|
+
assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance");
|
|
949
|
+
}
|
|
950
|
+
finally {
|
|
951
|
+
if (typeof origFrontierModel === "string") {
|
|
952
|
+
process.env.OMX_DEFAULT_FRONTIER_MODEL = origFrontierModel;
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
delete process.env.OMX_DEFAULT_FRONTIER_MODEL;
|
|
956
|
+
}
|
|
957
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
it("resolves profile-scoped model before root fallback (Codex finding 4309857268)", async () => {
|
|
961
|
+
const codexHome = join(tmpdir(), `omx-profile-model-priority-test-${process.pid}`);
|
|
962
|
+
try {
|
|
963
|
+
// Create a config.toml with:
|
|
964
|
+
// - Root model: astra
|
|
965
|
+
// - Profile-scoped custom provider + model: custom_glm
|
|
966
|
+
// When the profile is active, metadata should reflect the profile model, not root
|
|
967
|
+
await mkdir(codexHome, { recursive: true });
|
|
968
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
969
|
+
'profile = "custom-lane"',
|
|
970
|
+
'model = "gpt-6-astra"',
|
|
971
|
+
'',
|
|
972
|
+
'[profiles.custom-lane]',
|
|
973
|
+
'model_provider = "custom-glm-provider"',
|
|
974
|
+
'model = "custom-glm-4.0"',
|
|
975
|
+
].join("\n"));
|
|
976
|
+
// With profile-scoped custom provider + model + root astra,
|
|
977
|
+
// planner should use profile model for metadata, not root astra
|
|
978
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
979
|
+
codexHomeOverride: codexHome,
|
|
980
|
+
});
|
|
981
|
+
// Should NOT emit astra guidance (the root model)
|
|
982
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not resolve to root astra when profile has custom provider with model");
|
|
983
|
+
// Should NOT have astra in resolved_model if custom provider is active
|
|
984
|
+
assert.doesNotMatch(plannerToml, /resolved_model: gpt-6-astra/, "planner metadata should not report root model when profile model is configured");
|
|
985
|
+
}
|
|
986
|
+
finally {
|
|
987
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
it("keeps exact pins when built-in provider omits model field (Codex finding 4309857276)", async () => {
|
|
991
|
+
const codexHome = join(tmpdir(), `omx-builtin-no-model-test-${process.pid}`);
|
|
992
|
+
try {
|
|
993
|
+
// Create a config.toml with explicit built-in provider (openai-chatgpt) but NO model field
|
|
994
|
+
// This should behave like the implicit default provider and keep exact pins
|
|
995
|
+
await mkdir(codexHome, { recursive: true });
|
|
996
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
997
|
+
'model_provider = "openai-chatgpt"',
|
|
998
|
+
// Intentionally NO model field
|
|
999
|
+
].join("\n"));
|
|
1000
|
+
// With explicit built-in provider but no root model,
|
|
1001
|
+
// planner should STILL bake exactModel (like implicit default)
|
|
1002
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
1003
|
+
codexHomeOverride: codexHome,
|
|
1004
|
+
});
|
|
1005
|
+
assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel when built-in provider is explicit even without root model");
|
|
1006
|
+
assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive exact guidance when built-in provider is explicit without root model");
|
|
1007
|
+
// Same for architect
|
|
1008
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
1009
|
+
codexHomeOverride: codexHome,
|
|
1010
|
+
});
|
|
1011
|
+
assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel when built-in provider is explicit without root model");
|
|
1012
|
+
assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive exact guidance when built-in provider is explicit without root model");
|
|
1013
|
+
}
|
|
1014
|
+
finally {
|
|
1015
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
it("omits fallback metadata for custom provider without root or profile model (Codex finding 4309974425)", async () => {
|
|
1019
|
+
const codexHome = join(tmpdir(), `omx-custom-provider-no-model-test-${process.pid}`);
|
|
1020
|
+
try {
|
|
1021
|
+
// Create a config.toml with custom provider but NO root model and NO profile model
|
|
1022
|
+
// The agent will inherit the custom provider's default model at runtime
|
|
1023
|
+
// So we should NOT emit resolved_model metadata claiming astra
|
|
1024
|
+
await mkdir(codexHome, { recursive: true });
|
|
1025
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
1026
|
+
'model_provider = "custom-provider-x"',
|
|
1027
|
+
'',
|
|
1028
|
+
'[model_providers.custom-provider-x]',
|
|
1029
|
+
'name = "custom-provider-x"',
|
|
1030
|
+
'base_url = "http://localhost:8000/v1"',
|
|
1031
|
+
'wire_api = "openai"',
|
|
1032
|
+
'requires_openai_auth = false',
|
|
1033
|
+
'env_key = "CUSTOM_PROVIDER_X_API_KEY"',
|
|
1034
|
+
].join("\n"));
|
|
1035
|
+
// With custom provider and no root/profile model,
|
|
1036
|
+
// planner should NOT emit resolved_model metadata
|
|
1037
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
1038
|
+
codexHomeOverride: codexHome,
|
|
1039
|
+
});
|
|
1040
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider and no root model");
|
|
1041
|
+
assert.doesNotMatch(plannerToml, /resolved_model:/, "planner should not emit resolved_model metadata with custom provider and no root model");
|
|
1042
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not emit astra guidance with custom provider and no root model");
|
|
1043
|
+
// Same for architect
|
|
1044
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
1045
|
+
codexHomeOverride: codexHome,
|
|
1046
|
+
});
|
|
1047
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider and no root model");
|
|
1048
|
+
assert.doesNotMatch(architectToml, /resolved_model:/, "architect should not emit resolved_model metadata with custom provider and no root model");
|
|
1049
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance with custom provider and no root model");
|
|
1050
|
+
}
|
|
1051
|
+
finally {
|
|
1052
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
it("treats mixed-case provider IDs as custom providers (Codex finding new)", async () => {
|
|
1056
|
+
const codexHome = join(tmpdir(), `omx-mixed-case-provider-test-${process.pid}`);
|
|
1057
|
+
try {
|
|
1058
|
+
// Create a config.toml with mixed-case provider ID ("OpenAI" instead of lowercase "openai")
|
|
1059
|
+
// This should be treated as a custom provider, NOT the built-in "openai" provider,
|
|
1060
|
+
// to preserve intentional custom provider configurations.
|
|
1061
|
+
await mkdir(codexHome, { recursive: true });
|
|
1062
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
1063
|
+
'model_provider = "OpenAI"', // Custom provider (not built-in)
|
|
1064
|
+
'model = "gpt-6-astra"',
|
|
1065
|
+
].join("\n"));
|
|
1066
|
+
// With a custom provider, planner should NOT bake exactModel
|
|
1067
|
+
const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
|
|
1068
|
+
codexHomeOverride: codexHome,
|
|
1069
|
+
});
|
|
1070
|
+
assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider");
|
|
1071
|
+
assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive exact guidance with custom provider");
|
|
1072
|
+
// Same for architect
|
|
1073
|
+
const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
|
|
1074
|
+
codexHomeOverride: codexHome,
|
|
1075
|
+
});
|
|
1076
|
+
assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider");
|
|
1077
|
+
assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive exact guidance with custom provider");
|
|
1078
|
+
}
|
|
1079
|
+
finally {
|
|
1080
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
it("applies profile models before built-in fallbacks (Codex finding 4310223529)", async () => {
|
|
1084
|
+
// This test verifies that when a built-in provider has an explicit model configured
|
|
1085
|
+
// (from profile or root), roles without exactModel get that model instead of
|
|
1086
|
+
// falling back to env/class-based defaults.
|
|
1087
|
+
const codexHome = join(tmpdir(), `omx-profile-model-test-${process.pid}`);
|
|
1088
|
+
try {
|
|
1089
|
+
// Create a config with built-in provider and explicit model
|
|
1090
|
+
// For roles without exactModel (like debugger), this model should be used
|
|
1091
|
+
// instead of falling back to resolveStandardModel/resolveFrontierModel
|
|
1092
|
+
await mkdir(codexHome, { recursive: true });
|
|
1093
|
+
await writeFile(join(codexHome, "config.toml"), [
|
|
1094
|
+
'model = "gpt-5.6-terra"',
|
|
1095
|
+
'model_provider = "openai"', // Built-in provider
|
|
1096
|
+
].join("\n"));
|
|
1097
|
+
// Debugger has no exactModel, so with this fix it should:
|
|
1098
|
+
// 1. See built-in provider (openai)
|
|
1099
|
+
// 2. See configured model (gpt-5.6-terra)
|
|
1100
|
+
// 3. Return the configured model instead of falling back to modelClass defaults
|
|
1101
|
+
const debuggerToml = generateAgentToml(AGENT_DEFINITIONS.debugger, "debugger prompt", {
|
|
1102
|
+
codexHomeOverride: codexHome,
|
|
1103
|
+
});
|
|
1104
|
+
assert.match(debuggerToml, /resolved_model: gpt-5\.6-terra/, "debugger should resolve to configured model with built-in provider");
|
|
1105
|
+
}
|
|
1106
|
+
finally {
|
|
1107
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
644
1110
|
});
|
|
645
1111
|
//# sourceMappingURL=native-config.test.js.map
|