sneakoscope 10.3.7 → 10.3.8
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 +12 -181
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/bridge.js +61 -1
- package/dist/commands/codex-app.js +4 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/native-worker-backend-router.js +55 -0
- package/dist/core/codex-app/codex-app-restart-policy.js +20 -0
- package/dist/core/codex-app/codex-context-window.js +61 -24
- package/dist/core/codex-app/menubar/constants.js +4 -0
- package/dist/core/codex-compat/codex-hook-output-builders.js +3 -1
- package/dist/core/codex-lb/bridge-cli-contract.js +2 -0
- package/dist/core/codex-lb/desktop-bridge/exclusive-provider-guard.js +59 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +23 -15
- package/dist/core/codex-lb/desktop-bridge/index.js +1 -0
- package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +10 -3
- package/dist/core/codex-lb/desktop-bridge/security.js +63 -1
- package/dist/core/codex-lb/desktop-bridge/server.js +5 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +5 -1
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +31 -5
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +23 -4
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-catalog.js +39 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +286 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-main-model.js +101 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-status.js +68 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +8 -5
- package/dist/core/codex-lb/desktop-controller-v3.js +26 -2
- package/dist/core/codex-lb/desktop-service.js +13 -2
- package/dist/core/commands/naruto-command.js +1 -1
- package/dist/core/decisions/child-model-choice.js +107 -0
- package/dist/core/decisions/cli.js +19 -2
- package/dist/core/decisions/routing.js +5 -4
- package/dist/core/hooks-runtime/hook-context.js +21 -7
- package/dist/core/hooks-runtime/hook-io.js +3 -2
- package/dist/core/hooks-runtime/jev-spawn-routing.js +83 -5
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -2
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +18 -1
- package/dist/core/hooks-runtime/subagent-context.js +13 -2
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +49 -6
- package/dist/core/hooks-runtime.js +12 -5
- package/dist/core/managed-assets/managed-assets-manifest.js +9 -4
- package/dist/core/subagents/child-model-allowlist.js +179 -0
- package/dist/core/subagents/child-model-plan.js +117 -0
- package/dist/core/subagents/naruto-host-credentials.js +82 -13
- package/dist/core/subagents/official-subagent-config.js +66 -15
- package/dist/core/subagents/official-subagent-preparation.js +45 -12
- package/dist/core/subagents/official-subagent-prompt.js +82 -11
- package/dist/core/subagents/official-subagent-runner.js +3 -0
- package/dist/core/subagents/read-only-list-role.js +73 -0
- package/dist/core/subagents/role-model-preferences.js +54 -17
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -0
- package/dist/native/sks-menubar/Sources/OpenRouterOnlyState.swift +314 -0
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +2 -1
- package/dist/native/sks-menubar/Sources/ProvidersModelExposure.swift +15 -3
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouterOnly.swift +162 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +36 -18
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +10 -4
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +2 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsModels.swift +201 -0
- package/dist/native/sks-menubar/Sources/SubagentModelsViewController.swift +373 -0
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
2
|
+
import { maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
3
|
+
import { readText } from '../../fsx.js';
|
|
4
|
+
import { isOpenRouterModelId } from '../../imagegen/imagegen-config.js';
|
|
5
|
+
import { MAX_SUBAGENT_MODELS, normalizeSubagentModelList, readOpenRouterOnlyStateSync, writeOpenRouterOnlyState } from '../../subagents/child-model-allowlist.js';
|
|
6
|
+
import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
|
|
7
|
+
import { readActiveCombinedBridgeCatalog } from '../combined-catalog.js';
|
|
8
|
+
import { MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
9
|
+
import { ensureUserReadOnlyListRole } from '../../subagents/read-only-list-role.js';
|
|
10
|
+
import { canonicalizeBridgeModelId } from '../route-index.js';
|
|
11
|
+
import { syncCatalogInternal } from './catalog.js';
|
|
12
|
+
import { hasOpenRouterRoute, openRouterOnlyStoreLocation } from './openrouter-only-catalog.js';
|
|
13
|
+
import { applyOpenRouterOnlyMainModel, openRouterOnlyMainModelTarget, restoreOpenRouterOnlyMainModel } from './openrouter-only-main-model.js';
|
|
14
|
+
import { openRouterOnlyProviderBlocker, openRouterOnlyStatusFromCore } from './openrouter-only-status.js';
|
|
15
|
+
import { commandResult, controllerEnv, controllerPaths, nowIso, stringArray } from './shared.js';
|
|
16
|
+
import { loadCore, statusFromCore } from './status.js';
|
|
17
|
+
async function finish(operation, ok, blockers, extra, options) {
|
|
18
|
+
const core = await loadCore(options);
|
|
19
|
+
const status = statusFromCore(core, options);
|
|
20
|
+
assertDesktopBridgeStatusV3(status);
|
|
21
|
+
const openRouterOnly = await openRouterOnlyStatusFromCore(core, options);
|
|
22
|
+
return commandResult(operation, ok, status, {
|
|
23
|
+
openrouter_only: openRouterOnly,
|
|
24
|
+
auth_priority: status.auth_priority,
|
|
25
|
+
...extra
|
|
26
|
+
}, blockers, options);
|
|
27
|
+
}
|
|
28
|
+
function syncBlockers(sync) {
|
|
29
|
+
const activation = sync.activation && typeof sync.activation === 'object' && !Array.isArray(sync.activation)
|
|
30
|
+
? sync.activation
|
|
31
|
+
: {};
|
|
32
|
+
const blockers = stringArray(activation.blockers);
|
|
33
|
+
return blockers.length > 0 ? blockers : ['combined_catalog_sync_failed'];
|
|
34
|
+
}
|
|
35
|
+
export const CODEX_RESTART_FAILED_BLOCKER = 'codex_restart_failed_manual_restart_required';
|
|
36
|
+
export const CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING = 'codex_relaunch_required_for_new_subagent_models';
|
|
37
|
+
export function codexRestartBlockers(restart) {
|
|
38
|
+
return restart?.attempted && !restart.ok ? [CODEX_RESTART_FAILED_BLOCKER] : [];
|
|
39
|
+
}
|
|
40
|
+
function restartCodex(changed, noRestart, options) {
|
|
41
|
+
return maybeRestartRunningCodexApp({
|
|
42
|
+
env: controllerEnv(options),
|
|
43
|
+
changed,
|
|
44
|
+
noRestart,
|
|
45
|
+
...(options.platform ? { platform: options.platform } : {}),
|
|
46
|
+
...(options.codexAppRunningImpl ? { isRunningImpl: options.codexAppRunningImpl } : {}),
|
|
47
|
+
...(options.codexAppRestartImpl ? { restartImpl: options.codexAppRestartImpl } : {})
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export async function openRouterOnlyStatusCommand(options) {
|
|
51
|
+
return finish('openrouter-only.status', true, [], {}, options);
|
|
52
|
+
}
|
|
53
|
+
export async function setOpenRouterOnly(enabled, noRestart, options) {
|
|
54
|
+
if (enabled)
|
|
55
|
+
return enableOpenRouterOnly(noRestart, options);
|
|
56
|
+
const off = await turnOffOpenRouterOnly(options, { repairCatalog: true });
|
|
57
|
+
if (!off.ok)
|
|
58
|
+
return finish('openrouter-only.set', false, off.blockers, { catalog_sync: off.catalog_sync }, options);
|
|
59
|
+
const restart = await restartCodex(off.changed, noRestart, options);
|
|
60
|
+
return finish('openrouter-only.set', true, [...off.blockers, ...codexRestartBlockers(restart)], {
|
|
61
|
+
changed: off.changed,
|
|
62
|
+
catalog_sync: off.catalog_sync,
|
|
63
|
+
main_model: off.main_model,
|
|
64
|
+
codex_restart: restart
|
|
65
|
+
}, options);
|
|
66
|
+
}
|
|
67
|
+
async function seedSubagentModels(home, configText, options) {
|
|
68
|
+
const selected = (await readBridgeModelSelectionState(home, nowIso(options))).selection.openrouter.public_ids;
|
|
69
|
+
const configured = readTopLevelTomlString(configText, 'model');
|
|
70
|
+
const candidates = (selected.length > 0 ? selected : [configured]).filter(isOpenRouterModelId);
|
|
71
|
+
return normalizeSubagentModelList(candidates.slice(0, MAX_SUBAGENT_MODELS).map((model, index) => ({
|
|
72
|
+
model,
|
|
73
|
+
criteria: '',
|
|
74
|
+
reasoning_effort: null,
|
|
75
|
+
default: index === 0
|
|
76
|
+
}))).entries;
|
|
77
|
+
}
|
|
78
|
+
async function enableOpenRouterOnly(noRestart, options) {
|
|
79
|
+
const core = await loadCore(options);
|
|
80
|
+
const location = openRouterOnlyStoreLocation(core.paths);
|
|
81
|
+
const before = readOpenRouterOnlyStateSync(location);
|
|
82
|
+
const providerBlocker = openRouterOnlyProviderBlocker(core);
|
|
83
|
+
if (providerBlocker)
|
|
84
|
+
return finish('openrouter-only.set', false, [providerBlocker], {}, options);
|
|
85
|
+
const entries = before.subagent_models.length > 0
|
|
86
|
+
? before.subagent_models
|
|
87
|
+
: await seedSubagentModels(core.paths.home, core.config, options);
|
|
88
|
+
if (entries.length === 0) {
|
|
89
|
+
return finish('openrouter-only.set', false, ['openrouter_only_no_openrouter_models'], {}, options);
|
|
90
|
+
}
|
|
91
|
+
const sync = await syncCatalogInternal({
|
|
92
|
+
...options,
|
|
93
|
+
settings: { ...(options.settings || {}), auth_priority_enabled: false }
|
|
94
|
+
}, {
|
|
95
|
+
openRouterOnly: { ...before, enabled: true, subagent_models: entries },
|
|
96
|
+
onActivated: async () => {
|
|
97
|
+
await requireRoutableEntry(core.paths, { ...before, enabled: true, subagent_models: entries });
|
|
98
|
+
await writeOpenRouterOnlyState({ enabled: true, subagent_models: entries }, location);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (sync.ok !== true)
|
|
102
|
+
return finish('openrouter-only.set', false, syncBlockers(sync), { catalog_sync: sync }, options);
|
|
103
|
+
const main = await switchMainModelToList(core.paths, location, options)
|
|
104
|
+
.catch(() => failedMainModel('openrouter_only_main_model_switch_failed'));
|
|
105
|
+
const readOnlyRole = await ensureUserReadOnlyListRole(core.paths.home).catch(() => 'failed');
|
|
106
|
+
const roleBlockers = readOnlyRole === 'failed' ? ['openrouter_only_read_only_role_install_failed'] : [];
|
|
107
|
+
const changed = !before.enabled || main.changed || readOnlyRole === 'created' || readOnlyRole === 'updated';
|
|
108
|
+
const restart = await restartCodex(changed, noRestart, options);
|
|
109
|
+
return finish('openrouter-only.set', true, [...main.blockers, ...roleBlockers, ...codexRestartBlockers(restart)], {
|
|
110
|
+
changed,
|
|
111
|
+
seeded_subagent_models: before.subagent_models.length === 0,
|
|
112
|
+
catalog_sync: sync,
|
|
113
|
+
main_model: main,
|
|
114
|
+
read_only_role: readOnlyRole,
|
|
115
|
+
codex_restart: restart
|
|
116
|
+
}, options);
|
|
117
|
+
}
|
|
118
|
+
async function switchMainModelToList(paths, location, options) {
|
|
119
|
+
const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
|
|
120
|
+
const state = readOpenRouterOnlyStateSync(location);
|
|
121
|
+
const main = await applyOpenRouterOnlyMainModel(paths, state, active.ok ? active.route_index : null, options);
|
|
122
|
+
if (main.restore !== state.restore)
|
|
123
|
+
await writeOpenRouterOnlyState({ restore: main.restore }, location);
|
|
124
|
+
return main.change;
|
|
125
|
+
}
|
|
126
|
+
function failedMainModel(blocker) {
|
|
127
|
+
return { changed: false, action: 'blocked', previous_model: null, model: null, write_status: null, blockers: [blocker] };
|
|
128
|
+
}
|
|
129
|
+
async function requireRoutableEntry(paths, state) {
|
|
130
|
+
const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
|
|
131
|
+
if (!openRouterOnlyMainModelTarget(state, active.ok ? active.route_index : null)) {
|
|
132
|
+
throw new Error('openrouter_only_no_routable_subagent_model');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function exposedModelIds(paths) {
|
|
136
|
+
return new Set(((await activeCatalog(paths))?.catalog.models ?? []).map((model) => String(model.public_id)));
|
|
137
|
+
}
|
|
138
|
+
async function activeCatalog(paths) {
|
|
139
|
+
const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
|
|
140
|
+
return active.ok ? active : null;
|
|
141
|
+
}
|
|
142
|
+
async function keepModelInPickerSelection(home, model, options) {
|
|
143
|
+
if (!isOpenRouterModelId(model))
|
|
144
|
+
return { changed: false, blockers: [] };
|
|
145
|
+
const { selection } = await readBridgeModelSelectionState(home, nowIso(options));
|
|
146
|
+
const ids = selection.openrouter.public_ids;
|
|
147
|
+
const key = canonicalizeBridgeModelId(model);
|
|
148
|
+
if (ids.some((id) => canonicalizeBridgeModelId(id) === key))
|
|
149
|
+
return { changed: false, blockers: [] };
|
|
150
|
+
if (ids.length >= MAX_SELECTED_OPENROUTER_MODELS)
|
|
151
|
+
return { changed: false, blockers: ['openrouter_only_main_model_unexposed'] };
|
|
152
|
+
await writeBridgeModelSelection(home, {
|
|
153
|
+
...selection,
|
|
154
|
+
updated_at: nowIso(options),
|
|
155
|
+
openrouter: { mode: 'selected', public_ids: [...ids, model] }
|
|
156
|
+
});
|
|
157
|
+
return { changed: true, blockers: [] };
|
|
158
|
+
}
|
|
159
|
+
async function codexLbRowsMissing(options) {
|
|
160
|
+
const core = await loadCore(options);
|
|
161
|
+
const profile = core.registry.profiles['codex-lb'];
|
|
162
|
+
return profile.enabled && profile.state === 'ready' && core.activeCatalog.ok
|
|
163
|
+
&& !core.activeCatalog.catalog.models.some((model) => model.provider_id === 'codex-lb');
|
|
164
|
+
}
|
|
165
|
+
async function restoreMainModel(paths, location, before, options) {
|
|
166
|
+
const main = await restoreOpenRouterOnlyMainModel(paths, before.restore, options);
|
|
167
|
+
await writeOpenRouterOnlyState({ restore: main.blockers.length > 0 ? before.restore : null }, location);
|
|
168
|
+
return main;
|
|
169
|
+
}
|
|
170
|
+
export async function turnOffOpenRouterOnly(options, behavior = {}) {
|
|
171
|
+
const paths = controllerPaths(options);
|
|
172
|
+
const location = openRouterOnlyStoreLocation(paths);
|
|
173
|
+
const before = readOpenRouterOnlyStateSync(location);
|
|
174
|
+
const repair = !before.enabled && behavior.repairCatalog === true && await codexLbRowsMissing(options);
|
|
175
|
+
if (!before.enabled && !before.restore && !repair) {
|
|
176
|
+
return { ok: true, changed: false, catalog_sync: null, main_model: null, blockers: [] };
|
|
177
|
+
}
|
|
178
|
+
const off = { ...before, enabled: false };
|
|
179
|
+
const sync = (commit) => syncCatalogInternal(options, {
|
|
180
|
+
...(behavior.restartService === undefined ? {} : { restartService: behavior.restartService }),
|
|
181
|
+
openRouterOnly: off,
|
|
182
|
+
...(commit ? { onActivated: () => writeOpenRouterOnlyState({ enabled: false }, location) } : {})
|
|
183
|
+
});
|
|
184
|
+
const configured = readTopLevelTomlString(String(await readText(paths.configPath, '')), 'model');
|
|
185
|
+
const restoreApplies = Boolean(before.restore?.applied_model) && configured === before.restore?.applied_model;
|
|
186
|
+
const kept = restoreApplies ? { changed: false, blockers: [] } : await keepModelInPickerSelection(paths.home, configured, options);
|
|
187
|
+
let catalogSync = null;
|
|
188
|
+
if (before.enabled || repair || kept.changed) {
|
|
189
|
+
catalogSync = await sync(true);
|
|
190
|
+
if (catalogSync.ok !== true) {
|
|
191
|
+
return { ok: false, changed: false, catalog_sync: catalogSync, main_model: null, blockers: syncBlockers(catalogSync) };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const main = await restoreMainModel(paths, location, before, options);
|
|
195
|
+
const blockers = [...kept.blockers, ...main.blockers];
|
|
196
|
+
if (main.action === 'blocked' && !hasOpenRouterRoute((await activeCatalog(paths))?.route_index ?? null, main.model)) {
|
|
197
|
+
const keptAfter = await keepModelInPickerSelection(paths.home, main.model, options);
|
|
198
|
+
blockers.push(...keptAfter.blockers);
|
|
199
|
+
if (keptAfter.changed) {
|
|
200
|
+
catalogSync = await sync(false);
|
|
201
|
+
if (catalogSync.ok !== true)
|
|
202
|
+
blockers.push(...syncBlockers(catalogSync));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
ok: true,
|
|
207
|
+
changed: before.enabled || repair || kept.changed || main.changed,
|
|
208
|
+
catalog_sync: catalogSync,
|
|
209
|
+
main_model: main,
|
|
210
|
+
blockers
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
export async function releaseOpenRouterOnlyWithoutBridge(options) {
|
|
214
|
+
const paths = controllerPaths(options);
|
|
215
|
+
const location = openRouterOnlyStoreLocation(paths);
|
|
216
|
+
const before = readOpenRouterOnlyStateSync(location);
|
|
217
|
+
if (!before.enabled && !before.restore)
|
|
218
|
+
return null;
|
|
219
|
+
if (before.enabled)
|
|
220
|
+
await writeOpenRouterOnlyState({ enabled: false }, location);
|
|
221
|
+
const main = await restoreMainModel(paths, location, before, options);
|
|
222
|
+
return { ok: true, changed: before.enabled || main.changed, catalog_sync: null, main_model: main, blockers: main.blockers };
|
|
223
|
+
}
|
|
224
|
+
export function releaseOpenRouterOnlyForAuthPriority(options) {
|
|
225
|
+
return turnOffOpenRouterOnly(options, { restartService: false });
|
|
226
|
+
}
|
|
227
|
+
export function restartCodexAfterOpenRouterOnlyOff(off, options) {
|
|
228
|
+
return restartCodex(off.changed, false, options);
|
|
229
|
+
}
|
|
230
|
+
async function availableOpenRouterModels(home) {
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(await readText(bridgeAvailableModelsPath(home), '') || 'null');
|
|
233
|
+
const rows = Array.isArray(parsed?.openrouter) ? parsed.openrouter : [];
|
|
234
|
+
return rows.map((row) => ({
|
|
235
|
+
public_id: String(row?.public_id || ''),
|
|
236
|
+
display_name: String(row?.display_name || row?.public_id || '')
|
|
237
|
+
})).filter((row) => row.public_id);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export async function listSubagentModels(options) {
|
|
244
|
+
const available = await availableOpenRouterModels(controllerPaths(options).home);
|
|
245
|
+
return finish('subagent-models.list', true, [], { available }, options);
|
|
246
|
+
}
|
|
247
|
+
export async function setSubagentModels(raw, noRestart, options) {
|
|
248
|
+
if (!Array.isArray(raw))
|
|
249
|
+
return finish('subagent-models.set', false, ['subagent_models_payload_invalid'], {}, options);
|
|
250
|
+
const { entries, issues } = normalizeSubagentModelList(raw);
|
|
251
|
+
if (issues.length > 0) {
|
|
252
|
+
return finish('subagent-models.set', false, issues.map((issue) => `${issue.code}:${issue.index}`), {}, options);
|
|
253
|
+
}
|
|
254
|
+
const paths = controllerPaths(options);
|
|
255
|
+
const location = openRouterOnlyStoreLocation(paths);
|
|
256
|
+
const before = readOpenRouterOnlyStateSync(location);
|
|
257
|
+
if (!before.enabled) {
|
|
258
|
+
await writeOpenRouterOnlyState({ subagent_models: entries }, location);
|
|
259
|
+
return finish('subagent-models.set', true, [], { catalog_sync: null }, options);
|
|
260
|
+
}
|
|
261
|
+
const exposedBefore = await exposedModelIds(paths);
|
|
262
|
+
const sync = await syncCatalogInternal(options, {
|
|
263
|
+
openRouterOnly: { ...before, subagent_models: entries },
|
|
264
|
+
onActivated: async () => {
|
|
265
|
+
if (entries.length > 0)
|
|
266
|
+
await requireRoutableEntry(paths, { ...before, subagent_models: entries });
|
|
267
|
+
await writeOpenRouterOnlyState({ subagent_models: entries }, location);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
if (sync.ok !== true)
|
|
271
|
+
return finish('subagent-models.set', false, syncBlockers(sync), { catalog_sync: sync }, options);
|
|
272
|
+
const main = await switchMainModelToList(paths, location, options)
|
|
273
|
+
.catch(() => failedMainModel('openrouter_only_main_model_switch_failed'));
|
|
274
|
+
const exposedAfter = await exposedModelIds(paths);
|
|
275
|
+
const catalogChanged = [...exposedAfter].some((id) => !exposedBefore.has(id));
|
|
276
|
+
const changed = catalogChanged || main.changed;
|
|
277
|
+
const restart = await restartCodex(changed, noRestart, options);
|
|
278
|
+
const relaunchPending = changed && !restart.attempted && restart.reason !== 'codex_not_running';
|
|
279
|
+
return finish('subagent-models.set', true, [...main.blockers, ...codexRestartBlockers(restart)], {
|
|
280
|
+
changed,
|
|
281
|
+
catalog_sync: sync,
|
|
282
|
+
main_model: main,
|
|
283
|
+
codex_restart: restart,
|
|
284
|
+
warnings: relaunchPending ? [CODEX_RELAUNCH_FOR_SUBAGENT_MODELS_WARNING] : []
|
|
285
|
+
}, options);
|
|
286
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
2
|
+
import { removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml, upsertTopLevelTomlString } from '../../codex-runtime/codex-desktop-config-policy.js';
|
|
3
|
+
import { exists, readText } from '../../fsx.js';
|
|
4
|
+
import { defaultSubagentEntry } from '../../subagents/child-model-allowlist.js';
|
|
5
|
+
import { canonicalizeBridgeModelId } from '../route-index.js';
|
|
6
|
+
import { hasOpenRouterRoute } from './openrouter-only-catalog.js';
|
|
7
|
+
const MAIN_MODEL_WRITE_CAUSE = 'openrouter-only-main-model';
|
|
8
|
+
function tomlBasicStringBody(value) {
|
|
9
|
+
return value.replace(/[\\"\u0000-\u001f\u007f]/g, (character) => character === '\\'
|
|
10
|
+
? '\\\\'
|
|
11
|
+
: character === '"' ? '\\"' : `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
12
|
+
}
|
|
13
|
+
export function openRouterOnlyMainModelTarget(state, routeIndex) {
|
|
14
|
+
const preferred = defaultSubagentEntry(state);
|
|
15
|
+
const ordered = [...(preferred ? [preferred] : []), ...state.subagent_models.filter((entry) => entry !== preferred)];
|
|
16
|
+
for (const entry of ordered) {
|
|
17
|
+
if (hasOpenRouterRoute(routeIndex, entry.model))
|
|
18
|
+
return canonicalizeBridgeModelId(entry.model);
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
async function writeModelLine(paths, before, next, expected, options) {
|
|
23
|
+
const write = await (options.safeWriteConfigImpl || safeWriteCodexConfigToml)(paths.configPath, before, next, MAIN_MODEL_WRITE_CAUSE, { verifyUnchangedBeforeWrite: true, expectedBeforeExists: await exists(paths.configPath) });
|
|
24
|
+
if (!write.ok)
|
|
25
|
+
return { ok: false, status: write.status, blockers: [`openrouter_only_main_model_write_${write.status}`] };
|
|
26
|
+
const after = write.expected_after?.text ?? next;
|
|
27
|
+
if (readTopLevelTomlString(after, 'model') !== expected) {
|
|
28
|
+
return { ok: false, status: write.status, blockers: ['openrouter_only_main_model_write_not_applied'] };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, status: write.status, blockers: [] };
|
|
31
|
+
}
|
|
32
|
+
function topLevelModelLinePresent(text) {
|
|
33
|
+
const lines = text.split('\n');
|
|
34
|
+
const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
|
|
35
|
+
return lines.slice(0, firstTable === -1 ? lines.length : firstTable).some((line) => /^\s*model\s*=/.test(line));
|
|
36
|
+
}
|
|
37
|
+
export async function applyOpenRouterOnlyMainModel(paths, state, routeIndex, options) {
|
|
38
|
+
const before = String(await readText(paths.configPath, ''));
|
|
39
|
+
const current = readTopLevelTomlString(before, 'model');
|
|
40
|
+
if (current === null && topLevelModelLinePresent(before)) {
|
|
41
|
+
return {
|
|
42
|
+
change: {
|
|
43
|
+
changed: false, action: 'blocked', previous_model: null, model: null, write_status: null,
|
|
44
|
+
blockers: ['openrouter_only_main_model_unparsed']
|
|
45
|
+
},
|
|
46
|
+
restore: state.restore
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (hasOpenRouterRoute(routeIndex, current)) {
|
|
50
|
+
return {
|
|
51
|
+
change: { changed: false, action: 'kept_openrouter', previous_model: current, model: current, write_status: null, blockers: [] },
|
|
52
|
+
restore: state.restore
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const target = openRouterOnlyMainModelTarget(state, routeIndex);
|
|
56
|
+
if (!target) {
|
|
57
|
+
return {
|
|
58
|
+
change: {
|
|
59
|
+
changed: false, action: 'blocked', previous_model: current, model: current, write_status: null,
|
|
60
|
+
blockers: ['openrouter_only_main_model_not_openrouter']
|
|
61
|
+
},
|
|
62
|
+
restore: state.restore
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const written = await writeModelLine(paths, before, upsertTopLevelTomlString(before, 'model', tomlBasicStringBody(target)), target, options);
|
|
66
|
+
if (!written.ok) {
|
|
67
|
+
return {
|
|
68
|
+
change: { changed: false, action: 'blocked', previous_model: current, model: current, write_status: written.status, blockers: written.blockers },
|
|
69
|
+
restore: state.restore
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
change: { changed: true, action: 'applied', previous_model: current, model: target, write_status: written.status, blockers: [] },
|
|
74
|
+
restore: { previous_model: current, applied_model: target }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export async function restoreOpenRouterOnlyMainModel(paths, restore, options) {
|
|
78
|
+
const before = String(await readText(paths.configPath, ''));
|
|
79
|
+
const current = readTopLevelTomlString(before, 'model');
|
|
80
|
+
const unchanged = (action) => ({ changed: false, action, previous_model: current, model: current, write_status: null, blockers: [] });
|
|
81
|
+
if (!restore?.applied_model)
|
|
82
|
+
return unchanged('no_record');
|
|
83
|
+
if (current !== restore.applied_model)
|
|
84
|
+
return unchanged('user_changed');
|
|
85
|
+
const previous = restore.previous_model;
|
|
86
|
+
const next = previous === null
|
|
87
|
+
? removeTopLevelTomlKeyIfValue(before, 'model', restore.applied_model)
|
|
88
|
+
: upsertTopLevelTomlString(before, 'model', tomlBasicStringBody(previous));
|
|
89
|
+
const written = await writeModelLine(paths, before, next, previous, options);
|
|
90
|
+
if (!written.ok) {
|
|
91
|
+
return { changed: false, action: 'blocked', previous_model: current, model: current, write_status: written.status, blockers: written.blockers };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
changed: true,
|
|
95
|
+
action: previous === null ? 'removed' : 'restored',
|
|
96
|
+
previous_model: current,
|
|
97
|
+
model: previous,
|
|
98
|
+
write_status: written.status,
|
|
99
|
+
blockers: []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
2
|
+
import { jevEnabled, readDecisionConfig } from '../../decisions/config.js';
|
|
3
|
+
import { readImagegenConfig } from '../../imagegen/imagegen-config.js';
|
|
4
|
+
import { defaultSubagentEntry } from '../../subagents/child-model-allowlist.js';
|
|
5
|
+
import { hasOpenRouterRoute, readControllerOpenRouterOnlyState } from './openrouter-only-catalog.js';
|
|
6
|
+
import { controllerEnv } from './shared.js';
|
|
7
|
+
import { loadCore } from './status.js';
|
|
8
|
+
export const OPENROUTER_ONLY_UNAVAILABLE_ERRORS = [
|
|
9
|
+
'openrouter_only_provider_disabled',
|
|
10
|
+
'openrouter_only_credential_missing',
|
|
11
|
+
'openrouter_only_subagent_list_empty',
|
|
12
|
+
'openrouter_only_main_model_not_openrouter',
|
|
13
|
+
'desktop_bridge_not_running'
|
|
14
|
+
];
|
|
15
|
+
export const OPENROUTER_ONLY_CODEX_IMAGE_MODE_WARNING = 'openrouter_only_codex_image_mode_unavailable';
|
|
16
|
+
export function openRouterOnlyProviderBlocker(core) {
|
|
17
|
+
const profile = core.registry.profiles.openrouter;
|
|
18
|
+
if (!profile.enabled)
|
|
19
|
+
return 'openrouter_only_provider_disabled';
|
|
20
|
+
if (profile.state !== 'ready')
|
|
21
|
+
return 'openrouter_only_credential_missing';
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function activeRouteIndex(core) {
|
|
25
|
+
return core.activeCatalog.ok ? core.activeCatalog.route_index : null;
|
|
26
|
+
}
|
|
27
|
+
export function openRouterOnlyError(core, state) {
|
|
28
|
+
return openRouterOnlyProviderBlocker(core)
|
|
29
|
+
?? (state.subagent_models.length === 0 ? 'openrouter_only_subagent_list_empty' : null)
|
|
30
|
+
?? (hasOpenRouterRoute(activeRouteIndex(core), readTopLevelTomlString(core.config, 'model'))
|
|
31
|
+
? null : 'openrouter_only_main_model_not_openrouter')
|
|
32
|
+
?? (core.service.running ? null : 'desktop_bridge_not_running');
|
|
33
|
+
}
|
|
34
|
+
export async function openRouterOnlyStatusFromCore(core, options, state = readControllerOpenRouterOnlyState(core.paths)) {
|
|
35
|
+
const routeIndex = activeRouteIndex(core);
|
|
36
|
+
const [jev, imagegen] = await Promise.all([
|
|
37
|
+
readDecisionConfig(controllerEnv(options)).then(jevEnabled).catch(() => false),
|
|
38
|
+
readImagegenConfig({ HOME: core.paths.home }).catch(() => null)
|
|
39
|
+
]);
|
|
40
|
+
const subagentModels = state.subagent_models.map((entry) => ({
|
|
41
|
+
model: entry.model,
|
|
42
|
+
criteria: entry.criteria,
|
|
43
|
+
reasoning_effort: entry.reasoning_effort,
|
|
44
|
+
default: entry.default,
|
|
45
|
+
routable: hasOpenRouterRoute(routeIndex, entry.model)
|
|
46
|
+
}));
|
|
47
|
+
const error = state.enabled ? openRouterOnlyError(core, state) : null;
|
|
48
|
+
const warnings = state.enabled
|
|
49
|
+
? [
|
|
50
|
+
...(imagegen?.mode === 'openrouter' ? [] : [OPENROUTER_ONLY_CODEX_IMAGE_MODE_WARNING]),
|
|
51
|
+
...subagentModels.filter((entry) => !entry.routable)
|
|
52
|
+
.map((entry) => `openrouter_only_subagent_model_unroutable:${entry.model}`)
|
|
53
|
+
]
|
|
54
|
+
: [];
|
|
55
|
+
return {
|
|
56
|
+
enabled: state.enabled,
|
|
57
|
+
state: !state.enabled ? 'off' : error ? 'unavailable' : 'active',
|
|
58
|
+
error,
|
|
59
|
+
main_model: readTopLevelTomlString(core.config, 'model'),
|
|
60
|
+
default_subagent_model: defaultSubagentEntry(state)?.model ?? null,
|
|
61
|
+
subagent_models: subagentModels,
|
|
62
|
+
jev_enabled: jev,
|
|
63
|
+
warnings
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function openRouterOnlyStatus(options) {
|
|
67
|
+
return openRouterOnlyStatusFromCore(await loadCore(options), options);
|
|
68
|
+
}
|
|
@@ -8,6 +8,7 @@ import { readBridgeRoutingPolicy, validateBridgeRoutingPolicy } from '../provide
|
|
|
8
8
|
import { sha256Stable } from '../route-index.js';
|
|
9
9
|
import { assertDesktopBridgeStatusV3 } from '../bridge-runtime-validation.js';
|
|
10
10
|
import { desktopBridgeReportReadinessV3, readLastDiagnostic } from './diagnostics.js';
|
|
11
|
+
import { readControllerOpenRouterOnlyState } from './openrouter-only-catalog.js';
|
|
11
12
|
import { activeProviderIds, controllerPaths, emptyScope, makeId, providerCode, providerRegistrySnapshot, recoveryActions, resolveValidatedCredentials, serviceLoopbackOrigin, unique } from './shared.js';
|
|
12
13
|
export async function desktopBridgeStatusV3(options = {}) {
|
|
13
14
|
const core = await loadCore(options);
|
|
@@ -39,10 +40,11 @@ export async function loadCore(options) {
|
|
|
39
40
|
...(snapshot ? { providerRegistry: snapshot } : {}),
|
|
40
41
|
...(policy ? { routePolicy: policy } : {})
|
|
41
42
|
});
|
|
42
|
-
const
|
|
43
|
+
const openRouterOnlyEnabled = readControllerOpenRouterOnlyState(paths).enabled;
|
|
44
|
+
const catalogSync = desktopBridgeCatalogStatusV3(activeCatalog, registry, policy, policyBlockers, checkedAt, openRouterOnlyEnabled ? ['codex-lb'] : []);
|
|
43
45
|
const diagnostic = await readLastDiagnostic(paths.diagnosticPath, catalogSync.generation, service.state?.process_generation || null, service.state?.last_verified_probe_ids || []);
|
|
44
46
|
const settings = await readDesktopBridgeServiceSettings(options.settingsPath || desktopBridgeServicePaths(paths.home).settings_path).catch(() => null);
|
|
45
|
-
return { authPriorityEnabled: settings?.auth_priority_enabled === true, paths, checkedAt, config, credentials, registry, activeCatalog, policy, policyBlockers, service, auth, catalogSync, diagnostic };
|
|
47
|
+
return { authPriorityEnabled: settings?.auth_priority_enabled === true, openRouterOnlyEnabled, paths, checkedAt, config, credentials, registry, activeCatalog, policy, policyBlockers, service, auth, catalogSync, diagnostic };
|
|
46
48
|
}
|
|
47
49
|
export function scopeCatalogBlockersToActiveProviders(combinedBlockers, activeProviderProblems, inactiveProviderProblems) {
|
|
48
50
|
return combinedBlockers.filter((blocker) => activeProviderProblems.has(blocker) || !inactiveProviderProblems.has(blocker));
|
|
@@ -185,7 +187,8 @@ function providerProfileStatus(core, providerId) {
|
|
|
185
187
|
capabilities: core.diagnostic?.report.providers[providerId] || emptyScope(`provider:${providerId}`, core.checkedAt)
|
|
186
188
|
};
|
|
187
189
|
}
|
|
188
|
-
export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlockers, checkedAt) {
|
|
190
|
+
export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlockers, checkedAt, excludedProviders = []) {
|
|
191
|
+
const catalogEnabled = (providerId) => registry.profiles[providerId].enabled && !excludedProviders.includes(providerId);
|
|
189
192
|
const providerRows = Object.fromEntries(['codex-lb', 'openrouter'].map((providerId) => {
|
|
190
193
|
const profile = registry.profiles[providerId];
|
|
191
194
|
const models = active.ok ? active.catalog.models.filter((model) => model.provider_id === providerId) : [];
|
|
@@ -194,7 +197,7 @@ export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlo
|
|
|
194
197
|
const expired = Boolean(persisted?.expires_at && Date.parse(persisted.expires_at) <= Date.parse(checkedAt));
|
|
195
198
|
const state = !active.ok
|
|
196
199
|
? 'not_started'
|
|
197
|
-
: !
|
|
200
|
+
: !catalogEnabled(providerId) ? 'not_started'
|
|
198
201
|
: expired ? 'stale'
|
|
199
202
|
: indexed?.state === 'ready' && models.length > 0
|
|
200
203
|
&& persisted?.state === 'verified' && persisted.generation === indexed.catalog_generation
|
|
@@ -220,7 +223,7 @@ export function desktopBridgeCatalogStatusV3(active, registry, policy, policyBlo
|
|
|
220
223
|
};
|
|
221
224
|
return [providerId, row];
|
|
222
225
|
}));
|
|
223
|
-
const enabled = ['codex-lb', 'openrouter'].filter(
|
|
226
|
+
const enabled = ['codex-lb', 'openrouter'].filter(catalogEnabled);
|
|
224
227
|
const verified = enabled.filter((id) => providerRows[id].state === 'verified');
|
|
225
228
|
const stale = enabled.filter((id) => providerRows[id].state === 'stale');
|
|
226
229
|
const conflicts = active.ok ? active.route_index.conflicts.length : 0;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { listSelectableModels, selectExposedModels, syncCatalog } from './desktop-controller-v3/catalog.js';
|
|
2
2
|
import { explainRoute, ensureDesktopBridge, repairDesktopBridge, rollbackDesktopBridge, setDefaultProvider, setOfficialModelsMode, setAuthPriority, unmanageDesktopBridge } from './desktop-controller-v3/lifecycle-commands.js';
|
|
3
3
|
import { configureProvider, removeCredential, setProviderState, validateProvider } from './desktop-controller-v3/provider-commands.js';
|
|
4
|
+
import { listSubagentModels, openRouterOnlyStatusCommand, setOpenRouterOnly, setSubagentModels } from './desktop-controller-v3/openrouter-only-commands.js';
|
|
5
|
+
import { openRouterOnlyStatus } from './desktop-controller-v3/openrouter-only-status.js';
|
|
4
6
|
import { commandResult, safeCode } from './desktop-controller-v3/shared.js';
|
|
5
7
|
import { desktopBridgeStatusV3 } from './desktop-controller-v3/status.js';
|
|
6
8
|
import { verifyDesktopBridgeV3 } from './desktop-controller-v3/verification.js';
|
|
@@ -8,6 +10,7 @@ import { withFileLock } from '../locks/file-lock.js';
|
|
|
8
10
|
import path from 'node:path';
|
|
9
11
|
export { desktopBridgeDiagnosticBindingCurrentV3, desktopBridgeReportReadinessV3 } from './desktop-controller-v3/diagnostics.js';
|
|
10
12
|
export { runDesktopBridgeDeepProviderProbesV3 } from './desktop-controller-v3/live-probes.js';
|
|
13
|
+
export { openRouterOnlyStatus } from './desktop-controller-v3/openrouter-only-status.js';
|
|
11
14
|
export { desktopBridgeCatalogStatusV3, desktopBridgeStatusV3 } from './desktop-controller-v3/status.js';
|
|
12
15
|
export { verifyDesktopBridgeV3 } from './desktop-controller-v3/verification.js';
|
|
13
16
|
export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
@@ -19,6 +22,10 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
19
22
|
}
|
|
20
23
|
if (request.operation === 'verify')
|
|
21
24
|
return verifyDesktopBridgeV3(request.level, options);
|
|
25
|
+
if (request.operation === 'openrouter-only.status')
|
|
26
|
+
return openRouterOnlyStatusCommand(options);
|
|
27
|
+
if (request.operation === 'subagent-models.list')
|
|
28
|
+
return listSubagentModels(options);
|
|
22
29
|
try {
|
|
23
30
|
const home = path.resolve(options.home || process.env.HOME || '.');
|
|
24
31
|
return await withFileLock({
|
|
@@ -62,6 +69,12 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
62
69
|
return setDefaultProvider(request.provider_id, options);
|
|
63
70
|
if (request.operation === 'auth-priority.set')
|
|
64
71
|
return setAuthPriority(request.enabled, options);
|
|
72
|
+
if (request.operation === 'openrouter-only.set') {
|
|
73
|
+
return setOpenRouterOnly(request.enabled, request.no_restart === true, options);
|
|
74
|
+
}
|
|
75
|
+
if (request.operation === 'subagent-models.set') {
|
|
76
|
+
return setSubagentModels(request.subagent_models, request.no_restart === true, options);
|
|
77
|
+
}
|
|
65
78
|
if (request.operation === 'route.official-models')
|
|
66
79
|
return setOfficialModelsMode(request.mode, options);
|
|
67
80
|
if (request.operation === 'route.explain')
|
|
@@ -74,7 +87,18 @@ export async function executeDesktopBridgeCommandV3(request, options = {}) {
|
|
|
74
87
|
catch (error) {
|
|
75
88
|
const blocker = safeCode(error, 'desktop_bridge_command_failed');
|
|
76
89
|
const status = await desktopBridgeStatusV3(options).catch(() => null);
|
|
77
|
-
return commandResult(request.operation, false, status, request.operation
|
|
78
|
-
? { auth_priority: status.auth_priority } : {}, [blocker], options);
|
|
90
|
+
return commandResult(request.operation, false, status, await failureModeEcho(request.operation, status, options), [blocker], options);
|
|
79
91
|
}
|
|
80
92
|
}
|
|
93
|
+
async function failureModeEcho(operation, status, options) {
|
|
94
|
+
const modeOperation = operation === 'auth-priority.set'
|
|
95
|
+
|| operation.startsWith('openrouter-only.')
|
|
96
|
+
|| operation.startsWith('subagent-models.');
|
|
97
|
+
if (!modeOperation)
|
|
98
|
+
return {};
|
|
99
|
+
const openRouterOnly = await openRouterOnlyStatus(options).catch(() => null);
|
|
100
|
+
return {
|
|
101
|
+
...(status?.auth_priority ? { auth_priority: status.auth_priority } : {}),
|
|
102
|
+
...(openRouterOnly ? { openrouter_only: openRouterOnly } : {})
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -14,6 +14,7 @@ import { captureCodexAuthSnapshot } from './desktop-auth-invariant.js';
|
|
|
14
14
|
import { applyOfficialModelPassthrough, bridgeRoutePolicyPath, writeBridgeRoutingPolicy } from './provider-route-policy.js';
|
|
15
15
|
import { desktopBridgeRuntimeVersion, desktopBridgeRuntimeVersionStale } from './desktop-bridge/state.js';
|
|
16
16
|
import { PACKAGE_VERSION } from '../version.js';
|
|
17
|
+
import { canonicalChildModelId, readOpenRouterOnlyStateSync } from '../subagents/child-model-allowlist.js';
|
|
17
18
|
import { BRIDGE_OFFICIAL_ROUTE_ID } from './bridge-contracts.js';
|
|
18
19
|
import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState, safeBridgeErrorCode, writeDesktopBridgeLaunchdPlist, selectAvailableDesktopBridgePort, startPreparedDesktopBridge, DESKTOP_BRIDGE_STATE_SCHEMA, DESKTOP_BRIDGE_OFFICIAL_UPSTREAM_BASE_URL, DesktopBridgeError, } from './desktop-bridge/index.js';
|
|
19
20
|
export const DEFAULT_DESKTOP_BRIDGE_HOST = '127.0.0.1';
|
|
@@ -230,6 +231,15 @@ async function runtimeCredentials(settings, options) {
|
|
|
230
231
|
});
|
|
231
232
|
return { registry, resolver, sources: Object.fromEntries([...adopted].map(([id, value]) => [id, value.source])), loaded };
|
|
232
233
|
}
|
|
234
|
+
export function resolveDesktopBridgeOpenRouterOnly(input) {
|
|
235
|
+
const state = readOpenRouterOnlyStateSync({ home: input.home, ...(input.env ? { env: input.env } : {}) });
|
|
236
|
+
if (!state.enabled)
|
|
237
|
+
return null;
|
|
238
|
+
const models = state.subagent_models
|
|
239
|
+
.map((entry) => canonicalChildModelId(entry.model))
|
|
240
|
+
.filter((model) => canonicalizeBridgeModelId(model) === model);
|
|
241
|
+
return { enabled: true, subagent_models: [...new Set(models)] };
|
|
242
|
+
}
|
|
233
243
|
function normalizeEndpointForBinding(value) {
|
|
234
244
|
try {
|
|
235
245
|
return new URL(value).toString().replace(/\/+$/, '');
|
|
@@ -258,7 +268,8 @@ export async function resolveDesktopBridgeRuntimeConfig(options = {}) {
|
|
|
258
268
|
throw new Error('desktop_bridge_provider_credentials_unavailable');
|
|
259
269
|
const persistProviderSessionPins = options.persistProviderSessionPins
|
|
260
270
|
|| ((pins) => persistDesktopBridgeSessionPins(paths.settings_path, settings, pins));
|
|
261
|
-
const
|
|
271
|
+
const openRouterOnly = resolveDesktopBridgeOpenRouterOnly({ home, ...(options.env ? { env: options.env } : {}) });
|
|
272
|
+
const config = { providerRegistry: settings.provider_registry, routePolicy: settings.route_policy, providerSessionPins: settings.provider_session_pins, ...(options.resolveRequestRoute ? { resolveRequestRoute: options.resolveRequestRoute } : {}), persistProviderSessionPins, resolveProviderCredential: credentials.resolver, clientCapabilitySha256: settings.client_capability_sha256, listenHost: settings.listen_host, listenPort: settings.listen_port, allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, allowedOrigins: settings.allowed_origins, connectTimeoutMs: settings.connect_timeout_ms, idleTimeoutMs: settings.idle_timeout_ms, officialPassthrough: settings.official_passthrough.enabled ? { baseUrl: settings.official_passthrough.base_url } : null, ...(openRouterOnly ? { openRouterOnly } : {}) };
|
|
262
273
|
const primary = credentials.sources['codex-lb'] || credentials.sources.openrouter;
|
|
263
274
|
if (!primary)
|
|
264
275
|
throw new Error('desktop_bridge_provider_credentials_unavailable');
|
|
@@ -512,7 +523,7 @@ export async function serveDesktopBridge(options = {}) {
|
|
|
512
523
|
},
|
|
513
524
|
},
|
|
514
525
|
});
|
|
515
|
-
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, ...(deferredUpstreams.length ? { deferred_upstreams: deferredUpstreams } : {}), secret_fields_redacted: true })}\n`);
|
|
526
|
+
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', at: new Date().toISOString(), sks_version: PACKAGE_VERSION, pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, ...(deferredUpstreams.length ? { deferred_upstreams: deferredUpstreams } : {}), ...(runtime.config.openRouterOnly ? { openrouter_only: { enabled: true, subagent_model_count: runtime.config.openRouterOnly.subagent_models.length } } : {}), secret_fields_redacted: true })}\n`);
|
|
516
527
|
await waitForShutdown(handle);
|
|
517
528
|
return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
|
|
518
529
|
}
|
|
@@ -44,7 +44,7 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
44
44
|
schema: 'sks.naruto-credential-policy.v1',
|
|
45
45
|
ok: false,
|
|
46
46
|
blockers: credentialPolicy.blockers,
|
|
47
|
-
hint: 'sks naruto run --auth-mode=host --model-provider=<config.toml provider block> --provider-env-key=<ENV NAME>'
|
|
47
|
+
hint: credentialPolicy.hint ?? 'sks naruto run --auth-mode=host --model-provider=<config.toml provider block> --provider-env-key=<ENV NAME>'
|
|
48
48
|
};
|
|
49
49
|
if (args.includes('--json'))
|
|
50
50
|
console.log(JSON.stringify(blocked, null, 2));
|