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.
Files changed (66) hide show
  1. package/README.md +27 -5
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/command-manifest-lite.js +1 -1
  5. package/dist/cli/install-helpers-codex-lb-config.js +55 -20
  6. package/dist/commands/bridge.js +14 -1
  7. package/dist/commands/codex.js +5 -1
  8. package/dist/commands/doctor-console.js +2 -0
  9. package/dist/commands/doctor.js +8 -9
  10. package/dist/config/skills-manifest.json +1 -1
  11. package/dist/core/agents/agent-role-config.js +6 -2
  12. package/dist/core/agents/sks-model-pin-hygiene.js +150 -0
  13. package/dist/core/codex/codex-config-guard.js +18 -13
  14. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +7 -1
  15. package/dist/core/codex-app/menubar/constants.js +0 -1
  16. package/dist/core/codex-lb/bridge-contracts.js +5 -0
  17. package/dist/core/codex-lb/combined-catalog/builder.js +4 -2
  18. package/dist/core/codex-lb/combined-catalog/latest-generation.js +7 -34
  19. package/dist/core/codex-lb/combined-catalog/normalize.js +3 -1
  20. package/dist/core/codex-lb/combined-catalog/persistence.js +4 -0
  21. package/dist/core/codex-lb/combined-catalog/storage.js +4 -1
  22. package/dist/core/codex-lb/desktop-bridge/codex-lb-turn-state.js +35 -0
  23. package/dist/core/codex-lb/desktop-bridge/compaction-adapter.js +3 -0
  24. package/dist/core/codex-lb/desktop-bridge/header-policy.js +10 -4
  25. package/dist/core/codex-lb/desktop-bridge/http-forward.js +138 -36
  26. package/dist/core/codex-lb/desktop-bridge/models-endpoint.js +62 -0
  27. package/dist/core/codex-lb/desktop-bridge/provider-handoff.js +88 -0
  28. package/dist/core/codex-lb/desktop-bridge/provider-request-compat.js +89 -0
  29. package/dist/core/codex-lb/desktop-bridge/rejection-log.js +61 -0
  30. package/dist/core/codex-lb/desktop-bridge/responses-websocket.js +108 -16
  31. package/dist/core/codex-lb/desktop-bridge/security.js +101 -55
  32. package/dist/core/codex-lb/desktop-bridge/server.js +15 -2
  33. package/dist/core/codex-lb/desktop-bridge/types.js +3 -0
  34. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +3 -0
  35. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +2 -6
  36. package/dist/core/codex-lb/desktop-bridge-migration.js +14 -2
  37. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +125 -3
  38. package/dist/core/codex-lb/desktop-controller-v3/model-refresh.js +140 -0
  39. package/dist/core/codex-lb/desktop-controller-v3/openrouter-only-commands.js +5 -5
  40. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +14 -11
  41. package/dist/core/codex-lb/desktop-controller-v3/status.js +2 -3
  42. package/dist/core/codex-lb/desktop-controller-v3.js +3 -1
  43. package/dist/core/codex-lb/desktop-service.js +51 -17
  44. package/dist/core/codex-lb/model-refresh-after.js +16 -0
  45. package/dist/core/codex-lb/request-route-resolver.js +1 -1
  46. package/dist/core/codex-runtime/codex-desktop-config-policy.js +0 -1
  47. package/dist/core/commands/basic-cli.js +5 -0
  48. package/dist/core/commands/menubar-command.js +5 -1
  49. package/dist/core/doctor/desktop-bridge-catalog-repair.js +20 -17
  50. package/dist/core/hooks-runtime/jev-spawn-routing.js +1 -5
  51. package/dist/core/hooks-runtime/parent-orchestration-gate.js +6 -8
  52. package/dist/core/hooks-runtime.js +13 -9
  53. package/dist/core/init/skills.js +22 -3
  54. package/dist/core/provider/provider-context.js +4 -1
  55. package/dist/core/search/context.js +2 -2
  56. package/dist/core/subagents/model-tiers.js +19 -3
  57. package/dist/core/update/update-migration-state/desktop-bridge-catalog-repair-stage.js +4 -3
  58. package/dist/core/update/update-migration-state.js +10 -40
  59. package/dist/core/version.js +1 -1
  60. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +0 -1
  61. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +32 -0
  62. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +15 -5
  63. package/dist/native/sks-menubar/Sources/SidebarItem.swift +0 -2
  64. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +3 -0
  65. package/package.json +2 -2
  66. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +0 -49
@@ -1,6 +1,6 @@
1
1
  import { buildBridgeRouteIndex, sha256Stable } from '../route-index.js';
2
2
  import { COMBINED_BRIDGE_CATALOG_SCHEMA } from './contracts.js';
3
- import { normalizeProviderCatalog, providerCatalogStatus, routeProviderState } from './normalize.js';
3
+ import { OPENROUTER_PRIORITY_BASE, normalizeProviderCatalog, providerCatalogStatus, routeProviderState } from './normalize.js';
4
4
  import { pinCatalogToLatestGenerations } from './latest-generation.js';
5
5
  import { compareModels, unique } from './shared.js';
6
6
  import { applyBridgeModelSelection, availableModelRows, emptyBridgeModelSelection } from './model-selection.js';
@@ -11,7 +11,9 @@ export function buildCombinedBridgeCatalog(registry, options) {
11
11
  openrouter: normalizeProviderCatalog(options.catalogs.openrouter)
12
12
  };
13
13
  const selection = options.selection || emptyBridgeModelSelection(createdAt);
14
- const allModels = pinCatalogToLatestGenerations([...normalized['codex-lb'].models, ...normalized.openrouter.models].sort(compareModels));
14
+ let openRouterIndex = 0;
15
+ const allModels = pinCatalogToLatestGenerations([...normalized['codex-lb'].models, ...normalized.openrouter.models].sort(compareModels)).map((model) => model.provider_id === 'openrouter' ? { ...model, priority: OPENROUTER_PRIORITY_BASE + openRouterIndex++ } : model)
16
+ .sort((left, right) => left.priority - right.priority || left.slug.localeCompare(right.slug) || compareModels(left, right));
15
17
  const availableOpenRouterModels = availableModelRows(allModels, selection);
16
18
  const models = options.selection ? applyBridgeModelSelection(allModels, selection) : allModels;
17
19
  const routeBuild = buildBridgeRouteIndex({
@@ -3,13 +3,6 @@ function isListed(model) {
3
3
  const visibility = String(model.visibility || 'list').toLowerCase();
4
4
  return visibility !== 'hide' && visibility !== 'hidden';
5
5
  }
6
- function escapeRegExp(text) {
7
- return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8
- }
9
- function displayNamePattern(displayName) {
10
- const body = escapeRegExp(displayName.trim()).replace(/\\?-|\s+/g, '[-\\s]');
11
- return new RegExp(`(?<![\\w-])${body}(?![\\w-])`, 'gi');
12
- }
13
6
  export function pinCatalogToLatestGenerations(models) {
14
7
  const newest = new Map();
15
8
  for (const model of models) {
@@ -20,34 +13,14 @@ export function pinCatalogToLatestGenerations(models) {
20
13
  if (!best || compareModelVersions(id.version, parseGptModelId(best.public_id).version) > 0)
21
14
  newest.set(id.family, model);
22
15
  }
23
- const byId = new Map(models.map((model) => [model.public_id, model]));
24
16
  return models.map((model) => {
25
17
  const id = parseGptModelId(model.public_id);
26
- let next = model;
27
- if (id && isListed(model)) {
28
- const best = newest.get(id.family);
29
- if (best && compareModelVersions(parseGptModelId(best.public_id).version, id.version) > 0)
30
- next = { ...next, visibility: 'hide' };
31
- }
32
- const upgrade = model.upgrade;
33
- const target = upgrade && typeof upgrade === 'object' ? String(upgrade.model || '') : '';
34
- const targetId = parseGptModelId(target);
35
- const replacement = targetId ? newest.get(targetId.family) : undefined;
36
- if (targetId && replacement && replacement.public_id !== target
37
- && compareModelVersions(parseGptModelId(replacement.public_id).version, targetId.version) > 0) {
38
- const old = byId.get(target);
39
- const markdown = upgrade.migration_markdown;
40
- next = {
41
- ...next,
42
- upgrade: {
43
- ...upgrade,
44
- model: replacement.public_id,
45
- ...(typeof markdown === 'string' && old?.display_name
46
- ? { migration_markdown: markdown.replace(displayNamePattern(old.display_name), () => replacement.display_name) }
47
- : {})
48
- }
49
- };
50
- }
51
- return next;
18
+ if (!id || !isListed(model))
19
+ return model;
20
+ const best = newest.get(id.family);
21
+ if (!best || compareModelVersions(parseGptModelId(best.public_id).version, id.version) <= 0)
22
+ return model;
23
+ const { upgrade: _retired, ...rest } = model;
24
+ return { ...rest, visibility: 'hide' };
52
25
  });
53
26
  }
@@ -130,7 +130,7 @@ function normalizeCodexLbBridgeCatalogModels(payload, sourceCatalogGeneration) {
130
130
  ...codexModelInfoFields(publicId, row, {
131
131
  tools: row.supports_tools === true || row.supports_tool_choice === true
132
132
  }),
133
- ...(typeof row.priority === 'number' ? {} : { priority: 100 })
133
+ ...(typeof row.priority === 'number' ? {} : { priority: CODEX_LB_DEFAULT_PRIORITY })
134
134
  });
135
135
  }
136
136
  catch (error) {
@@ -164,6 +164,8 @@ export const OPENROUTER_REASONING_LEVELS = [
164
164
  { effort: 'xhigh', description: 'Extra high reasoning depth for complex problems' }
165
165
  ];
166
166
  export const OPENROUTER_DEFAULT_REASONING_LEVEL = 'medium';
167
+ export const CODEX_LB_DEFAULT_PRIORITY = 500;
168
+ export const OPENROUTER_PRIORITY_BASE = 1000;
167
169
  function openRouterReasoningLevels() {
168
170
  return OPENROUTER_REASONING_LEVELS.map((level) => ({ ...level }));
169
171
  }
@@ -43,6 +43,10 @@ export async function stageCombinedBridgeCatalog(input) {
43
43
  || existing.route_index.generation !== input.build.route_index.generation) {
44
44
  throw new Error('combined_catalog_generation_bundle_mismatch');
45
45
  }
46
+ if (JSON.stringify(existing.catalog.provider_statuses) !== JSON.stringify(input.build.catalog.provider_statuses)) {
47
+ await fs.rename(tempCatalog, layout.catalogPath);
48
+ await fsyncDirectory(layout.bundleDirectory);
49
+ }
46
50
  }
47
51
  else {
48
52
  await fs.rename(tempBundle, layout.bundleDirectory);
@@ -139,7 +139,10 @@ export function emptyRouteIndex() {
139
139
  }).route_index;
140
140
  }
141
141
  export function catalogObservationGeneration(catalog) {
142
- return sha256Stable(catalog.provider_statuses);
142
+ return sha256Stable(Object.fromEntries(Object.entries(catalog.provider_statuses).map(([providerId, row]) => {
143
+ const { checked_at: _checkedAt, expires_at: _expiresAt, ...observed } = row;
144
+ return [providerId, observed];
145
+ })));
143
146
  }
144
147
  function validProviderStatuses(value) {
145
148
  if (!value || typeof value !== 'object' || Array.isArray(value))
@@ -0,0 +1,35 @@
1
+ const MAX_THREADS = 4_096;
2
+ const MAX_VALUES_PER_THREAD = 8;
3
+ const issued = new Map();
4
+ function scope(threadId, keyFingerprint) {
5
+ return `${keyFingerprint}\u0000${threadId}`;
6
+ }
7
+ function headerValues(value) {
8
+ const values = Array.isArray(value) ? value : value === undefined ? [] : [String(value)];
9
+ return values.map((entry) => entry.trim()).filter(Boolean);
10
+ }
11
+ export function rememberCodexLbTurnState(threadId, keyFingerprint, headers) {
12
+ const values = headerValues(headers['x-codex-turn-state']);
13
+ if (!threadId || !values.length)
14
+ return;
15
+ const key = scope(threadId, keyFingerprint);
16
+ const kept = (issued.get(key) ?? []).filter((value) => !values.includes(value));
17
+ issued.delete(key);
18
+ issued.set(key, [...kept, ...values].slice(-MAX_VALUES_PER_THREAD));
19
+ while (issued.size > MAX_THREADS)
20
+ issued.delete(issued.keys().next().value);
21
+ }
22
+ export function admitCodexLbTurnState(headers, threadId, keyFingerprint) {
23
+ const values = headerValues(headers['x-codex-turn-state']);
24
+ if (!values.length) {
25
+ delete headers['x-codex-turn-state'];
26
+ return;
27
+ }
28
+ const known = threadId ? issued.get(scope(threadId, keyFingerprint)) : undefined;
29
+ if (values.length === 1 && known?.includes(values[0]))
30
+ return;
31
+ delete headers['x-codex-turn-state'];
32
+ }
33
+ export function forgetCodexLbTurnStates() {
34
+ issued.clear();
35
+ }
@@ -43,6 +43,9 @@ export function decodeBridgeCompaction(encrypted) {
43
43
  return null;
44
44
  }
45
45
  }
46
+ export function isNativeCompactionItem(item) {
47
+ return isObject(item) && COMPACTION_ITEM_TYPES.has(String(item.type)) && decodeBridgeCompaction(item.encrypted_content) === null;
48
+ }
46
49
  function summaryMessage(summary) {
47
50
  return { type: 'message', role: 'user', content: [{ type: 'input_text', text: `${CODEX_SUMMARY_PREFIX}\n${summary}` }] };
48
51
  }
@@ -7,9 +7,10 @@ const REQUEST_HEADER_ALLOWLIST = new Set([
7
7
  ]);
8
8
  const CODEX_LOCAL_HEADERS = new Set([
9
9
  'thread-id', 'thread_id', 'session-id', 'session_id', 'x-client-request-id',
10
- 'x-codex-installation-id', 'x-codex-turn-metadata', 'x-codex-window-id',
10
+ 'x-codex-installation-id', 'x-codex-turn-metadata', 'x-codex-window-id', 'x-codex-turn-state',
11
11
  'originator', 'openai-beta',
12
12
  ]);
13
+ const CLIENT_IDENTITY_HEADERS = new Set(['authorization', 'cookie', 'chatgpt-account-id']);
13
14
  const NEVER_FORWARD_FROM_CLIENT = new Set([
14
15
  'authorization', 'cookie', 'forwarded', 'proxy-authorization', 'x-api-key', 'x-codex-lb-api-key',
15
16
  'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto', 'x-real-ip',
@@ -39,14 +40,19 @@ function injectCredential(result, providerId, transport, credential) {
39
40
  result.authorization = `Bearer ${credential.value}`;
40
41
  }
41
42
  export function buildProviderUpstreamHeaders(inbound, context, upstreamHost) {
43
+ if (context.providerId === 'codex-lb') {
44
+ const result = buildOfficialPassthroughHeaders(inbound, upstreamHost);
45
+ for (const name of CLIENT_IDENTITY_HEADERS)
46
+ delete result[name];
47
+ injectCredential(result, context.providerId, context.authTransport, context.credential);
48
+ return result;
49
+ }
42
50
  const result = {};
43
51
  for (const [rawName, rawValue] of Object.entries(inbound)) {
44
52
  if (rawValue === undefined)
45
53
  continue;
46
54
  const name = rawName.toLowerCase();
47
- const providerAllowed = (context.providerId === 'openrouter' && OPENROUTER_CLIENT_HEADERS.has(name))
48
- || (context.providerId === 'codex-lb' && CODEX_LOCAL_HEADERS.has(name));
49
- if (!REQUEST_HEADER_ALLOWLIST.has(name) && !providerAllowed)
55
+ if (!REQUEST_HEADER_ALLOWLIST.has(name) && !(context.providerId === 'openrouter' && OPENROUTER_CLIENT_HEADERS.has(name)))
50
56
  continue;
51
57
  result[name] = rawValue;
52
58
  }
@@ -2,16 +2,21 @@ import http, {} from 'node:http';
2
2
  import https from 'node:https';
3
3
  import zlib from 'node:zlib';
4
4
  import { pipeline } from 'node:stream/promises';
5
- import { BRIDGE_OFFICIAL_ROUTE_ID } from '../bridge-contracts.js';
5
+ import { BRIDGE_OFFICIAL_ROUTE_ID, BRIDGE_REMOTE_COMPACTION } from '../bridge-contracts.js';
6
6
  import { buildOfficialPassthroughHeaders, buildProviderUpstreamHeaders, rewriteResponseHeaders } from './header-policy.js';
7
- import { createDesktopBridgeRejectionLogger } from './rejection-log.js';
8
- import { OPENROUTER_ONLY_REFUSAL_CODES, openRouterOnlyEnabled } from './exclusive-provider-guard.js';
9
- import { ensureDesktopBridgeRemoteTarget, isNoRouteUpstreamError, isUnreachableUpstreamError, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader } from './security.js';
7
+ import { createDesktopBridgeRejectionLogger, desktopBridgeRequestShape, safeUpstreamParam } from './rejection-log.js';
8
+ import { OPENROUTER_ONLY_REFUSAL_CODES } from './exclusive-provider-guard.js';
9
+ import { ensureDesktopBridgeRemoteTarget, isNoRouteUpstreamError, isResponsesFamilyPath, isUnreachableUpstreamError, pinnedPublicModel, refreshDesktopBridgeRemoteTarget, resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader, threadRouteMove } from './security.js';
10
+ import { admitCodexLbTurnState, forgetCodexLbTurnStates, rememberCodexLbTurnState } from './codex-lb-turn-state.js';
11
+ import { ProviderHandoffRefusedError, assertProviderHandoffAllowed, providerHandoffRefusalBody, stripProviderBoundItems, } from './provider-handoff.js';
10
12
  import { desktopBridgeListenOrigin } from './state.js';
13
+ import { adaptRequestForRoute, logRequestAdaptation, providerExtraInclude } from './provider-request-compat.js';
14
+ import { desktopBridgeModelsEtag } from './models-endpoint.js';
11
15
  import { buildCompactionSummaryRequest, compactEndpointResult, compactionCreatedEvent, compactionFailedEvent, compactionResponseEvents, expandBridgeCompactionItems, newCompactionResponseId, parseProviderSummary, requestsRemoteCompaction, safeUpstreamErrorId, sseEvent, summaryFailure, summaryWithinBounds, } from './compaction-adapter.js';
12
- import { isAdvisableEventStream, modelCapacityAdvisingSse } from './capacity-retry-advice.js';
13
- import { DEFAULT_DESKTOP_BRIDGE_MAX_REQUEST_BODY_BYTES, DesktopBridgeError, DesktopBridgeRequestBodyTooLargeError } from './types.js';
16
+ import { MODEL_CAPACITY_ERROR_CODE, MODEL_CAPACITY_RETRY_AFTER_SECONDS, isAdvisableEventStream, modelCapacityAdvisingSse } from './capacity-retry-advice.js';
17
+ import { DEFAULT_DESKTOP_BRIDGE_MAX_REQUEST_BODY_BYTES, DesktopBridgeError, DesktopBridgeRequestBodyTooLargeError, isDesktopBridgeRoutingConflict } from './types.js';
14
18
  const MAX_UPSTREAM_ERROR_BODY_BYTES = 1024 * 1024;
19
+ const NATIVE_COMPACTION_IDLE_FLOOR_MS = 300_000;
15
20
  function decodeBridgeRequestBody(body, encoding, maximum) {
16
21
  const options = { maxOutputLength: maximum };
17
22
  try {
@@ -61,11 +66,16 @@ async function reencodeLikeClient(plain, encoding) {
61
66
  return new Promise((resolve) => compress(plain, (error, result) => resolve(error ? null : result)));
62
67
  }
63
68
  const RESPONSES_PATHS = ['/backend-api/codex/responses', '/api/v1/responses', '/v1/responses'];
64
- function modelBodyKind(req, pathname, config) {
69
+ function modelBodyKind(req, pathname) {
65
70
  if (RESPONSES_PATHS.includes(pathname))
66
71
  return req.method === 'POST' ? 'responses' : null;
67
- return openRouterOnlyEnabled(config) && req.method === 'POST'
68
- && RESPONSES_PATHS.some((prefix) => pathname.startsWith(`${prefix}/`)) ? 'sub-endpoint' : null;
72
+ return req.method === 'POST' && RESPONSES_PATHS.some((prefix) => pathname.startsWith(`${prefix}/`)) ? 'sub-endpoint' : null;
73
+ }
74
+ function advertisedModelsEtag(req, config) {
75
+ if (!config.modelsCatalog)
76
+ return null;
77
+ const pathname = new URL(String(req.url || '/'), 'http://bridge.invalid').pathname;
78
+ return modelBodyKind(req, pathname) === 'responses' ? desktopBridgeModelsEtag(config.modelsCatalog.generation) : null;
69
79
  }
70
80
  function isCompactEndpoint(req, pathname) {
71
81
  return req.method === 'POST' && RESPONSES_PATHS.some((prefix) => pathname === `${prefix}/compact`);
@@ -105,7 +115,7 @@ export async function prepareDesktopBridgeRequest(req, config) {
105
115
  let contentEncodingStripped = false;
106
116
  let encoding = '';
107
117
  let encodedBody = null;
108
- const bodyKind = modelBodyKind(req, pathname, config);
118
+ const bodyKind = modelBodyKind(req, pathname);
109
119
  if (bodyKind) {
110
120
  const maximum = config.maxRequestBodyBytes ?? DEFAULT_DESKTOP_BRIDGE_MAX_REQUEST_BODY_BYTES;
111
121
  body = await readBoundedBody(req, maximum);
@@ -131,12 +141,19 @@ export async function prepareDesktopBridgeRequest(req, config) {
131
141
  body = decoded;
132
142
  }
133
143
  const headerModel = singleBridgeHeader(req.headers, 'x-sks-model');
134
- const model = typeof payload?.model === 'string' ? payload.model : headerModel;
135
144
  const sessionIdentity = resolveCodexSessionIdentity(req.headers, payload);
136
- const route = await resolveAndBindDesktopBridgeRouteContext({
145
+ const model = typeof payload?.model === 'string'
146
+ ? payload.model
147
+ : headerModel || (isResponsesFamilyPath(pathname) ? pinnedPublicModel(sessionIdentity.thread_id, config) : null);
148
+ const routeRequest = {
137
149
  public_model: String(model || ''), session_id: sessionIdentity.thread_id,
138
150
  pathname, transport: 'http', headers: req.headers, identity: sessionIdentity,
139
- }, config);
151
+ };
152
+ let move = null;
153
+ const route = await resolveAndBindDesktopBridgeRouteContext(routeRequest, config, (resolved) => {
154
+ move = threadRouteMove(routeRequest, resolved, config);
155
+ assertProviderHandoffAllowed(payload, move);
156
+ });
140
157
  let rewrite = false;
141
158
  if (payload && (bodyKind === 'responses' || typeof payload.model === 'string') && payload.model !== route.upstream_model) {
142
159
  payload.model = route.upstream_model;
@@ -144,14 +161,20 @@ export async function prepareDesktopBridgeRequest(req, config) {
144
161
  }
145
162
  if (expandBridgeCompactionItems(payload))
146
163
  rewrite = true;
164
+ if (route.session_pin?.handoff_from && stripProviderBoundItems(payload, Boolean(move)))
165
+ rewrite = true;
166
+ const adapted = adaptRequestForRoute(payload, route, providerExtraInclude(config, route));
167
+ if (adapted.length) {
168
+ rewrite = true;
169
+ logRequestAdaptation(config, route, adapted, 'http');
170
+ }
147
171
  if (payload && rewrite)
148
172
  body = Buffer.from(JSON.stringify(payload));
149
173
  const compaction = bodyKind === 'responses' && requestsRemoteCompaction(payload)
150
174
  ? 'trigger'
151
175
  : bodyKind === 'sub-endpoint' && isCompactEndpoint(req, pathname) ? 'endpoint' : null;
152
176
  let plainBody = null;
153
- const answeredByBridge = Boolean(compaction) && route.provider_id !== BRIDGE_OFFICIAL_ROUTE_ID;
154
- if (encodedBody && body && !answeredByBridge && keepsClientEncoding(route)) {
177
+ if (encodedBody && body && !answeredByBridge(compaction, route) && keepsClientEncoding(route)) {
155
178
  const upstreamBody = rewrite ? await reencodeLikeClient(body, encoding) : encodedBody;
156
179
  if (upstreamBody) {
157
180
  plainBody = body;
@@ -160,7 +183,10 @@ export async function prepareDesktopBridgeRequest(req, config) {
160
183
  }
161
184
  }
162
185
  const credential = route.provider_id === BRIDGE_OFFICIAL_ROUTE_ID ? null : await resolveCredential(config, route);
163
- return { body, route, credential, contentEncodingStripped, plainBody, compaction, payload };
186
+ return { body, route, credential, contentEncodingStripped, plainBody, compaction, payload, threadId: sessionIdentity.thread_id, move };
187
+ }
188
+ function answeredByBridge(compaction, route) {
189
+ return Boolean(compaction) && BRIDGE_REMOTE_COMPACTION[route.provider_id] === 'bridge_summary';
164
190
  }
165
191
  function connectTimeout(request, config) {
166
192
  request.once('socket', (socket) => {
@@ -191,8 +217,12 @@ function writeHttpBridgeError(res, error, req) {
191
217
  res.destroy(error instanceof Error ? error : undefined);
192
218
  return;
193
219
  }
194
- res.writeHead(code.startsWith('catalog_') || code.startsWith('session_') || code.includes('route_')
195
- || OPENROUTER_ONLY_REFUSAL_CODES.has(code) ? 409 : 502, {
220
+ if (error instanceof ProviderHandoffRefusedError) {
221
+ res.writeHead(400, { 'content-type': 'application/json', 'cache-control': 'no-store', connection: 'close' });
222
+ res.end(providerHandoffRefusalBody(error));
223
+ return;
224
+ }
225
+ res.writeHead(isDesktopBridgeRoutingConflict(code) || OPENROUTER_ONLY_REFUSAL_CODES.has(code) ? 409 : 502, {
196
226
  'content-type': 'application/json', 'cache-control': 'no-store', connection: 'close',
197
227
  });
198
228
  res.end(JSON.stringify({ error: { type: 'sks_bridge_error', code, message: code } }));
@@ -229,27 +259,57 @@ function redactedUpstreamClientMessage(statusCode, transient) {
229
259
  const UNREADABLE_BODY_HINT = /json|pars(?:e|ing)|decod|encod|compress|utf-?8/i;
230
260
  async function readRedactedUpstreamError(response) {
231
261
  let total = 0;
262
+ let complete = true;
232
263
  const chunks = [];
233
264
  for await (const raw of response) {
234
265
  const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
235
266
  total += chunk.length;
236
267
  if (total > MAX_UPSTREAM_ERROR_BODY_BYTES) {
237
268
  response.destroy();
269
+ complete = false;
238
270
  break;
239
271
  }
240
272
  chunks.push(chunk);
241
273
  }
242
274
  let upstreamCode = null;
243
275
  let upstreamType = null;
244
- const text = Buffer.concat(chunks).toString('utf8');
276
+ let upstreamParam = null;
277
+ const raw = Buffer.concat(chunks);
278
+ const encoding = String(response.headers['content-encoding'] || '').trim().toLowerCase();
279
+ let decoded = complete ? raw : null;
280
+ if (decoded && encoding && encoding !== 'identity') {
281
+ try {
282
+ decoded = decodeBridgeRequestBody(decoded, encoding, 4 * MAX_UPSTREAM_ERROR_BODY_BYTES);
283
+ }
284
+ catch {
285
+ decoded = null;
286
+ }
287
+ }
288
+ const text = (decoded ?? raw).toString('utf8');
245
289
  try {
246
290
  const parsed = JSON.parse(text);
247
291
  upstreamCode = safeUpstreamErrorId(parsed?.error?.code) ?? safeUpstreamErrorId(parsed?.detail);
248
292
  upstreamType = safeUpstreamErrorId(parsed?.error?.type);
293
+ upstreamParam = safeUpstreamParam(parsed?.error?.param);
249
294
  }
250
295
  catch {
251
296
  }
252
- return { upstreamCode, upstreamType, unreadableBody: UNREADABLE_BODY_HINT.test(text.slice(0, 4096)) };
297
+ return {
298
+ upstreamCode,
299
+ upstreamType,
300
+ upstreamParam,
301
+ unreadableBody: UNREADABLE_BODY_HINT.test(text.slice(0, 4096)),
302
+ body: decoded,
303
+ };
304
+ }
305
+ function scrubUpstreamErrorBody(body, credential) {
306
+ let text = body.toString('utf8');
307
+ if (credential)
308
+ text = text.split(credential).join('[REDACTED]');
309
+ text = text
310
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/-]{8,}=*/gi, 'Bearer [REDACTED]')
311
+ .replace(/\bsk-[A-Za-z0-9_-]{16,}/g, '[REDACTED]');
312
+ return Buffer.from(text, 'utf8');
253
313
  }
254
314
  function buildRedactedUpstreamErrorBody(statusCode, upstreamCode, upstreamType, transient) {
255
315
  return Buffer.from(JSON.stringify({
@@ -284,6 +344,7 @@ export function destroyDesktopBridgeUpstreamAgents() {
284
344
  agent.destroy();
285
345
  upstreamAgents.clear();
286
346
  encodedBodyRefusals.clear();
347
+ forgetCodexLbTurnStates();
287
348
  }
288
349
  const STALE_CONNECTION_ERROR_CODES = new Set(['ECONNRESET', 'EPIPE', 'ECONNABORTED']);
289
350
  function isStalePooledSocketFailure(request, error) {
@@ -319,7 +380,7 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
319
380
  if (!official && !request.credential)
320
381
  throw new DesktopBridgeError('bridge_provider_credential_invalid');
321
382
  await ensureDesktopBridgeRemoteTarget(remote, config.remoteLookup);
322
- if (!official && request.compaction && request.payload) {
383
+ if (!official && request.compaction && answeredByBridge(request.compaction, request.route) && request.payload) {
323
384
  await answerCompactionWithProviderSummary(req, res, config, {
324
385
  kind: request.compaction,
325
386
  payload: request.payload,
@@ -341,6 +402,12 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
341
402
  : buildProviderUpstreamHeaders(req.headers, {
342
403
  providerId: provider.provider_id, authTransport: provider.auth_transport, credential: request.credential,
343
404
  }, target.host);
405
+ const codexLb = provider?.provider_id === 'codex-lb';
406
+ const threadId = request.threadId ?? null;
407
+ if (request.move)
408
+ delete headers['x-codex-turn-state'];
409
+ else if (codexLb)
410
+ admitCodexLbTurnState(headers, threadId, request.credential.fingerprint);
344
411
  if (request.body)
345
412
  headers['content-length'] = String(request.body.length);
346
413
  else
@@ -348,6 +415,8 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
348
415
  if (request.contentEncodingStripped)
349
416
  delete headers['content-encoding'];
350
417
  const agent = upstreamAgent(remote.secure, `${remote.address}:${remote.port}`, config.idleTimeoutMs);
418
+ const idleTimeoutMs = request.compaction ? Math.max(config.idleTimeoutMs, NATIVE_COMPACTION_IDLE_FLOOR_MS) : config.idleTimeoutMs;
419
+ const modelsEtag = advertisedModelsEtag(req, config);
351
420
  const replayable = Buffer.isBuffer(request.body);
352
421
  let body = request.body;
353
422
  let plainFallbackActive = false;
@@ -371,7 +440,7 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
371
440
  ...(remote.tlsServername ? { servername: remote.tlsServername } : {}),
372
441
  });
373
442
  connectTimeout(upstream, config);
374
- upstream.setTimeout(config.idleTimeoutMs, () => upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout')));
443
+ upstream.setTimeout(idleTimeoutMs, () => upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout')));
375
444
  req.once('aborted', abort);
376
445
  res.once('close', abort);
377
446
  upstream.once('error', (error) => {
@@ -390,6 +459,8 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
390
459
  });
391
460
  upstream.once('response', (response) => {
392
461
  const statusCode = response.statusCode || 502;
462
+ if (codexLb)
463
+ rememberCodexLbTurnState(threadId, request.credential.fingerprint, response.headers);
393
464
  if (plainFallbackActive && statusCode < 400 && !official && !encodedBodyRefusals.has(provider.provider_id)) {
394
465
  encodedBodyRefusals.add(provider.provider_id);
395
466
  logHttpRejection({ code: 'bridge_upstream_encoded_body_refused', transport: 'http', ...(req.method === undefined ? {} : { method: req.method }), ...(req.url === undefined ? {} : { url: req.url }), provider_id: provider.provider_id });
@@ -414,7 +485,10 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
414
485
  }
415
486
  responseStarted = true;
416
487
  try {
417
- res.writeHead(statusCode, rewriteResponseHeaders(response.headers, upstreamBaseUrl, authenticatedLocalBaseUrl));
488
+ const responseHeaders = rewriteResponseHeaders(response.headers, upstreamBaseUrl, authenticatedLocalBaseUrl);
489
+ if (modelsEtag)
490
+ responseHeaders['x-models-etag'] = modelsEtag;
491
+ res.writeHead(statusCode, responseHeaders);
418
492
  }
419
493
  catch (error) {
420
494
  response.destroy(error instanceof Error ? error : undefined);
@@ -425,7 +499,7 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
425
499
  return;
426
500
  }
427
501
  if (statusCode >= 400) {
428
- void readRedactedUpstreamError(response).then(({ upstreamCode, upstreamType, unreadableBody }) => {
502
+ void readRedactedUpstreamError(response).then(({ upstreamCode, upstreamType, upstreamParam, unreadableBody, body: upstreamBody }) => {
429
503
  if (budget.plainFallback && (statusCode === 415
430
504
  || (unreadableBody && !upstreamType && !upstreamCode && (statusCode === 400 || statusCode === 422)))) {
431
505
  responseStarted = true;
@@ -440,27 +514,42 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
440
514
  : 'bridge_upstream_transient_mislabel')));
441
515
  return;
442
516
  }
443
- const clientStatus = transientMislabel ? 503 : statusCode;
444
- const body = buildRedactedUpstreamErrorBody(statusCode, upstreamCode, upstreamType, transientMislabel);
517
+ const verbatim = codexLb && upstreamBody !== null;
518
+ const clientStatus = verbatim ? statusCode : transientMislabel ? 503 : statusCode;
519
+ const body = verbatim
520
+ ? scrubUpstreamErrorBody(upstreamBody, request.credential.value)
521
+ : buildRedactedUpstreamErrorBody(statusCode, upstreamCode, upstreamType, transientMislabel);
522
+ const rejected = statusCode >= 400 && statusCode <= 499;
445
523
  logHttpRejection({
446
- code: transientMislabel
447
- ? `bridge_upstream_status_${statusCode}_translated_503`
448
- : upstreamCode ? `bridge_upstream_status_${statusCode}:${upstreamCode}` : `bridge_upstream_status_${statusCode}`,
524
+ code: rejected
525
+ ? 'bridge_upstream_request_rejected'
526
+ : transientMislabel && !verbatim
527
+ ? `bridge_upstream_status_${statusCode}_translated_503`
528
+ : upstreamCode ? `bridge_upstream_status_${statusCode}:${upstreamCode}` : `bridge_upstream_status_${statusCode}`,
449
529
  transport: 'http',
450
530
  ...(req.method === undefined ? {} : { method: req.method }),
451
531
  ...(req.url === undefined ? {} : { url: req.url }),
452
532
  status: clientStatus,
453
533
  provider_id: provider.provider_id,
454
534
  public_model: request.route.public_model,
535
+ ...(rejected ? {
536
+ upstream_status: statusCode,
537
+ upstream_code: upstreamCode,
538
+ upstream_type: upstreamType,
539
+ upstream_param: upstreamParam,
540
+ request_shape: desktopBridgeRequestShape(request.payload),
541
+ } : {}),
455
542
  });
456
543
  responseStarted = true;
457
544
  const responseHeaders = rewriteResponseHeaders(response.headers, upstreamBaseUrl, authenticatedLocalBaseUrl);
458
545
  responseHeaders['content-length'] = String(body.length);
459
546
  delete responseHeaders['transfer-encoding'];
460
- if (transientMislabel)
461
- responseHeaders['retry-after'] = String(responseHeaders['retry-after'] || '10');
462
- if (statusCode === 429 && !responseHeaders['retry-after'])
463
- responseHeaders['retry-after'] = '10';
547
+ delete responseHeaders['content-encoding'];
548
+ if (verbatim && upstreamCode === MODEL_CAPACITY_ERROR_CODE && responseHeaders['retry-after'] === undefined) {
549
+ responseHeaders['retry-after'] = String(MODEL_CAPACITY_RETRY_AFTER_SECONDS);
550
+ }
551
+ if (modelsEtag)
552
+ responseHeaders['x-models-etag'] = modelsEtag;
464
553
  res.writeHead(clientStatus, responseHeaders);
465
554
  res.end(body, () => finish());
466
555
  }).catch(finish);
@@ -479,6 +568,8 @@ export async function forwardHttp(req, res, config, prepared, authenticatedLocal
479
568
  const responseHeaders = rewriteResponseHeaders(response.headers, upstreamBaseUrl, authenticatedLocalBaseUrl);
480
569
  if (advising)
481
570
  delete responseHeaders['content-length'];
571
+ if (modelsEtag)
572
+ responseHeaders['x-models-etag'] = modelsEtag;
482
573
  res.writeHead(statusCode, responseHeaders);
483
574
  }
484
575
  catch (error) {
@@ -638,6 +729,10 @@ export async function requestProviderCompactionSummary(config, input, body, onAc
638
729
  }
639
730
  }
640
731
  }
732
+ function singleRetryAfter(value) {
733
+ const text = String(Array.isArray(value) ? value[0] ?? '' : value ?? '').trim();
734
+ return /^[A-Za-z0-9 ,:.-]{1,64}$/.test(text) ? text : null;
735
+ }
641
736
  function upstreamErrorIds(body) {
642
737
  try {
643
738
  const parsed = JSON.parse(body.toString('utf8'));
@@ -654,12 +749,18 @@ async function answerCompactionWithProviderSummary(req, res, config, input) {
654
749
  const streaming = input.kind === 'trigger';
655
750
  const responseId = newCompactionResponseId();
656
751
  const publicModel = input.route.public_model;
657
- const body = Buffer.from(JSON.stringify(buildCompactionSummaryRequest(input.payload, input.route.upstream_model)));
752
+ const summaryRequest = buildCompactionSummaryRequest(input.payload, input.route.upstream_model);
753
+ adaptRequestForRoute(summaryRequest, input.route, providerExtraInclude(config, input.route));
754
+ const body = Buffer.from(JSON.stringify(summaryRequest));
658
755
  let keepalive = null;
659
756
  const startStream = () => {
660
757
  if (!streaming || res.headersSent)
661
758
  return;
662
- res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
759
+ const modelsEtag = advertisedModelsEtag(req, config);
760
+ res.writeHead(200, {
761
+ 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive',
762
+ ...(modelsEtag ? { 'x-models-etag': modelsEtag } : {}),
763
+ });
663
764
  res.write(sseEvent(compactionCreatedEvent(responseId, publicModel)));
664
765
  keepalive = setInterval(() => { if (!res.writableEnded)
665
766
  res.write(': keepalive\n\n'); }, COMPACTION_KEEPALIVE_MS);
@@ -683,9 +784,10 @@ async function answerCompactionWithProviderSummary(req, res, config, input) {
683
784
  const transient = isTransientUpstreamFailure(call.status, upstreamType, upstreamCode);
684
785
  const clientStatus = transient ? 503 : call.status;
685
786
  const errorBody = buildRedactedUpstreamErrorBody(call.status, upstreamCode, upstreamType, transient);
787
+ const retryAfter = singleRetryAfter(call.headers['retry-after']);
686
788
  res.writeHead(clientStatus, {
687
789
  'content-type': 'application/json', 'cache-control': 'no-store', 'content-length': String(errorBody.length),
688
- ...(transient || call.status === 429 ? { 'retry-after': String(call.headers['retry-after'] || '10') } : {}),
790
+ ...(retryAfter ? { 'retry-after': retryAfter } : {}),
689
791
  });
690
792
  res.end(errorBody);
691
793
  return;
@@ -0,0 +1,62 @@
1
+ export const DESKTOP_BRIDGE_MODELS_PATHS = ['/backend-api/codex/models', '/api/v1/models', '/v1/models'];
2
+ export function desktopBridgeModelsEtag(generation) {
3
+ return `sksb-${generation.slice(0, 16)}`;
4
+ }
5
+ export function isDesktopBridgeModelsRequest(req, pathname) {
6
+ return (req.method === 'GET' || req.method === 'HEAD') && DESKTOP_BRIDGE_MODELS_PATHS.includes(pathname);
7
+ }
8
+ function versionParts(value) {
9
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(value ?? '').trim());
10
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
11
+ }
12
+ function newerThan(left, right) {
13
+ for (let index = 0; index < 3; index += 1) {
14
+ if (left[index] !== right[index])
15
+ return left[index] > right[index];
16
+ }
17
+ return false;
18
+ }
19
+ export function modelsForClient(catalog, clientVersion) {
20
+ const client = versionParts(clientVersion);
21
+ if (!client)
22
+ return [...catalog.models];
23
+ return catalog.models.filter((row) => {
24
+ const minimum = versionParts(row.minimal_client_version);
25
+ return !minimum || !newerThan(minimum, client);
26
+ });
27
+ }
28
+ export function answerDesktopBridgeModels(req, res, catalog) {
29
+ const etag = desktopBridgeModelsEtag(catalog.generation);
30
+ const headers = { etag, 'cache-control': 'no-store', 'content-type': 'application/json' };
31
+ const ifNoneMatch = String(req.headers['if-none-match'] || '').split(',').map((value) => value.trim());
32
+ if (ifNoneMatch.includes(etag)) {
33
+ res.writeHead(304, headers);
34
+ res.end();
35
+ return;
36
+ }
37
+ const clientVersion = new URL(String(req.url || '/'), 'http://bridge.invalid').searchParams.get('client_version');
38
+ const body = Buffer.from(JSON.stringify({ models: modelsForClient(catalog, clientVersion) }));
39
+ res.writeHead(200, { ...headers, 'content-length': String(body.length) });
40
+ res.end(req.method === 'HEAD' ? undefined : body);
41
+ }
42
+ function isRecord(value) {
43
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
44
+ }
45
+ export function frameWithModelsEtag(frame, etag) {
46
+ if (!frame.includes('"codex.response.metadata"'))
47
+ return null;
48
+ let event;
49
+ try {
50
+ event = JSON.parse(frame.toString('utf8'));
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ if (!isRecord(event) || event.type !== 'codex.response.metadata')
56
+ return null;
57
+ const headers = isRecord(event.headers) ? event.headers : {};
58
+ const others = Object.entries(headers).filter(([name]) => name.toLowerCase() !== 'x-models-etag');
59
+ if (headers['x-models-etag'] === etag && others.length === Object.keys(headers).length - 1)
60
+ return null;
61
+ return Buffer.from(JSON.stringify({ ...event, headers: { ...Object.fromEntries(others), 'x-models-etag': etag } }));
62
+ }