super-dsh 0.1.9 → 0.1.10-e
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 +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1204 -93
- package/cordis.patch.yml +64 -0
- package/package.json +17 -8
- package/superd-hub/dist/client/agent-settings.d.ts +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -0
- package/superd-hub/dist/client/roster.js +6 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/lib/client/index.js +1204 -93
- package/agent-codex/dist/models.js +0 -152
- package/agent-omp/dist/sidecar-client.js +0 -195
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codex-native-config.ts — reading the foreign runtime's own home, and the one
|
|
3
|
+
* artifact we still put on disk (2026-10-04).
|
|
4
|
+
*
|
|
5
|
+
* Codex is a single-provider harness: its `config.toml` can name exactly ONE
|
|
6
|
+
* `model_provider`, so the LIST of providers a user has configured has nowhere
|
|
7
|
+
* to live natively and lives in the adapter's store instead
|
|
8
|
+
* (`codex-providers.ts`). This module is the other half of that split — the
|
|
9
|
+
* sole authority on what the user's own file actually says.
|
|
10
|
+
*
|
|
11
|
+
* The direction is one-way and stayed that way through the 2026-10-04 rework:
|
|
12
|
+
* `config.toml` is a SOURCE. Nothing here writes it, and nothing writes
|
|
13
|
+
* `auth.json`; a provider reaches a `codex app-server` child as process
|
|
14
|
+
* arguments (see `codex-launch.ts`), which is also why the two protection
|
|
15
|
+
* rules this module used to carry — surgical TOML edits, a merged `auth.json` —
|
|
16
|
+
* are gone rather than kept as dead weight. cc-switch can still own the native
|
|
17
|
+
* file; the sync in `codex-provider-gateway.ts` picks up whatever it leaves
|
|
18
|
+
* there.
|
|
19
|
+
*
|
|
20
|
+
* Two reads live here:
|
|
21
|
+
*
|
|
22
|
+
* - `readNativeSelection` — what the file puts IN FORCE right now (the panel's
|
|
23
|
+
* passive "your own codex runs this" line, and the app-wide default model);
|
|
24
|
+
* - `readNativeProviders` — every `[model_providers.<id>]` table, which is what
|
|
25
|
+
* the sync folds into the canonical store.
|
|
26
|
+
*
|
|
27
|
+
* Plus the per-provider model catalog we generate under the WORLD home. New
|
|
28
|
+
* entries are cloned from a template rather than synthesized: codex 0.154
|
|
29
|
+
* validates a deep, partly nested field set (`supported_reasoning_levels`,
|
|
30
|
+
* `shell_type`, `visibility`, `priority`, `truncation_policy.…`) that only a
|
|
31
|
+
* real catalog entry carries (measured in plan Task 2 — see
|
|
32
|
+
* docs/superpowers/plans/2026-10-03-*).
|
|
33
|
+
*
|
|
34
|
+
* Everything above the fs layer is a pure string→string function, which is
|
|
35
|
+
* where the tests live.
|
|
36
|
+
*
|
|
37
|
+
* @module agent-codex/codex-native-config
|
|
38
|
+
*/
|
|
39
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
40
|
+
import { dirname, join } from "node:path";
|
|
41
|
+
import { isWireProtocol } from "./codex-providers.js";
|
|
42
|
+
/** The catalog file this adapter owns inside the codex home. */
|
|
43
|
+
export const SUPERD_CATALOG_FILE = "superd-model-catalog.json";
|
|
44
|
+
const CONFIG_FILE = "config.toml";
|
|
45
|
+
/** Quote a TOML basic string. Provider ids are slug-validated, but URLs are free. */
|
|
46
|
+
export function tomlString(value) {
|
|
47
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
48
|
+
}
|
|
49
|
+
/** Classify every line, tracking the enclosing table. Tolerant: junk is `other`. */
|
|
50
|
+
function classify(text) {
|
|
51
|
+
const out = [];
|
|
52
|
+
let table = null;
|
|
53
|
+
for (const raw of text.split("\n")) {
|
|
54
|
+
const trimmed = raw.trim();
|
|
55
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
56
|
+
out.push({ text: raw, table });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const header = /^\[([^\]]+)\]$/.exec(trimmed);
|
|
60
|
+
if (header !== null) {
|
|
61
|
+
table = header[1].trim();
|
|
62
|
+
out.push({ text: raw, table, header: table });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const eq = trimmed.indexOf("=");
|
|
66
|
+
out.push(eq > 0 ? { text: raw, table, key: trimmed.slice(0, eq).trim() } : { text: raw, table });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/** The `{models:[…]}` member of a catalog string, or `[]` for anything unusable. */
|
|
71
|
+
function catalogEntries(text) {
|
|
72
|
+
if (text === undefined)
|
|
73
|
+
return [];
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const models = parsed?.models;
|
|
82
|
+
if (!Array.isArray(models))
|
|
83
|
+
return [];
|
|
84
|
+
return models.filter((entry) => entry !== null && typeof entry === "object");
|
|
85
|
+
}
|
|
86
|
+
function slugOf(entry) {
|
|
87
|
+
const slug = entry["slug"];
|
|
88
|
+
return typeof slug === "string" && slug !== "" ? slug : undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the catalog this adapter points `model_catalog_json` at.
|
|
92
|
+
*
|
|
93
|
+
* Starts from `existing` (our file, so previously absorbed templates survive),
|
|
94
|
+
* absorbs anything in `absorbed` (the catalog currently named by the config)
|
|
95
|
+
* that we do not already hold, then upserts `models` by slug. Each upserted
|
|
96
|
+
* entry is a CLONE of `template` — an existing entry for the same slug if there
|
|
97
|
+
* is one, else the caller's template, else the first entry either side supplied
|
|
98
|
+
* — with only the identity fields overridden. The other ~35 keys are Codex's
|
|
99
|
+
* and are copied through untouched; see the plan's D5 for why we do not try to
|
|
100
|
+
* synthesize them.
|
|
101
|
+
*/
|
|
102
|
+
export function mergeModelCatalog(existing, absorbed, models, template) {
|
|
103
|
+
// Both sides are a TEMPLATE POOL only — never output. What gets emitted is
|
|
104
|
+
// exactly `models`, the models the provider we are switching to serves.
|
|
105
|
+
//
|
|
106
|
+
// Replacing rather than accumulating is a correction (2026-10-03, reported
|
|
107
|
+
// from use): the earlier "absorb everything, drop nothing" rule kept every
|
|
108
|
+
// model any previous provider had ever contributed, so the Chat model
|
|
109
|
+
// selector grew without bound — GLM-5.2, then +DeepSeek Flash, then
|
|
110
|
+
// +GLM-5.3 Flash, with a provider that served only the last two. The catalog
|
|
111
|
+
// is Codex's metadata dictionary for the models it is ABOUT to run, and the
|
|
112
|
+
// DSH selector reads it verbatim (`models.ts` → `LlmAdapter.listModels`), so
|
|
113
|
+
// its contents are user-visible as "the models I can pick". cc-switch writes
|
|
114
|
+
// only the active provider's catalog for the same reason; we now match it.
|
|
115
|
+
const pool = [...catalogEntries(existing), ...catalogEntries(absorbed)];
|
|
116
|
+
const bySlug = new Map();
|
|
117
|
+
for (const entry of pool) {
|
|
118
|
+
const slug = slugOf(entry);
|
|
119
|
+
if (slug !== undefined && !bySlug.has(slug))
|
|
120
|
+
bySlug.set(slug, entry);
|
|
121
|
+
}
|
|
122
|
+
const fallback = template ?? pool[0];
|
|
123
|
+
const out = [];
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (const model of models) {
|
|
126
|
+
if (seen.has(model.id))
|
|
127
|
+
continue;
|
|
128
|
+
seen.add(model.id);
|
|
129
|
+
const base = bySlug.get(model.id) ?? fallback ?? {};
|
|
130
|
+
const entry = {
|
|
131
|
+
...base,
|
|
132
|
+
slug: model.id,
|
|
133
|
+
display_name: model.displayName ?? model.id,
|
|
134
|
+
};
|
|
135
|
+
if (model.contextWindow !== undefined) {
|
|
136
|
+
// Codex reads context_window and caps against max_context_window; a stale
|
|
137
|
+
// template value must not silently cap a larger real window.
|
|
138
|
+
entry["context_window"] = model.contextWindow;
|
|
139
|
+
entry["max_context_window"] = model.contextWindow;
|
|
140
|
+
}
|
|
141
|
+
out.push(entry);
|
|
142
|
+
}
|
|
143
|
+
return `${JSON.stringify({ models: out }, null, 2)}\n`;
|
|
144
|
+
}
|
|
145
|
+
/** Read a file, or `undefined` when it is absent or unreadable. */
|
|
146
|
+
function readIfExists(path) {
|
|
147
|
+
if (!existsSync(path))
|
|
148
|
+
return undefined;
|
|
149
|
+
try {
|
|
150
|
+
return readFileSync(path, "utf8");
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** What `config.toml` currently puts in force (fail-soft: `{}` when it says nothing). */
|
|
157
|
+
export function readNativeSelection(codexHome) {
|
|
158
|
+
const text = readIfExists(join(codexHome, CONFIG_FILE));
|
|
159
|
+
if (text === undefined)
|
|
160
|
+
return {};
|
|
161
|
+
const lines = classify(text);
|
|
162
|
+
const top = new Map();
|
|
163
|
+
let providerId;
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
if (line.table !== null || line.key === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
const value = line.text.slice(line.text.indexOf("=") + 1).trim().replace(/^"(.*)"$/, "$1");
|
|
168
|
+
if (line.key === "model_provider")
|
|
169
|
+
providerId = value;
|
|
170
|
+
else if (line.key === "model" || line.key === "model_catalog_json")
|
|
171
|
+
top.set(line.key, value);
|
|
172
|
+
}
|
|
173
|
+
const model = top.get("model");
|
|
174
|
+
if (providerId === undefined) {
|
|
175
|
+
return model === undefined ? {} : { model };
|
|
176
|
+
}
|
|
177
|
+
let baseUrl;
|
|
178
|
+
let protocol;
|
|
179
|
+
let envKey;
|
|
180
|
+
let openaiAuth = false;
|
|
181
|
+
const wanted = `model_providers.${providerId}`;
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (line.table !== wanted || line.key === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
const value = line.text.slice(line.text.indexOf("=") + 1).trim().replace(/^"(.*)"$/, "$1");
|
|
186
|
+
if (line.key === "base_url")
|
|
187
|
+
baseUrl = value;
|
|
188
|
+
else if (line.key === "wire_api")
|
|
189
|
+
protocol = value;
|
|
190
|
+
else if (line.key === "env_key")
|
|
191
|
+
envKey = value;
|
|
192
|
+
else if (line.key === "requires_openai_auth")
|
|
193
|
+
openaiAuth = value === "true";
|
|
194
|
+
}
|
|
195
|
+
const auth = envKey !== undefined
|
|
196
|
+
? { kind: "env", name: envKey }
|
|
197
|
+
: openaiAuth
|
|
198
|
+
? { kind: "apiKey" }
|
|
199
|
+
: undefined;
|
|
200
|
+
return {
|
|
201
|
+
providerId,
|
|
202
|
+
...(model === undefined ? {} : { model }),
|
|
203
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
204
|
+
...(protocol === undefined ? {} : { protocol }),
|
|
205
|
+
...(auth === undefined ? {} : { auth }),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** The catalog file the current config points at, resolved against the home. */
|
|
209
|
+
function currentCatalogPath(configText, codexHome) {
|
|
210
|
+
const text = configText ?? "";
|
|
211
|
+
const found = classify(text).find((line) => line.table === null && line.key === "model_catalog_json");
|
|
212
|
+
if (found === undefined)
|
|
213
|
+
return undefined;
|
|
214
|
+
const raw = found.text.slice(found.text.indexOf("=") + 1).trim().replace(/^"(.*)"$/, "$1");
|
|
215
|
+
if (raw === "")
|
|
216
|
+
return undefined;
|
|
217
|
+
return join(codexHome, raw);
|
|
218
|
+
}
|
|
219
|
+
/** The unquoted value of a `key = value` line, tolerating a trailing comment. */
|
|
220
|
+
function lineValue(line) {
|
|
221
|
+
const raw = line.text.slice(line.text.indexOf("=") + 1).trim();
|
|
222
|
+
const quoted = /^"(.*)"$/.exec(raw) ?? /^'(.*)'$/.exec(raw);
|
|
223
|
+
if (quoted !== null)
|
|
224
|
+
return quoted[1];
|
|
225
|
+
const hash = raw.indexOf(" #");
|
|
226
|
+
return hash >= 0 ? raw.slice(0, hash).trim() : raw;
|
|
227
|
+
}
|
|
228
|
+
/** The model slugs a catalog string advertises, in file order. */
|
|
229
|
+
function catalogSlugs(text) {
|
|
230
|
+
return catalogEntries(text)
|
|
231
|
+
.map((entry) => slugOf(entry))
|
|
232
|
+
.filter((slug) => slug !== undefined);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Parse every `[model_providers.<id>]` table out of the native config.
|
|
236
|
+
*
|
|
237
|
+
* This is the sync SOURCE (2026-10-04): the store is canonical, and the user's
|
|
238
|
+
* own `config.toml` is folded into it by endpoint URL. Only one level of table
|
|
239
|
+
* nesting is read — `model_providers.<id>` and anything under
|
|
240
|
+
* `model_providers.<id>.…` belongs to `<id>` — and a table that declares no
|
|
241
|
+
* usable `base_url` is skipped (there is nothing to dedupe it by).
|
|
242
|
+
*
|
|
243
|
+
* @param codexHome - the NATIVE codex home.
|
|
244
|
+
* @returns every declarable provider, in file order; `[]` when the file is absent.
|
|
245
|
+
*/
|
|
246
|
+
export function readNativeProviders(codexHome) {
|
|
247
|
+
const text = readIfExists(join(codexHome, CONFIG_FILE));
|
|
248
|
+
if (text === undefined)
|
|
249
|
+
return [];
|
|
250
|
+
const lines = classify(text);
|
|
251
|
+
let activeId;
|
|
252
|
+
let activeModel;
|
|
253
|
+
let catalogFile;
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
if (line.table !== null || line.key === undefined)
|
|
256
|
+
continue;
|
|
257
|
+
if (line.key === "model_provider")
|
|
258
|
+
activeId = lineValue(line);
|
|
259
|
+
else if (line.key === "model")
|
|
260
|
+
activeModel = lineValue(line);
|
|
261
|
+
else if (line.key === "model_catalog_json")
|
|
262
|
+
catalogFile = lineValue(line);
|
|
263
|
+
}
|
|
264
|
+
const drafts = new Map();
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
const table = line.table;
|
|
267
|
+
if (table === null || line.key === undefined)
|
|
268
|
+
continue;
|
|
269
|
+
if (table !== "model_providers" && !table.startsWith("model_providers."))
|
|
270
|
+
continue;
|
|
271
|
+
const rest = table.slice("model_providers.".length);
|
|
272
|
+
if (rest === "")
|
|
273
|
+
continue;
|
|
274
|
+
const id = rest.split(".")[0];
|
|
275
|
+
if (id === "")
|
|
276
|
+
continue;
|
|
277
|
+
let draft = drafts.get(id);
|
|
278
|
+
if (draft === undefined) {
|
|
279
|
+
draft = { id };
|
|
280
|
+
drafts.set(id, draft);
|
|
281
|
+
}
|
|
282
|
+
// A nested sub-table's keys are not the provider's own; only the top level counts.
|
|
283
|
+
if (rest !== id)
|
|
284
|
+
continue;
|
|
285
|
+
const value = lineValue(line);
|
|
286
|
+
if (line.key === "name")
|
|
287
|
+
draft.label = value;
|
|
288
|
+
else if (line.key === "base_url")
|
|
289
|
+
draft.baseUrl = value;
|
|
290
|
+
else if (line.key === "wire_api")
|
|
291
|
+
draft.protocol = value;
|
|
292
|
+
else if (line.key === "env_key")
|
|
293
|
+
draft.envKey = value;
|
|
294
|
+
else if (line.key === "requires_openai_auth")
|
|
295
|
+
draft.openaiAuth = value === "true";
|
|
296
|
+
}
|
|
297
|
+
// The catalog the native home currently points at — the only model source,
|
|
298
|
+
// and only for the provider in force.
|
|
299
|
+
const activeSlugs = activeId !== undefined && catalogFile !== undefined && catalogFile !== ""
|
|
300
|
+
? catalogSlugs(readIfExists(join(codexHome, catalogFile)))
|
|
301
|
+
: [];
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const draft of drafts.values()) {
|
|
304
|
+
const baseUrl = nonEmpty(draft.baseUrl);
|
|
305
|
+
if (baseUrl === undefined)
|
|
306
|
+
continue;
|
|
307
|
+
const active = draft.id === activeId;
|
|
308
|
+
const models = active
|
|
309
|
+
? [...new Set([...(activeModel === undefined ? [] : [activeModel]), ...activeSlugs])]
|
|
310
|
+
: [];
|
|
311
|
+
out.push({
|
|
312
|
+
id: draft.id,
|
|
313
|
+
label: nonEmpty(draft.label) ?? draft.id,
|
|
314
|
+
baseUrl,
|
|
315
|
+
protocol: isWireProtocol(draft.protocol) ? draft.protocol : "responses",
|
|
316
|
+
auth: draft.envKey !== undefined && draft.envKey !== ""
|
|
317
|
+
? { kind: "env", name: draft.envKey }
|
|
318
|
+
: { kind: "apiKey", value: "" },
|
|
319
|
+
models,
|
|
320
|
+
active,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
/* -------------------------------------------------------------------------- */
|
|
326
|
+
/* Per-provider model catalogs (adapter-owned; the native home is never written) */
|
|
327
|
+
/* -------------------------------------------------------------------------- */
|
|
328
|
+
/** The directory holding one catalog file per provider, under the world home. */
|
|
329
|
+
export function providerCatalogDir(worldHome) {
|
|
330
|
+
return join(worldHome, "codex-catalogs");
|
|
331
|
+
}
|
|
332
|
+
/** The catalog file this adapter points `model_catalog_json` at for one provider. */
|
|
333
|
+
export function providerCatalogPath(worldHome, providerId) {
|
|
334
|
+
return join(providerCatalogDir(worldHome), `${providerId}.json`);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The template every generated entry is cloned from.
|
|
338
|
+
*
|
|
339
|
+
* Codex 0.154 refuses a synthesized catalog entry: it validates a deep, partly
|
|
340
|
+
* nested field set (`supported_reasoning_levels`, `shell_type`, `visibility`,
|
|
341
|
+
* `priority`, `truncation_policy.…`) that only a REAL catalog entry carries.
|
|
342
|
+
* So the first entry of any catalog we can read — ours, or the one the user's
|
|
343
|
+
* own config points at — is the source of the ~35 fields we do not own.
|
|
344
|
+
*
|
|
345
|
+
* @param codexHome - the native home, to find the config-named catalog.
|
|
346
|
+
* @param worldHome - the world home, holding our previously generated catalogs.
|
|
347
|
+
* @returns a template entry, or `undefined` when nothing readable exists yet.
|
|
348
|
+
*/
|
|
349
|
+
export function catalogTemplate(codexHome, worldHome) {
|
|
350
|
+
const configText = readIfExists(join(codexHome, CONFIG_FILE));
|
|
351
|
+
const absorbedPath = currentCatalogPath(configText, codexHome);
|
|
352
|
+
const sources = [
|
|
353
|
+
readIfExists(providerCatalogPath(worldHome, "superd-model-catalog")),
|
|
354
|
+
absorbedPath === undefined ? undefined : readIfExists(absorbedPath),
|
|
355
|
+
readIfExists(join(codexHome, SUPERD_CATALOG_FILE)),
|
|
356
|
+
];
|
|
357
|
+
for (const text of sources) {
|
|
358
|
+
const first = catalogEntries(text)[0];
|
|
359
|
+
if (first !== undefined)
|
|
360
|
+
return first;
|
|
361
|
+
}
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Materialize one provider's model catalog into the ADAPTER's own tree.
|
|
366
|
+
*
|
|
367
|
+
* Nothing is written into `~/.codex` any more (2026-10-04 on-the-fly rework):
|
|
368
|
+
* the file lives at `<worldHome>/codex-catalogs/<id>.json` and reaches the child
|
|
369
|
+
* through `-c model_catalog_json="<absolute path>"`. Codex resolves that key
|
|
370
|
+
* against the process cwd when it arrives as an override, so the path is
|
|
371
|
+
* absolute on purpose (measured: a relative path fails `ENOENT`).
|
|
372
|
+
*
|
|
373
|
+
* @param worldHome - the world's DSH home (adapter state root).
|
|
374
|
+
* @param codexHome - the native home, only read (template material).
|
|
375
|
+
* @param provider - the provider whose models the catalog must advertise.
|
|
376
|
+
* @returns the absolute catalog path, or `undefined` when the provider has no models.
|
|
377
|
+
*/
|
|
378
|
+
export function writeProviderCatalog(worldHome, codexHome, provider) {
|
|
379
|
+
if (provider.models.length === 0)
|
|
380
|
+
return undefined;
|
|
381
|
+
const path = providerCatalogPath(worldHome, provider.id);
|
|
382
|
+
const next = mergeModelCatalog(readIfExists(path), undefined, provider.models, catalogTemplate(codexHome, worldHome));
|
|
383
|
+
if (readIfExists(path) !== next) {
|
|
384
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
385
|
+
writeFileSync(path, next, { encoding: "utf8", mode: 0o644 });
|
|
386
|
+
}
|
|
387
|
+
return path;
|
|
388
|
+
}
|
|
389
|
+
/** A trimmed non-empty string, or `undefined`. */
|
|
390
|
+
function nonEmpty(value) {
|
|
391
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
392
|
+
}
|