sneakoscope 10.7.0 → 10.7.2
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 +27 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +55 -20
- package/dist/commands/bridge.js +14 -1
- package/dist/commands/codex.js +5 -1
- package/dist/commands/doctor-console.js +2 -0
- package/dist/commands/doctor.js +8 -9
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/agent-role-config.js +6 -2
- package/dist/core/agents/sks-model-pin-hygiene.js +150 -0
- package/dist/core/codex/codex-config-guard.js +18 -13
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +7 -1
- package/dist/core/codex-app/menubar/constants.js +0 -1
- package/dist/core/codex-lb/bridge-contracts.js +5 -0
- package/dist/core/codex-lb/combined-catalog/builder.js +4 -2
- package/dist/core/codex-lb/combined-catalog/latest-generation.js +7 -34
- package/dist/core/codex-lb/combined-catalog/normalize.js +3 -1
- package/dist/core/codex-lb/combined-catalog/persistence.js +4 -0
- package/dist/core/codex-lb/combined-catalog/storage.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/codex-lb-turn-state.js +35 -0
- package/dist/core/codex-lb/desktop-bridge/compaction-adapter.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +10 -4
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +138 -36
- package/dist/core/codex-lb/desktop-bridge/models-endpoint.js +62 -0
- package/dist/core/codex-lb/desktop-bridge/provider-handoff.js +88 -0
- package/dist/core/codex-lb/desktop-bridge/provider-request-compat.js +89 -0
- package/dist/core/codex-lb/desktop-bridge/rejection-log.js +61 -0
- package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +108 -16
- package/dist/core/codex-lb/desktop-bridge/security.js +101 -55
- package/dist/core/codex-lb/desktop-bridge/server.js +15 -2
- package/dist/core/codex-lb/desktop-bridge/types.js +3 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +3 -0
- package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +2 -6
- package/dist/core/codex-lb/desktop-bridge-migration.js +14 -2
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +125 -3
- package/dist/core/codex-lb/desktop-controller-v3/model-refresh.js +140 -0
- package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +5 -5
- package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +14 -11
- package/dist/core/codex-lb/desktop-controller-v3/status.js +2 -3
- package/dist/core/codex-lb/desktop-controller-v3.js +3 -1
- package/dist/core/codex-lb/desktop-service.js +51 -17
- package/dist/core/codex-lb/model-refresh-after.js +16 -0
- package/dist/core/codex-lb/request-route-resolver.js +1 -1
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +0 -1
- package/dist/core/commands/basic-cli.js +5 -0
- package/dist/core/commands/menubar-command.js +5 -1
- package/dist/core/doctor/desktop-bridge-catalog-repair.js +20 -17
- package/dist/core/hooks-runtime/jev-spawn-routing.js +1 -5
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +6 -8
- package/dist/core/hooks-runtime.js +13 -9
- package/dist/core/init/skills.js +22 -3
- package/dist/core/provider/provider-context.js +4 -1
- package/dist/core/search/context.js +2 -2
- package/dist/core/subagents/model-tiers.js +19 -3
- package/dist/core/update/update-migration-state/desktop-bridge-catalog-repair-stage.js +4 -3
- package/dist/core/update/update-migration-state.js +10 -40
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +0 -1
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +32 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +15 -5
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +0 -2
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +3 -0
- package/package.json +2 -2
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +0 -49
|
@@ -209,14 +209,45 @@ function canonicalPublicModel(value) {
|
|
|
209
209
|
export function desktopBridgeOfficialPassthroughEnabled(config) {
|
|
210
210
|
return Boolean(config.officialPassthrough?.baseUrl);
|
|
211
211
|
}
|
|
212
|
-
function
|
|
212
|
+
function mintSessionPin(sessionId, target, publicModel, policy, previous) {
|
|
213
|
+
const handoffFrom = previous && previous.provider_id !== target.provider_id ? previous.provider_id : previous?.handoff_from;
|
|
214
|
+
return {
|
|
215
|
+
thread_id: sessionId,
|
|
216
|
+
provider_id: target.provider_id,
|
|
217
|
+
public_model: publicModel,
|
|
218
|
+
upstream_model: target.upstream_model,
|
|
219
|
+
catalog_generation: policy.catalog_generation,
|
|
220
|
+
route_policy_generation: policy.policy_generation,
|
|
221
|
+
created_at: new Date().toISOString(),
|
|
222
|
+
...(handoffFrom ? { handoff_from: handoffFrom } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function officialRouteContext(publicModel, policy, upstreamModel, pin = null) {
|
|
213
226
|
return {
|
|
214
227
|
provider_id: BRIDGE_OFFICIAL_ROUTE_ID,
|
|
215
228
|
public_model: publicModel,
|
|
216
229
|
upstream_model: upstreamModel || publicModel,
|
|
217
230
|
catalog_generation: policy.catalog_generation,
|
|
218
231
|
route_policy_generation: policy.policy_generation,
|
|
219
|
-
session_pin:
|
|
232
|
+
session_pin: pin,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function pinnedRouteContext(pin, publicModel, policy) {
|
|
236
|
+
const aged = pin.catalog_generation !== policy.catalog_generation
|
|
237
|
+
|| pin.route_policy_generation !== policy.policy_generation;
|
|
238
|
+
return {
|
|
239
|
+
provider_id: pin.provider_id,
|
|
240
|
+
public_model: publicModel,
|
|
241
|
+
upstream_model: pin.upstream_model,
|
|
242
|
+
catalog_generation: policy.catalog_generation,
|
|
243
|
+
route_policy_generation: policy.policy_generation,
|
|
244
|
+
session_pin: aged
|
|
245
|
+
? {
|
|
246
|
+
...pin,
|
|
247
|
+
catalog_generation: policy.catalog_generation,
|
|
248
|
+
route_policy_generation: policy.policy_generation,
|
|
249
|
+
}
|
|
250
|
+
: pin,
|
|
220
251
|
};
|
|
221
252
|
}
|
|
222
253
|
export function resolveBridgeRequestRoute(request, policy, pins) {
|
|
@@ -224,43 +255,22 @@ export function resolveBridgeRequestRoute(request, policy, pins) {
|
|
|
224
255
|
const sessionId = request.session_id ? canonicalSessionId(request.session_id) : null;
|
|
225
256
|
const pin = sessionId ? pins.find((entry) => entry.thread_id === sessionId) || null : null;
|
|
226
257
|
if (pin && pin.public_model === publicModel) {
|
|
258
|
+
if (pin.provider_id === BRIDGE_OFFICIAL_ROUTE_ID)
|
|
259
|
+
return pinnedRouteContext(pin, publicModel, policy);
|
|
227
260
|
const bare = policy.model_routes[publicModel];
|
|
228
261
|
const qualified = policy.model_routes[`${pin.provider_id}:${publicModel}`];
|
|
229
262
|
const current = bare?.provider_id === pin.provider_id && bare.upstream_model === pin.upstream_model ? bare : qualified;
|
|
230
263
|
if (!current || current.provider_id !== pin.provider_id || current.upstream_model !== pin.upstream_model) {
|
|
231
264
|
throw new DesktopBridgeError('session_pin_route_unavailable');
|
|
232
265
|
}
|
|
233
|
-
|
|
234
|
-
|| pin.route_policy_generation !== policy.policy_generation;
|
|
235
|
-
return {
|
|
236
|
-
provider_id: pin.provider_id,
|
|
237
|
-
public_model: publicModel,
|
|
238
|
-
upstream_model: pin.upstream_model,
|
|
239
|
-
catalog_generation: policy.catalog_generation,
|
|
240
|
-
route_policy_generation: policy.policy_generation,
|
|
241
|
-
session_pin: aged
|
|
242
|
-
? {
|
|
243
|
-
...pin,
|
|
244
|
-
catalog_generation: policy.catalog_generation,
|
|
245
|
-
route_policy_generation: policy.policy_generation,
|
|
246
|
-
}
|
|
247
|
-
: pin,
|
|
248
|
-
};
|
|
266
|
+
return pinnedRouteContext(pin, publicModel, policy);
|
|
249
267
|
}
|
|
250
268
|
const route = policy.model_routes[publicModel];
|
|
251
269
|
if (!route)
|
|
252
270
|
throw new DesktopBridgeError('catalog_model_route_missing');
|
|
271
|
+
const nextPin = sessionId ? mintSessionPin(sessionId, route, publicModel, policy, pin) : null;
|
|
253
272
|
if (route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID)
|
|
254
|
-
return officialRouteContext(publicModel, policy, route.upstream_model);
|
|
255
|
-
const nextPin = sessionId ? {
|
|
256
|
-
thread_id: sessionId,
|
|
257
|
-
provider_id: route.provider_id,
|
|
258
|
-
public_model: publicModel,
|
|
259
|
-
upstream_model: route.upstream_model,
|
|
260
|
-
catalog_generation: policy.catalog_generation,
|
|
261
|
-
route_policy_generation: policy.policy_generation,
|
|
262
|
-
created_at: new Date().toISOString(),
|
|
263
|
-
} : null;
|
|
273
|
+
return officialRouteContext(publicModel, policy, route.upstream_model, nextPin);
|
|
264
274
|
return {
|
|
265
275
|
provider_id: route.provider_id,
|
|
266
276
|
public_model: publicModel,
|
|
@@ -270,7 +280,7 @@ export function resolveBridgeRequestRoute(request, policy, pins) {
|
|
|
270
280
|
session_pin: nextPin,
|
|
271
281
|
};
|
|
272
282
|
}
|
|
273
|
-
function
|
|
283
|
+
function threadPin(request, pins) {
|
|
274
284
|
if (!request.session_id)
|
|
275
285
|
return null;
|
|
276
286
|
let threadId;
|
|
@@ -280,7 +290,25 @@ function threadPinProvider(request, config) {
|
|
|
280
290
|
catch {
|
|
281
291
|
return null;
|
|
282
292
|
}
|
|
283
|
-
return
|
|
293
|
+
return pins.find((pin) => pin.thread_id === threadId) ?? null;
|
|
294
|
+
}
|
|
295
|
+
function threadPinProvider(request, config) {
|
|
296
|
+
return threadPin(request, config.providerSessionPins)?.provider_id ?? null;
|
|
297
|
+
}
|
|
298
|
+
export function pinnedPublicModel(threadId, config) {
|
|
299
|
+
if (!threadId)
|
|
300
|
+
return null;
|
|
301
|
+
return threadPin({ session_id: threadId }, config.providerSessionPins)?.public_model ?? null;
|
|
302
|
+
}
|
|
303
|
+
const RESPONSES_PATH_PREFIXES = ['/backend-api/codex/responses', '/api/v1/responses', '/v1/responses'];
|
|
304
|
+
export function isResponsesFamilyPath(pathname) {
|
|
305
|
+
return RESPONSES_PATH_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
|
|
306
|
+
}
|
|
307
|
+
export function threadRouteMove(request, route, config) {
|
|
308
|
+
if (!route.session_pin)
|
|
309
|
+
return null;
|
|
310
|
+
const previous = threadPin(request, config.providerSessionPins);
|
|
311
|
+
return previous && previous.provider_id !== route.provider_id ? { from: previous.provider_id, to: route.provider_id } : null;
|
|
284
312
|
}
|
|
285
313
|
export function assertDesktopBridgeRouteContext(request, config) {
|
|
286
314
|
const resolver = config.resolveRequestRoute || resolveBridgeRequestRoute;
|
|
@@ -295,8 +323,16 @@ export function assertDesktopBridgeRouteContext(request, config) {
|
|
|
295
323
|
&& error.code === 'catalog_model_route_missing') {
|
|
296
324
|
const model = canonicalizeBridgeModelId(request.public_model) || '';
|
|
297
325
|
const live = model ? policy.model_routes[model] : undefined;
|
|
326
|
+
const pin = threadPin(request, config.providerSessionPins);
|
|
327
|
+
const responses = isResponsesFamilyPath(request.pathname);
|
|
328
|
+
if (pin && pin.provider_id !== BRIDGE_OFFICIAL_ROUTE_ID && responses) {
|
|
329
|
+
assertOpenRouterOnlyRoute(request, officialRouteContext(model, policy), config);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
298
332
|
if (!live || live.provider_id === BRIDGE_OFFICIAL_ROUTE_ID) {
|
|
299
|
-
|
|
333
|
+
const sessionId = model && responses && request.session_id ? canonicalSessionId(request.session_id) : null;
|
|
334
|
+
const target = { provider_id: BRIDGE_OFFICIAL_ROUTE_ID, upstream_model: live?.upstream_model || model };
|
|
335
|
+
route = officialRouteContext(model, policy, live?.upstream_model, sessionId ? mintSessionPin(sessionId, target, model, policy, pin) : null);
|
|
300
336
|
}
|
|
301
337
|
else
|
|
302
338
|
throw error;
|
|
@@ -315,8 +351,7 @@ export function assertDesktopBridgeRouteContext(request, config) {
|
|
|
315
351
|
if (route.catalog_generation !== policy.catalog_generation || route.route_policy_generation !== policy.policy_generation) {
|
|
316
352
|
throw new DesktopBridgeError('session_pin_route_unavailable');
|
|
317
353
|
}
|
|
318
|
-
|
|
319
|
-
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
354
|
+
assertRouteSessionPin(request, route, false);
|
|
320
355
|
return route;
|
|
321
356
|
}
|
|
322
357
|
const existingPin = request.session_id ? config.providerSessionPins.find((pin) => pin.thread_id === request.session_id && pin.public_model === route.public_model
|
|
@@ -330,21 +365,7 @@ export function assertDesktopBridgeRouteContext(request, config) {
|
|
|
330
365
|
if (route.catalog_generation !== policy.catalog_generation || route.route_policy_generation !== policy.policy_generation) {
|
|
331
366
|
throw new DesktopBridgeError('session_pin_route_unavailable');
|
|
332
367
|
}
|
|
333
|
-
|
|
334
|
-
const sessionId = canonicalSessionId(request.session_id);
|
|
335
|
-
if (!route.session_pin
|
|
336
|
-
|| route.session_pin.thread_id !== sessionId
|
|
337
|
-
|| route.session_pin.provider_id !== route.provider_id
|
|
338
|
-
|| route.session_pin.public_model !== route.public_model
|
|
339
|
-
|| route.session_pin.upstream_model !== route.upstream_model
|
|
340
|
-
|| route.session_pin.catalog_generation !== route.catalog_generation
|
|
341
|
-
|| route.session_pin.route_policy_generation !== route.route_policy_generation) {
|
|
342
|
-
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
else if (route.session_pin) {
|
|
346
|
-
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
347
|
-
}
|
|
368
|
+
assertRouteSessionPin(request, route, true);
|
|
348
369
|
const provider = config.providers[route.provider_id];
|
|
349
370
|
if (!provider || !provider.enabled)
|
|
350
371
|
throw new DesktopBridgeError('bridge_provider_route_unavailable');
|
|
@@ -358,7 +379,28 @@ export function assertDesktopBridgeRouteContext(request, config) {
|
|
|
358
379
|
}
|
|
359
380
|
return route;
|
|
360
381
|
}
|
|
361
|
-
|
|
382
|
+
function assertRouteSessionPin(request, route, required) {
|
|
383
|
+
if (!request.session_id) {
|
|
384
|
+
if (route.session_pin)
|
|
385
|
+
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (!route.session_pin) {
|
|
389
|
+
if (required)
|
|
390
|
+
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const sessionId = canonicalSessionId(request.session_id);
|
|
394
|
+
if (route.session_pin.thread_id !== sessionId
|
|
395
|
+
|| route.session_pin.provider_id !== route.provider_id
|
|
396
|
+
|| route.session_pin.public_model !== route.public_model
|
|
397
|
+
|| route.session_pin.upstream_model !== route.upstream_model
|
|
398
|
+
|| route.session_pin.catalog_generation !== route.catalog_generation
|
|
399
|
+
|| route.session_pin.route_policy_generation !== route.route_policy_generation) {
|
|
400
|
+
throw new DesktopBridgeError('bridge_session_pin_invalid');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
export async function resolveAndBindDesktopBridgeRouteContext(request, config, beforeBind) {
|
|
362
404
|
const pinsRead = config.providerSessionPins;
|
|
363
405
|
let steady = null;
|
|
364
406
|
try {
|
|
@@ -368,13 +410,14 @@ export async function resolveAndBindDesktopBridgeRouteContext(request, config) {
|
|
|
368
410
|
steady = null;
|
|
369
411
|
}
|
|
370
412
|
const steadyPin = steady?.session_pin;
|
|
371
|
-
if (steady && !steadyPin)
|
|
372
|
-
return steady;
|
|
373
413
|
const persisted = steadyPin ? pinsRead.find((entry) => entry.thread_id === steadyPin.thread_id) : undefined;
|
|
374
|
-
if (steady && steadyPin
|
|
414
|
+
if (steady && (!steadyPin || (persisted && sameSessionPin(persisted, steadyPin)))) {
|
|
415
|
+
beforeBind?.(steady);
|
|
375
416
|
return steady;
|
|
417
|
+
}
|
|
376
418
|
return withSessionPinMutation(config, async () => {
|
|
377
419
|
const route = steady && config.providerSessionPins === pinsRead ? steady : assertDesktopBridgeRouteContext(request, config);
|
|
420
|
+
beforeBind?.(route);
|
|
378
421
|
const pin = route.session_pin;
|
|
379
422
|
if (!pin)
|
|
380
423
|
return route;
|
|
@@ -418,7 +461,8 @@ function sameSessionPin(left, right) {
|
|
|
418
461
|
&& left.public_model === right.public_model
|
|
419
462
|
&& left.upstream_model === right.upstream_model
|
|
420
463
|
&& left.catalog_generation === right.catalog_generation
|
|
421
|
-
&& left.route_policy_generation === right.route_policy_generation
|
|
464
|
+
&& left.route_policy_generation === right.route_policy_generation
|
|
465
|
+
&& left.handoff_from === right.handoff_from;
|
|
422
466
|
}
|
|
423
467
|
export function canonicalSessionId(value) {
|
|
424
468
|
const sessionId = typeof value === 'string' ? value.trim() : '';
|
|
@@ -634,15 +678,17 @@ function assertRegistryAndPolicy(config, registry) {
|
|
|
634
678
|
}
|
|
635
679
|
}
|
|
636
680
|
const pinIds = new Set();
|
|
681
|
+
const pinTargets = [...ids, BRIDGE_OFFICIAL_ROUTE_ID];
|
|
637
682
|
for (const pin of config.providerSessionPins) {
|
|
638
683
|
const keys = Object.keys(pin);
|
|
639
684
|
if (keys.some((key) => ![
|
|
640
685
|
'thread_id', 'provider_id', 'public_model', 'upstream_model',
|
|
641
|
-
'catalog_generation', 'route_policy_generation', 'created_at',
|
|
686
|
+
'catalog_generation', 'route_policy_generation', 'created_at', 'handoff_from',
|
|
642
687
|
].includes(key))
|
|
643
688
|
|| canonicalSessionId(pin.thread_id) !== pin.thread_id
|
|
644
689
|
|| pinIds.has(pin.thread_id)
|
|
645
|
-
|| !
|
|
690
|
+
|| !pinTargets.includes(pin.provider_id)
|
|
691
|
+
|| (pin.handoff_from !== undefined && (!pinTargets.includes(pin.handoff_from) || pin.handoff_from === pin.provider_id))
|
|
646
692
|
|| canonicalizeBridgeModelId(pin.public_model) !== pin.public_model
|
|
647
693
|
|| normalizeBridgeUpstreamModelId(pin.upstream_model) !== pin.upstream_model
|
|
648
694
|
|| !pin.catalog_generation
|
|
@@ -6,10 +6,12 @@ import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
|
|
|
6
6
|
import { OPENROUTER_ONLY_REFUSAL_CODES } from './exclusive-provider-guard.js';
|
|
7
7
|
import { assertAllowedOrigin, assertAllowedPath, assertLoopbackPeer, assertLoopbackListenHost, assertWebSocketUpgrade, prepareDesktopBridgeConfig, safeBridgeErrorCode, validatePreparedDesktopBridgeConfig, } from './security.js';
|
|
8
8
|
import { createDesktopBridgePublicState, desktopBridgeListenOrigin, desktopBridgeStatePath, refreshDesktopBridgeState, removeDesktopBridgeStateIfOwned, writeDesktopBridgeState, } from './state.js';
|
|
9
|
-
import { DesktopBridgeError, DesktopBridgeRequestBodyTooLargeError } from './types.js';
|
|
9
|
+
import { DesktopBridgeError, DesktopBridgeRequestBodyTooLargeError, isDesktopBridgeRoutingConflict } from './types.js';
|
|
10
|
+
import { ProviderHandoffRefusedError, providerHandoffRefusalBody } from './provider-handoff.js';
|
|
10
11
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
11
12
|
import { DESKTOP_BRIDGE_CLIENT_PATH_PREFIX, DESKTOP_BRIDGE_DIAGNOSTIC_HEALTH_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PATH, DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DESKTOP_BRIDGE_IMAGEGEN_PATH } from './types.js';
|
|
12
13
|
import { handleDesktopBridgeImagegen } from './imagegen-endpoint.js';
|
|
14
|
+
import { answerDesktopBridgeModels, isDesktopBridgeModelsRequest } from './models-endpoint.js';
|
|
13
15
|
import { forwardWebSocket, safeEndUpgradeSocket } from './websocket-forward.js';
|
|
14
16
|
function authenticateDesktopBridgeClient(req, input) {
|
|
15
17
|
const raw = String(req.url || '/');
|
|
@@ -94,7 +96,9 @@ function rejectionStatus(code) {
|
|
|
94
96
|
return 404;
|
|
95
97
|
if (code === 'bridge_request_capacity_exhausted')
|
|
96
98
|
return 503;
|
|
97
|
-
if (code
|
|
99
|
+
if (isDesktopBridgeRoutingConflict(code))
|
|
100
|
+
return 409;
|
|
101
|
+
if (code.startsWith('bridge_upstream_'))
|
|
98
102
|
return 502;
|
|
99
103
|
if (code.includes('origin') || code.includes('peer') || code.includes('loopback') || code.includes('capability'))
|
|
100
104
|
return 403;
|
|
@@ -134,6 +138,11 @@ function writeBridgeRejection(res, error, req) {
|
|
|
134
138
|
res.destroy(error instanceof Error ? error : undefined);
|
|
135
139
|
return;
|
|
136
140
|
}
|
|
141
|
+
if (error instanceof ProviderHandoffRefusedError) {
|
|
142
|
+
res.writeHead(400, { 'content-type': 'application/json', 'cache-control': 'no-store', connection: 'close' });
|
|
143
|
+
res.end(providerHandoffRefusalBody(error));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
137
146
|
const code = rejectedCode;
|
|
138
147
|
res.writeHead(rejectionStatus(code), {
|
|
139
148
|
'content-type': 'application/json',
|
|
@@ -262,6 +271,10 @@ export async function startPreparedDesktopBridge(input, options = {}) {
|
|
|
262
271
|
return;
|
|
263
272
|
}
|
|
264
273
|
assertAllowedPath(authenticated.pathname, input.allowedPathPrefixes);
|
|
274
|
+
if (input.modelsCatalog && isDesktopBridgeModelsRequest(req, authenticated.pathname)) {
|
|
275
|
+
answerDesktopBridgeModels(req, res, input.modelsCatalog);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
265
278
|
const prepared = await prepareDesktopBridgeRequest(req, input);
|
|
266
279
|
await forwardHttp(req, res, input, prepared, `${desktopBridgeListenOrigin(input)}${authenticated.clientBasePath}`);
|
|
267
280
|
}
|
|
@@ -22,6 +22,9 @@ export class DesktopBridgeError extends Error {
|
|
|
22
22
|
this.code = code;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
export function isDesktopBridgeRoutingConflict(code) {
|
|
26
|
+
return code.startsWith('catalog_') || code.startsWith('session_') || code.includes('route_');
|
|
27
|
+
}
|
|
25
28
|
export const DEFAULT_DESKTOP_BRIDGE_MAX_REQUEST_BODY_BYTES = 128 * 1024 * 1024;
|
|
26
29
|
export class DesktopBridgeRequestBodyTooLargeError extends DesktopBridgeError {
|
|
27
30
|
maximumBytes;
|
|
@@ -9,6 +9,7 @@ import { OPENROUTER_ONLY_REFUSAL_CODES, openRouterOnlyEnabled } from './exclusiv
|
|
|
9
9
|
import { desktopBridgeOfficialPassthroughEnabled, ensureDesktopBridgeRemoteTarget, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader, canonicalSessionId } from './security.js';
|
|
10
10
|
import { desktopBridgeListenOrigin } from './state.js';
|
|
11
11
|
import { forwardResponsesWebSocket, isResponsesWebSocketRequest } from './responses-websocket.js';
|
|
12
|
+
import { admitCodexLbTurnState } from './codex-lb-turn-state.js';
|
|
12
13
|
import { DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DesktopBridgeError, } from './types.js';
|
|
13
14
|
const MAX_HEAD = 64 * 1024;
|
|
14
15
|
const REDACTED_RESPONSE_HEADERS = new Set(['authorization', 'cookie', 'proxy-authenticate', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-codex-lb-api-key']);
|
|
@@ -203,6 +204,8 @@ export async function forwardWebSocket(req, client, head, config, authenticatedL
|
|
|
203
204
|
const headers = official
|
|
204
205
|
? buildOfficialPassthroughWebSocketHeaders(req.headers, target.host)
|
|
205
206
|
: buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential: credential }, target.host);
|
|
207
|
+
if (provider?.provider_id === 'codex-lb')
|
|
208
|
+
admitCodexLbTurnState(headers, route.session_pin?.thread_id ?? null, credential.fingerprint);
|
|
206
209
|
upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
|
|
207
210
|
if (head.length)
|
|
208
211
|
upstream.write(head);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, DESKTOP_BRIDGE_MANAGED_MARKER
|
|
2
|
+
import { DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, DESKTOP_BRIDGE_MANAGED_MARKER } from '../../../cli/install-helpers-codex-lb-config.js';
|
|
3
3
|
import { escapeRegExp } from '../../text/regex.js';
|
|
4
4
|
import { CANONICAL_OPENROUTER_ENDPOINT, HISTORICAL_DESKTOP_BRIDGE_MARKER, HISTORICAL_DESKTOP_COMPAT_MARKER, HISTORICAL_MODEL_CATALOG_MARKER, HISTORICAL_OAUTH_SELECTION_MARKER, HISTORICAL_OPENAI_ROUTING_MARKER, HISTORICAL_PROVIDER_MODE_MARKER, HISTORICAL_PROVIDER_SELECTION_MARKER, hasTomlTable, hasTopLevelMarker, isHistoricalProviderMode, secretFreeProviderEndpoint, tomlTableBoolean, tomlTableContainsCustomHeader, tomlTableString, topLevelTomlString } from './historical-toml.js';
|
|
5
5
|
export function inspectHistoricalDesktopBridgeIntent(configText) {
|
|
@@ -122,11 +122,7 @@ export function inspectHistoricalDesktopBridgeIntent(configText) {
|
|
|
122
122
|
}
|
|
123
123
|
export function prepareHistoricalConfigForDesktopBridgeWriter(text, state) {
|
|
124
124
|
const source = String(text || '');
|
|
125
|
-
if ([
|
|
126
|
-
DESKTOP_BRIDGE_MANAGED_MARKER,
|
|
127
|
-
DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER,
|
|
128
|
-
DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER
|
|
129
|
-
].every((marker) => hasTopLevelMarker(source, marker)))
|
|
125
|
+
if ([DESKTOP_BRIDGE_MANAGED_MARKER, DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER].every((marker) => hasTopLevelMarker(source, marker)))
|
|
130
126
|
return source;
|
|
131
127
|
const historicalMarkers = historicalManagedMarkers();
|
|
132
128
|
const hasHistoricalMarker = historicalMarkers.some((marker) => hasTopLevelMarker(source, marker));
|
|
@@ -55,7 +55,8 @@ async function migrateDesktopBridgeConfigUnlocked(input) {
|
|
|
55
55
|
const migrationInputConfig = prepareHistoricalConfigForDesktopBridgeWriter(currentConfig, historicalState);
|
|
56
56
|
nextConfig = upsertDesktopBridgeManagedConfig(migrationInputConfig, {
|
|
57
57
|
bridgeBaseUrl: input.bridgeBaseUrl,
|
|
58
|
-
combinedCatalogPath
|
|
58
|
+
combinedCatalogPath,
|
|
59
|
+
bindModelCatalog: input.bindModelCatalog !== false
|
|
59
60
|
});
|
|
60
61
|
validateMetadataUpdates(input.metadataUpdates || [], { home, configPath, authPath });
|
|
61
62
|
}
|
|
@@ -74,6 +75,17 @@ async function migrateDesktopBridgeConfigUnlocked(input) {
|
|
|
74
75
|
error: message
|
|
75
76
|
};
|
|
76
77
|
}
|
|
78
|
+
if (input.configWrite === 'forbid' && nextConfig !== currentConfig) {
|
|
79
|
+
return {
|
|
80
|
+
...baseResult,
|
|
81
|
+
ok: false,
|
|
82
|
+
status: 'blocked',
|
|
83
|
+
managed_runtime: null,
|
|
84
|
+
receipt_path: null,
|
|
85
|
+
auth_semantic_identity_preserved: true,
|
|
86
|
+
blockers: ['desktop_bridge_config_write_deferred']
|
|
87
|
+
};
|
|
88
|
+
}
|
|
77
89
|
const metadataUpdates = normalizedMetadataUpdates(input.metadataUpdates || []);
|
|
78
90
|
const metadataAlreadyCurrent = await Promise.all(metadataUpdates.map(async (update) => {
|
|
79
91
|
const currentSha = await fileSha256OrMissing(update.path);
|
|
@@ -82,7 +94,7 @@ async function migrateDesktopBridgeConfigUnlocked(input) {
|
|
|
82
94
|
const hasUnifiedMarkers = [
|
|
83
95
|
DESKTOP_BRIDGE_MANAGED_MARKER,
|
|
84
96
|
DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER,
|
|
85
|
-
DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER
|
|
97
|
+
...(input.bindModelCatalog === false ? [] : [DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER])
|
|
86
98
|
].every((marker) => hasTopLevelMarker(currentConfig, marker));
|
|
87
99
|
if (nextConfig === currentConfig && hasUnifiedMarkers && metadataAlreadyCurrent.every(Boolean)) {
|
|
88
100
|
const authAfter = await captureCodexAuthSnapshot({ home, authPath });
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readText, writeJsonAtomic } from '../../fsx.js';
|
|
2
|
+
import { codexReadsBridgeModels, hasUserOwnedModelCatalog, repairManagedCodexLbClientSelection } from '../../../cli/install-helpers-codex-lb-config.js';
|
|
3
|
+
import { safeWriteCodexConfigToml } from '../../codex-runtime/codex-desktop-config-policy.js';
|
|
2
4
|
import { readTopLevelTomlString } from '../../codex-app/codex-model-catalog.js';
|
|
3
5
|
import { codexRestartBlockers, maybeRestartRunningCodexApp } from '../../codex-app/codex-app-restart-policy.js';
|
|
4
6
|
import { BRIDGE_MODEL_SELECTION_SCHEMA, MAX_SELECTED_OPENROUTER_MODELS, bridgeAvailableModelsPath, pruneSelection, readBridgeModelSelectionState, writeBridgeModelSelection } from '../combined-catalog/model-selection.js';
|
|
@@ -16,6 +18,7 @@ import { sha256Stable } from '../route-index.js';
|
|
|
16
18
|
import { bridgeBaseUrl, commandResult, controllerEnv, controllerPaths, nowIso, providerCode, providerRegistrySnapshot, resolveRawCredentials, resolveValidatedCredentials, safeCode, serializedSettings, stringArray, timeoutMs, unique } from './shared.js';
|
|
17
19
|
import { desktopBridgeStatusV3 } from './status.js';
|
|
18
20
|
import { openRouterOnlyExposure, readControllerOpenRouterOnlyState, withoutCodexLbCatalog } from './openrouter-only-catalog.js';
|
|
21
|
+
import { MODEL_REFRESH_RECOVERY_COMMAND, alignCodexModelSource, bridgeServesModels, listedModelSlugs, modelListDiff, modelRefreshLine, readModelRefreshState, refreshedRecently, writeModelRefreshState } from './model-refresh.js';
|
|
19
22
|
export async function syncCatalog(options) {
|
|
20
23
|
const result = await syncCatalogInternal(options);
|
|
21
24
|
const status = await desktopBridgeStatusV3(options);
|
|
@@ -24,9 +27,21 @@ export async function syncCatalog(options) {
|
|
|
24
27
|
: {};
|
|
25
28
|
return commandResult('catalog.sync', result.ok === true, status, { catalog_sync: result }, result.ok === true ? [] : stringArray(activation.blockers), options);
|
|
26
29
|
}
|
|
30
|
+
async function repairCodexLbClientSelection(configPath) {
|
|
31
|
+
const current = await readText(configPath, '');
|
|
32
|
+
const next = repairManagedCodexLbClientSelection(current);
|
|
33
|
+
if (next === current)
|
|
34
|
+
return null;
|
|
35
|
+
const write = await safeWriteCodexConfigToml(configPath, current, next, 'codex-lb-client-provider-repair', { verifyUnchangedBeforeWrite: true });
|
|
36
|
+
return write.ok ? null : `codex_lb_client_provider_repair_refused:${write.status}`;
|
|
37
|
+
}
|
|
27
38
|
export async function syncCatalogInternal(options, behavior = {}) {
|
|
28
39
|
const paths = controllerPaths(options);
|
|
29
|
-
const
|
|
40
|
+
const repair = behavior.configWrites === false ? null : await repairCodexLbClientSelection(paths.configPath);
|
|
41
|
+
if (repair)
|
|
42
|
+
return blockedSync(inspectHistoricalDesktopBridgeIntent(''), [repair]);
|
|
43
|
+
const configText = await readText(paths.configPath, '');
|
|
44
|
+
const historicalIntent = inspectHistoricalDesktopBridgeIntent(configText);
|
|
30
45
|
if (historicalIntent.blockers.length > 0)
|
|
31
46
|
return blockedSync(historicalIntent, historicalIntent.blockers);
|
|
32
47
|
const rawCredentials = await resolveRawCredentials(options, paths);
|
|
@@ -55,6 +70,10 @@ export async function syncCatalogInternal(options, behavior = {}) {
|
|
|
55
70
|
? openRouterOnlyExposure(selection, openRouterOnly, await readText(paths.configPath, ''))
|
|
56
71
|
: selection;
|
|
57
72
|
const build = buildCombinedBridgeCatalog(catalogRegistry(registry), { catalogs, created_at: nowIso(options), selection: exposure });
|
|
73
|
+
const lost = providersLostToFailedFetch(await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath), catalogs, catalogRegistry(registry));
|
|
74
|
+
if (lost.length > 0) {
|
|
75
|
+
return { ...blockedSync(historicalIntent, lost.map((providerId) => `${providerCode(providerId)}_catalog_refresh_failed`)), build, active_generation_preserved: true };
|
|
76
|
+
}
|
|
58
77
|
if (!selectionState.stored)
|
|
59
78
|
await writeBridgeModelSelection(paths.home, selection).catch(() => undefined);
|
|
60
79
|
await writeJsonAtomic(bridgeAvailableModelsPath(paths.home), {
|
|
@@ -126,6 +145,8 @@ export async function syncCatalogInternal(options, behavior = {}) {
|
|
|
126
145
|
receiptDir: paths.receiptDir,
|
|
127
146
|
bridgeBaseUrl: managedBridgeBaseUrl,
|
|
128
147
|
combinedCatalogPath: staging.catalog_path,
|
|
148
|
+
bindModelCatalog: !codexReadsBridgeModels(configText) && !hasUserOwnedModelCatalog(configText),
|
|
149
|
+
configWrite: behavior.configWrites === false ? 'forbid' : 'allow',
|
|
129
150
|
newCatalogGeneration: build.catalog.generation,
|
|
130
151
|
metadataUpdates: [
|
|
131
152
|
{
|
|
@@ -303,6 +324,20 @@ function emptyProviderCatalog(providerId, state, checkedAt, blockers) {
|
|
|
303
324
|
warnings: []
|
|
304
325
|
};
|
|
305
326
|
}
|
|
327
|
+
function providersLostToFailedFetch(active, catalogs, registry) {
|
|
328
|
+
if (!active.ok)
|
|
329
|
+
return [];
|
|
330
|
+
return ['codex-lb', 'openrouter'].filter((providerId) => {
|
|
331
|
+
const blockers = catalogs[providerId].blockers || [];
|
|
332
|
+
return registry.profiles[providerId].enabled
|
|
333
|
+
&& catalogs[providerId].state === 'failed'
|
|
334
|
+
&& !blockers.some(refusesCredential)
|
|
335
|
+
&& active.catalog.models.some((model) => model.provider_id === providerId);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function refusesCredential(blocker) {
|
|
339
|
+
return /unauthorized|credential_missing|key_missing|invalid_key|_http_40[13]\b/.test(blocker);
|
|
340
|
+
}
|
|
306
341
|
function validationFailureState(blockers) {
|
|
307
342
|
return blockers.some((blocker) => /(?:401|403|auth|rejected|invalid_key|unauthorized)/i.test(blocker))
|
|
308
343
|
? 'rejected' : 'unavailable';
|
|
@@ -349,12 +384,14 @@ export async function selectExposedModels(publicIds, options) {
|
|
|
349
384
|
updated_at: nowIso(options),
|
|
350
385
|
openrouter: { mode: 'selected', public_ids: requested }
|
|
351
386
|
});
|
|
352
|
-
const sync = await
|
|
387
|
+
const sync = await syncAsRefresh(options, 'models.select');
|
|
353
388
|
const status = await desktopBridgeStatusV3(options);
|
|
354
389
|
const activation = sync.activation && typeof sync.activation === 'object'
|
|
355
390
|
? sync.activation
|
|
356
391
|
: {};
|
|
357
|
-
const
|
|
392
|
+
const refresh = sync.model_refresh;
|
|
393
|
+
const changed = before.selection.openrouter.public_ids.join('\0') !== requested.join('\0')
|
|
394
|
+
&& (refresh.codex_model_source !== 'bridge_models' || refresh.restart_codex_required);
|
|
358
395
|
const restart = sync.ok === true
|
|
359
396
|
? await maybeRestartRunningCodexApp({
|
|
360
397
|
env: controllerEnv(options),
|
|
@@ -374,3 +411,88 @@ function seedSelectionFromConfiguredModel(selection, configText) {
|
|
|
374
411
|
return selection;
|
|
375
412
|
return { ...selection, openrouter: { mode: 'selected', public_ids: [configured] } };
|
|
376
413
|
}
|
|
414
|
+
export async function refreshModelCatalog(options, input) {
|
|
415
|
+
const paths = controllerPaths(options);
|
|
416
|
+
const refreshedAt = nowIso(options);
|
|
417
|
+
const base = {
|
|
418
|
+
schema: 'sks.bridge-model-refresh.v1',
|
|
419
|
+
reason: input.reason,
|
|
420
|
+
refreshed_at: refreshedAt,
|
|
421
|
+
added: [],
|
|
422
|
+
removed: [],
|
|
423
|
+
restart_codex_required: false
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
const registryGeneration = (await resolveBridgeProviderRegistry({
|
|
427
|
+
home: paths.home,
|
|
428
|
+
credentials: await resolveRawCredentials(options, paths)
|
|
429
|
+
})).generation;
|
|
430
|
+
const recent = input.force ? null : await readModelRefreshState(paths.home);
|
|
431
|
+
if (recent && refreshedRecently(recent, registryGeneration, Date.parse(refreshedAt))) {
|
|
432
|
+
return {
|
|
433
|
+
...base, ok: true, skipped: true, generation: recent.generation, total: recent.total, codex_model_source: null,
|
|
434
|
+
line: `Model list refreshed recently: ${recent.total} models generation ${recent.generation.slice(0, 8)}`,
|
|
435
|
+
warnings: ['model_refresh_skipped_recent'], blockers: [], sync: null
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const before = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
|
|
439
|
+
const sync = await syncCatalogInternal(options, { ...(input.behavior || {}), configWrites: input.allowConfigWrites });
|
|
440
|
+
if (sync.ok !== true)
|
|
441
|
+
return refreshFailed(base, stringArray(sync.activation?.blockers), sync);
|
|
442
|
+
const build = sync.build;
|
|
443
|
+
const activation = sync.activation;
|
|
444
|
+
const diff = modelListDiff(listedModelSlugs(before.ok ? before.catalog : null), listedModelSlugs(build.catalog));
|
|
445
|
+
const source = await alignCodexModelSource({
|
|
446
|
+
home: paths.home,
|
|
447
|
+
configPath: paths.configPath,
|
|
448
|
+
authPath: paths.authPath,
|
|
449
|
+
catalog: build.catalog,
|
|
450
|
+
catalogPath: typeof activation.catalog_path === 'string' ? activation.catalog_path : null,
|
|
451
|
+
allowConfigWrites: input.allowConfigWrites,
|
|
452
|
+
selfCheck: options.modelsSelfCheckImpl || bridgeServesModels
|
|
453
|
+
});
|
|
454
|
+
const restart = source.changed || (source.source === 'model_catalog_json' && diff.added.length + diff.removed.length > 0);
|
|
455
|
+
const total = listedModelSlugs(build.catalog).length;
|
|
456
|
+
await writeModelRefreshState(paths.home, {
|
|
457
|
+
refreshed_at: refreshedAt,
|
|
458
|
+
provider_registry_generation: registryGeneration,
|
|
459
|
+
generation: build.catalog.generation,
|
|
460
|
+
total
|
|
461
|
+
}).catch(() => undefined);
|
|
462
|
+
return {
|
|
463
|
+
...base, ok: true, skipped: false, generation: build.catalog.generation, total, added: diff.added, removed: diff.removed,
|
|
464
|
+
codex_model_source: source.source, restart_codex_required: restart,
|
|
465
|
+
line: modelRefreshLine({ total, added: diff.added.length, removed: diff.removed.length, generation: build.catalog.generation, restartCodex: restart }),
|
|
466
|
+
warnings: source.warnings, blockers: [], sync
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return refreshFailed(base, [safeCode(error, 'model_refresh_failed')], null);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function refreshFailed(base, blockers, sync) {
|
|
474
|
+
return {
|
|
475
|
+
...base, ok: false, skipped: false, generation: null, total: 0, codex_model_source: null,
|
|
476
|
+
line: `Model list refresh failed; the previous list stays active. Run \`${MODEL_REFRESH_RECOVERY_COMMAND}\` to retry.`,
|
|
477
|
+
warnings: [`model_refresh_failed: run \`${MODEL_REFRESH_RECOVERY_COMMAND}\``],
|
|
478
|
+
blockers: unique(blockers.length > 0 ? blockers : ['model_refresh_failed']),
|
|
479
|
+
sync
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
export async function refreshModels(request, options) {
|
|
483
|
+
const refresh = await refreshModelCatalog(options, {
|
|
484
|
+
reason: request.reason || 'cli',
|
|
485
|
+
force: request.force !== false,
|
|
486
|
+
allowConfigWrites: request.allow_config_writes !== false
|
|
487
|
+
});
|
|
488
|
+
const { sync, ...model_refresh } = refresh;
|
|
489
|
+
const status = await desktopBridgeStatusV3(options);
|
|
490
|
+
return commandResult('models.refresh', refresh.ok, status, { model_refresh, catalog_sync: sync }, refresh.blockers, options);
|
|
491
|
+
}
|
|
492
|
+
export async function syncAsRefresh(options, reason, behavior = {}) {
|
|
493
|
+
const { sync, ...model_refresh } = await refreshModelCatalog(options, { reason, force: true, allowConfigWrites: true, behavior });
|
|
494
|
+
return {
|
|
495
|
+
...(sync ?? blockedSync(null, model_refresh.blockers)),
|
|
496
|
+
model_refresh
|
|
497
|
+
};
|
|
498
|
+
}
|