@bitkyc08/opencodex 2.69.0 → 2.70.0

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 (147) hide show
  1. package/gui/dist/assets/App-_MrbTrBe.js +51 -0
  2. package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
  3. package/gui/dist/assets/index-DKrO2eF3.js +86 -0
  4. package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
  5. package/gui/dist/index.html +1 -1
  6. package/package.json +1 -1
  7. package/src/adapters/anthropic-model-contract.ts +119 -0
  8. package/src/adapters/anthropic.ts +14 -83
  9. package/src/adapters/base.ts +2 -0
  10. package/src/adapters/command-code-project-context.ts +542 -0
  11. package/src/adapters/command-code-tool-text.ts +99 -67
  12. package/src/adapters/command-code.ts +5 -1
  13. package/src/adapters/cursor/catalog.ts +11 -0
  14. package/src/adapters/cursor/effort-map.ts +3 -0
  15. package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
  16. package/src/adapters/devin/cloud-direct/chat.ts +78 -48
  17. package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
  18. package/src/adapters/devin/context-overflow.ts +75 -0
  19. package/src/adapters/devin/live-models.ts +174 -13
  20. package/src/adapters/devin/reasoning-signature.ts +100 -0
  21. package/src/adapters/devin.ts +310 -117
  22. package/src/adapters/kiro/payload.ts +10 -10
  23. package/src/adapters/kiro-images.ts +58 -7
  24. package/src/adapters/openai-chat/passthrough.ts +3 -1
  25. package/src/adapters/openai-chat.ts +2 -1
  26. package/src/adapters/openai-responses/passthrough.ts +2 -1
  27. package/src/bridge/response-json.ts +15 -1
  28. package/src/bridge/sse.ts +3 -1
  29. package/src/claude/intercept/model-bindings.ts +1 -0
  30. package/src/cli/account-extended.ts +31 -1
  31. package/src/cli/account.ts +3 -0
  32. package/src/cli/capabilities.ts +13 -0
  33. package/src/cli/client-start-fence.ts +36 -0
  34. package/src/cli/dispatch.ts +3 -2
  35. package/src/cli/index.ts +98 -117
  36. package/src/cli/provider-runtime.ts +23 -2
  37. package/src/cli/provider.ts +3 -0
  38. package/src/cli/resolve.ts +18 -0
  39. package/src/cli/start-owner-fence.ts +30 -0
  40. package/src/cli/stop-approval.ts +13 -1
  41. package/src/cli/stop-restore.ts +73 -0
  42. package/src/cli/tray-proxy.ts +221 -18
  43. package/src/cli/version-skew.ts +25 -23
  44. package/src/client/runtime.ts +8 -4
  45. package/src/codex/auth-api/account-list.ts +11 -5
  46. package/src/codex/auth-api/login-flow.ts +5 -3
  47. package/src/codex/auth-api/main-account-probe.ts +186 -110
  48. package/src/codex/auth-api/pool-mode-gate.ts +61 -7
  49. package/src/codex/auth-api/pool-quota-probe.ts +142 -90
  50. package/src/codex/auth-api/reset-credit-service.ts +1 -1
  51. package/src/codex/catalog/gather-capture.ts +1 -0
  52. package/src/codex/catalog/model-hints.ts +3 -3
  53. package/src/codex/catalog/provider-models.ts +2 -0
  54. package/src/codex/low-quota-events.ts +28 -0
  55. package/src/codex/low-quota-observer.ts +23 -0
  56. package/src/codex/low-quota-protection.ts +272 -0
  57. package/src/codex/quota-query-backoff.ts +148 -0
  58. package/src/codex/quota.ts +7 -2
  59. package/src/config/diagnostics.ts +10 -1
  60. package/src/config/load-degrade.ts +17 -0
  61. package/src/config/mutation-lock.ts +3 -4
  62. package/src/config/process-state.ts +1 -1
  63. package/src/config/provider-validation.ts +22 -0
  64. package/src/config/schema/blocked-model-redirects.ts +17 -0
  65. package/src/config/schema/config-schema.ts +4 -1
  66. package/src/config/schema/leaf-validators.ts +33 -2
  67. package/src/config/serving-runtimes.ts +412 -0
  68. package/src/generated/compatibility-version.json +211 -115
  69. package/src/generated/model-metadata.ts +3 -3
  70. package/src/images/loop.ts +3 -0
  71. package/src/lib/errors.ts +2 -1
  72. package/src/lib/handled-signal-exit.ts +23 -0
  73. package/src/lib/request-failure-model.ts +1 -0
  74. package/src/lib/self-launch-argv.ts +9 -0
  75. package/src/lib/shadow-call.ts +5 -2
  76. package/src/lib/strict-semver.ts +26 -0
  77. package/src/lib/transient-service-path.ts +16 -0
  78. package/src/lib/windows-service-wrappers.ts +10 -6
  79. package/src/lib/winsw.ts +3 -1
  80. package/src/oauth/anthropic-model-routes.ts +105 -0
  81. package/src/oauth/anthropic-routing.ts +85 -48
  82. package/src/oauth/devin.ts +135 -5
  83. package/src/oauth/index.ts +20 -3
  84. package/src/oauth/kiro-terminal-failover.ts +23 -9
  85. package/src/oauth/pool-settings-capability.ts +10 -2
  86. package/src/oauth/store.ts +1 -1
  87. package/src/protocols/encoders/adapter-events.ts +3 -1
  88. package/src/providers/github-copilot-context.ts +27 -0
  89. package/src/providers/kiro-models.ts +4 -0
  90. package/src/providers/model-rename-fields.ts +3 -0
  91. package/src/providers/quota/account-cache.ts +8 -3
  92. package/src/providers/quota/devin.ts +286 -0
  93. package/src/providers/quota/vendor-probes-oauth.ts +6 -2
  94. package/src/providers/quota.ts +1 -1
  95. package/src/providers/registry/entries-core.ts +1 -1
  96. package/src/providers/registry/model-seeds.ts +5 -2
  97. package/src/responses/compaction-images.ts +27 -0
  98. package/src/router.ts +104 -16
  99. package/src/routing/analytics.ts +1 -0
  100. package/src/server/auth-cors.ts +9 -1
  101. package/src/server/background-lifecycle.ts +21 -10
  102. package/src/server/chat-completions.ts +2 -1
  103. package/src/server/chat-native.ts +1 -0
  104. package/src/server/index.ts +4 -8
  105. package/src/server/inference/client-encoder-delivery.ts +3 -0
  106. package/src/server/management/context.ts +3 -0
  107. package/src/server/management/low-quota-routes.ts +14 -0
  108. package/src/server/management/oauth-account-routes.ts +21 -1
  109. package/src/server/management/provider-overwrite-carry.ts +79 -6
  110. package/src/server/management/provider-routes.ts +41 -28
  111. package/src/server/management/route-registry.ts +1 -0
  112. package/src/server/management-api.ts +7 -0
  113. package/src/server/responses/adapter-continuation.ts +3 -0
  114. package/src/server/responses/adapter-delivery.ts +3 -0
  115. package/src/server/responses/adapter-dispatch.ts +20 -4
  116. package/src/server/responses/antigravity-validation-refusal.ts +20 -0
  117. package/src/server/responses/core-auth.ts +1 -0
  118. package/src/server/responses/core-codex-account.ts +1 -0
  119. package/src/server/responses/core-normalize.ts +4 -0
  120. package/src/server/responses/passthrough-dispatch.ts +8 -1
  121. package/src/server/responses/request-sidecar-auth.ts +2 -0
  122. package/src/server/responses/request-transport.ts +27 -10
  123. package/src/server/responses/response-effects.ts +4 -1
  124. package/src/server/responses/run-turn-execution.ts +126 -42
  125. package/src/server/responses/sidecar-execution.ts +4 -1
  126. package/src/service/diagnostics.ts +3 -1
  127. package/src/service/guarded-manager-target.ts +39 -12
  128. package/src/service/launchd.ts +14 -6
  129. package/src/service/repair.ts +6 -1
  130. package/src/service/service-child-ownership.ts +168 -0
  131. package/src/service/state.ts +22 -4
  132. package/src/service/systemd.ts +2 -2
  133. package/src/service/windows-guarded-manager.ts +530 -0
  134. package/src/service/windows-taskxml.ts +2 -2
  135. package/src/service.ts +2 -0
  136. package/src/types/config.ts +26 -0
  137. package/src/types/provider.ts +12 -0
  138. package/src/types/request.ts +2 -0
  139. package/src/types.ts +1 -0
  140. package/src/usage/expected-prices.ts +13 -0
  141. package/src/usage/telemetry-contract.ts +1 -0
  142. package/src/vision/anthropic-describe.ts +2 -1
  143. package/src/web-search/anthropic-executor.ts +2 -1
  144. package/src/web-search/loop.ts +1 -0
  145. package/gui/dist/assets/App-DDbgOTh6.js +0 -51
  146. package/gui/dist/assets/index-CuaBJkG4.js +0 -86
  147. package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
@@ -23,9 +23,8 @@ import {
23
23
  import { EMPTY_EXEC_OUTPUT_MESSAGE, annotateCodeModeHostFailure, normalizeEmptyExecToolResultText } from "../exec-tool-result-normalize";
24
24
  import { identifyRoutedModel } from "../identity";
25
25
  import {
26
- countKiroUninlinableImages,
27
26
  extractKiroImages,
28
- kiroUninlinableImageMarker,
27
+ kiroImageOmissionMarker,
29
28
  type KiroImage,
30
29
  } from "../kiro-images";
31
30
  import { convertKiroToolContext } from "../kiro-tools";
@@ -105,6 +104,7 @@ export function kiroCompletionTool(): Record<string, unknown> {
105
104
  };
106
105
  }
107
106
 
107
+ /** Build the Kiro wire request, retaining markers for images that cannot be inlined. */
108
108
  export function buildKiroPayload(
109
109
  parsed: OcxParsedRequest,
110
110
  profileArn: string | undefined,
@@ -245,9 +245,9 @@ export function buildKiroPayload(
245
245
  if (msg.role === "user" || msg.role === "developer") {
246
246
  const content = (msg as { content: string | OcxContentPart[] }).content;
247
247
  const images = extractKiroImages(content);
248
- // Kiro inlines base64 bytes only. A remote reference used to vanish with neither
249
- // bytes nor a trace; attach a bounded, URL-free marker so the loss is visible.
250
- const marker = kiroUninlinableImageMarker(countKiroUninlinableImages(content));
248
+ // Kiro inlines base64 bytes only. Neither remote references nor malformed
249
+ // inline data URLs can be forwarded; leave a bounded, URL-free marker.
250
+ const marker = kiroImageOmissionMarker(content);
251
251
  const text = userContentText(content);
252
252
  pushUser(marker ? (text ? text + "\n" + marker : marker) : text, images);
253
253
  } else if (msg.role === "assistant") {
@@ -295,14 +295,14 @@ export function buildKiroPayload(
295
295
  const annotatedExecText = normalizedExecText === undefined && codeModeExecName !== undefined
296
296
  ? annotateCodeModeHostFailure(text, execOptions)
297
297
  : undefined;
298
- const uninlinableMarker = kiroUninlinableImageMarker(countKiroUninlinableImages(tr.content));
298
+ const omittedImageMarker = kiroImageOmissionMarker(tr.content);
299
299
  // Appended to the SELECTED result text, not to `text`: when an exec normalization
300
300
  // fires, resultText below takes normalizedExecText/annotatedExecText instead, and
301
301
  // a marker attached to `text` would be dropped — reinstating the silent loss this
302
302
  // exists to remove.
303
303
  const chosenText = normalizedExecText ?? annotatedExecText ?? (text.trim() ? text : KIRO_EMPTY_TOOL_RESULT_MESSAGE);
304
- const resultText = uninlinableMarker
305
- ? (chosenText ? chosenText + "\n" + uninlinableMarker : uninlinableMarker)
304
+ const resultText = omittedImageMarker
305
+ ? (chosenText ? chosenText + "\n" + omittedImageMarker : omittedImageMarker)
306
306
  : chosenText;
307
307
  const images = extractKiroImages(tr.content);
308
308
  const toolUseId = normalizeToolId(tr.toolCallId);
@@ -315,8 +315,8 @@ export function buildKiroPayload(
315
315
  ? (annotatedExecText ?? text) : undefined;
316
316
  // The grouping path rebuilds a collapsed turn's content from these texts, so the
317
317
  // marker has to ride along here too or an adjacent-result turn loses it.
318
- const rawGroupText = uninlinableMarker
319
- ? (rawGroupBase ? rawGroupBase + "\n" + uninlinableMarker : uninlinableMarker)
318
+ const rawGroupText = omittedImageMarker
319
+ ? (rawGroupBase ? rawGroupBase + "\n" + omittedImageMarker : omittedImageMarker)
320
320
  : rawGroupBase;
321
321
  const last = turns.at(-1);
322
322
  if (
@@ -1,5 +1,7 @@
1
1
  import type { OcxContentPart } from "../types";
2
2
  import { normalizeImageTargets, type NormalizeOptions, type NormalizeTarget } from "./anthropic-image-normalize";
3
+ import { MAX_INPUT_BASE64_LENGTH, MAX_INPUT_PIXELS } from "./anthropic-image-codec";
4
+ import { sniffImageDimensions } from "./anthropic-image-guard";
3
5
 
4
6
  // CodeWhisperer native image part (matches Kiro IDE wire format): the base64 bytes live directly in
5
7
  // userInputMessage.images, NOT in userInputMessageContext. Verified against kiro-gateway.
@@ -8,8 +10,7 @@ export interface KiroImage {
8
10
  source: { bytes: string }; // pure base64, no "data:...;base64," prefix
9
11
  }
10
12
 
11
- // Codex sends each image as a `data:` URL (base64) or a remote https URL. Only data URLs can be
12
- // inlined as bytes here; remote URLs are not fetchable at request-build time.
13
+ /** Parse inline image bytes; remote URLs are not fetched at request-build time. */
13
14
  function parseDataUrlImage(imageUrl: string): KiroImage | undefined {
14
15
  if (!imageUrl.startsWith("data:")) return undefined;
15
16
  const comma = imageUrl.indexOf(",");
@@ -65,17 +66,36 @@ export function kiroUninlinableImageMarker(count: number): string {
65
66
  return "[" + String(count) + " images omitted: remote image references are not supported by this provider]";
66
67
  }
67
68
 
69
+ /** Report malformed inline references separately from remote URLs. */
70
+ export function kiroImageOmissionMarker(content: string | OcxContentPart[]): string {
71
+ if (typeof content === "string") return "";
72
+ const remote = kiroUninlinableImageMarker(countKiroUninlinableImages(content));
73
+ let malformed = 0;
74
+ for (const part of content) {
75
+ if (part.type === "image" && part.imageUrl.startsWith("data:") && !parseDataUrlImage(part.imageUrl)) malformed++;
76
+ }
77
+ const inline = malformed === 1
78
+ ? "[image omitted: malformed inline image data URL]"
79
+ : malformed > 1 ? `[${malformed} images omitted: malformed inline image data URLs]` : "";
80
+ return [remote, inline].filter(Boolean).join("\n");
81
+ }
82
+
68
83
  /**
69
- * Conservative POLICY caps for the CodeWhisperer GenerateAssistantResponse payload,
70
- * whose limits are undocumented. Derived from adjacent AWS surfaces
84
+ * Conservative POLICY caps for the CodeWhisperer GenerateAssistantResponse payload.
85
+ * The 100-image request cap comes from Kiro's IMAGE_COUNT_EXCEEDED error
86
+ * ("101 exceeds limit 100"); the per-message and byte limits are undocumented
87
+ * and derived from adjacent AWS surfaces
71
88
  * (devlog/260714_image_normalization_pipeline/050): Bedrock `Message` allows 20 images
72
89
  * per message (Converse), and `InvokeModel` caps requests at 25,000,000 bytes — 18MiB
73
90
  * bounds the IMAGE share of the body with headroom for text/tools.
74
91
  */
75
92
  export const KIRO_IMAGE_BASE64_BUDGET = 18 * 1024 * 1024;
76
93
  export const KIRO_MAX_IMAGES_PER_MESSAGE = 20;
94
+ export const KIRO_MAX_IMAGES_PER_REQUEST = 100;
77
95
 
78
96
  const COUNT_CAP_NOTE = "[image omitted: exceeded the 20-image per-message cap; oldest images in this message were dropped]";
97
+ const REQUEST_CAP_NOTE = "[images omitted: exceeded the 100-image request cap; oldest images in this message were dropped]";
98
+ const REQUEST_CAP_EMPTY_NOTE = "[images omitted: exceeded the 100-image request cap; no images remain in this message]";
79
99
 
80
100
  /** A kiro wire message that can carry images (history userInputMessage or currentMessage). */
81
101
  interface KiroImageCarrier {
@@ -105,11 +125,20 @@ function appendNote(carrier: KiroImageCarrier, note: string): void {
105
125
  carrier.content = carrier.content ? `${carrier.content}\n${note}` : note;
106
126
  }
107
127
 
128
+ /** Cheap structural check (no decode or encode) for the request-wide count. */
129
+ function countsTowardRequestCap(image: KiroImage): boolean {
130
+ const b64 = typeof image.source?.bytes === "string" ? image.source.bytes : "";
131
+ if (b64.length === 0 || b64.length > MAX_INPUT_BASE64_LENGTH) return false;
132
+ const dims = sniffImageDimensions(b64);
133
+ return dims !== null && dims.width * dims.height <= MAX_INPUT_PIXELS;
134
+ }
135
+
108
136
  /**
109
137
  * Apply the generous image pipeline to a built CodeWhisperer payload (mutates in
110
- * place): per-message 20-image cap first (oldest dropped), then the shared tier
111
- * machinery with the kiro budget and terminal-overflow DROP (kiro has no downstream
112
- * guard). Test seams (encode/validate) forward into the core.
138
+ * place): per-message 20-image cap, then the 100-image request cap over
139
+ * structurally usable images (oldest dropped), then the shared tier machinery
140
+ * with the kiro budget and terminal-overflow DROP (kiro has no downstream guard).
141
+ * Test seams (encode/validate) forward into the core.
113
142
  */
114
143
  export async function normalizeKiroImages(
115
144
  payload: unknown,
@@ -126,6 +155,28 @@ export async function normalizeKiroImages(
126
155
  appendNote(carrier, COUNT_CAP_NOTE);
127
156
  }
128
157
 
158
+ // Request count cap BEFORE the byte budget: a surplus image must not push survivors to
159
+ // lower tiers, which #4532 then pins across turns. Only structurally usable images count
160
+ // (bytes present, within the bomb limits, dimensions sniffable), so a corrupt image cannot
161
+ // evict valid history; the normalizer below still drops it with its own marker. This stays
162
+ // a cheap header check: truncated data that still sniffs as an image does count.
163
+ let excess = carriers.reduce((count, carrier) => count + (carrier.images ?? []).filter(countsTowardRequestCap).length, 0)
164
+ - KIRO_MAX_IMAGES_PER_REQUEST;
165
+ for (const carrier of carriers) {
166
+ if (excess <= 0) break;
167
+ const images = carrier.images;
168
+ if (!images?.length) continue;
169
+ const kept: KiroImage[] = [];
170
+ for (const image of images) {
171
+ if (excess > 0 && countsTowardRequestCap(image)) excess--;
172
+ else kept.push(image);
173
+ }
174
+ if (kept.length === images.length) continue;
175
+ if (kept.length === 0) delete carrier.images;
176
+ else carrier.images = kept;
177
+ appendNote(carrier, kept.length === 0 ? REQUEST_CAP_EMPTY_NOTE : REQUEST_CAP_NOTE);
178
+ }
179
+
129
180
  // Targets over the survivors, oldest→newest across carriers. Drops resolve the image
130
181
  // by OBJECT IDENTITY at execution time (indices go stale after earlier splices) and
131
182
  // delete an emptied images field per the builder's omission contract.
@@ -6,6 +6,7 @@ import { applyExplicitChatDeveloperRole } from "./developer-role";
6
6
  import { openRouterProviderPayload, resolveOpenRouterRouting } from "../../providers/openrouter-routing";
7
7
  import { resolveVercelGatewayRouting, vercelGatewayProviderPayload } from "../../providers/vercel-gateway-routing";
8
8
  import { fastPolicyForModel } from "../../providers/service-tier";
9
+ import { applyGithubCopilotContextTier } from "../../providers/github-copilot-context";
9
10
  import { canonicalFastTierMarker, decideTier, type ResolvedFastPolicy } from "../../providers/fastwire";
10
11
  import { debugProviderDiagnostic } from "../../lib/debug";
11
12
  import { isDebugEnabled } from "../../lib/debug-settings";
@@ -53,6 +54,7 @@ export function buildOpenAIChatPassthroughRequest(
53
54
  stream: boolean,
54
55
  fastPolicy: ResolvedFastPolicy = fastPolicyForModel(provider, modelId, undefined, "chat"),
55
56
  fastMode?: boolean,
57
+ providerName?: string,
56
58
  ): AdapterRequest {
57
59
  const { url, headers, hasCredential } = openAIChatTransport(provider);
58
60
 
@@ -153,7 +155,7 @@ export function buildOpenAIChatPassthroughRequest(
153
155
  body.stream_options = rawBody.stream_options;
154
156
  }
155
157
 
156
- const bodyJson = JSON.stringify(body);
158
+ const bodyJson = JSON.stringify(applyGithubCopilotContextTier(body, provider, modelId, providerName));
157
159
 
158
160
  if (isDebugEnabled()) {
159
161
  let host = "upstream";
@@ -13,6 +13,7 @@ import { isDebugEnabled } from "../lib/debug-settings";
13
13
  import { openRouterProviderPayload, resolveOpenRouterRouting } from "../providers/openrouter-routing";
14
14
  import { resolveVercelGatewayRouting, vercelGatewayProviderPayload } from "../providers/vercel-gateway-routing";
15
15
  import { fastPolicyForModel } from "../providers/service-tier";
16
+ import { applyGithubCopilotContextTier } from "../providers/github-copilot-context";
16
17
  import { createAdapterTierMetadata, decideTier, type AdapterTierMetadata } from "../providers/fastwire";
17
18
  import {
18
19
  isTranslatorBudgetExceededError,
@@ -229,7 +230,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
229
230
  }
230
231
  if (parsed.stream) body.stream_options = { include_usage: true };
231
232
 
232
- const bodyJson = JSON.stringify(body);
233
+ const bodyJson = JSON.stringify(applyGithubCopilotContextTier(body, provider, parsed.modelId, incoming?.providerName));
233
234
  const actualServiceTier = typeof body.service_tier === "string" ? body.service_tier : null;
234
235
  const tierLog = createAdapterTierMetadata(
235
236
  parsed.options.tierObservation,
@@ -3,6 +3,7 @@ import { nameRoutedIdentity, repairIdentityInResponsesBody, stripRoutedIdentity
3
3
  import { stripBracketedModelSuffix } from "../openai-chat";
4
4
  import { normalizeOpenCodeGoAdditionalTools } from "../opencode-go-additional-tools";
5
5
  import { isXaiResponsesDestination } from "../../providers/xai-transport";
6
+ import { applyGithubCopilotContextTier } from "../../providers/github-copilot-context";
6
7
  import { Buffer } from "node:buffer";
7
8
  import type { IncomingMeta, ProviderAdapter } from "../base";
8
9
  import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage, type TierDecision } from "../../types";
@@ -538,7 +539,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
538
539
  if (!isCanonicalOpenAiForwardProvider(provider)) {
539
540
  validateFinalCustomToolCompatibility(finalBody, provider.supportsResponsesCustomTools);
540
541
  }
541
- const body = JSON.stringify(finalBody);
542
+ const body = JSON.stringify(applyGithubCopilotContextTier(finalBody, provider, parsed.modelId, incoming.providerName));
542
543
  const releaseBodyObservation = translatorBudget.observeExternallyCapped(
543
544
  "passthrough_serialization",
544
545
  Buffer.byteLength(body, "utf8"),
@@ -83,6 +83,8 @@ function buildResponseJSONWithBudget(
83
83
  modelId: string,
84
84
  options?: {
85
85
  hideThinkingSummary?: boolean;
86
+ /** Provider policy: suppress raw content-channel reasoning, keep provider-authored summaries. */
87
+ hideRawReasoning?: boolean;
86
88
  toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>;
87
89
  /** Request-visible tool names. Required for client calls when enforcement is explicitly enabled. */
88
90
  declaredToolNames?: ReadonlySet<string>;
@@ -102,6 +104,13 @@ function buildResponseJSONWithBudget(
102
104
  translatorBudget?: TranslatorBudget;
103
105
  /** Conversation identity for the reasoning replay cache (issue #950). */
104
106
  replayCacheScope?: OcxReasoningReplayScopeRef;
107
+ /**
108
+ * Fold-only callers whose body never reaches the client (direct client encoders): hidden raw
109
+ * reasoning is still handed to the replay cache, but no client-bound `ocxr1` envelope is
110
+ * materialized. Encoding reserves roughly ten times the text against the translator budget,
111
+ * so a block that fit live delivery could otherwise overflow here and lose the cache write.
112
+ */
113
+ omitHiddenReasoningEnvelope?: boolean;
105
114
  },
106
115
  ): Record<string, unknown> {
107
116
  const responseId = `resp_${uuid()}`;
@@ -284,7 +293,12 @@ function buildResponseJSONWithBudget(
284
293
  const rawText = joinChunks(currentRawReasoning);
285
294
  if (!rawText) return;
286
295
  rawReasoningForNextToolCall = rawText;
287
- if (options?.hideThinkingSummary === true) {
296
+ if (options?.hideThinkingSummary === true || options?.hideRawReasoning === true) {
297
+ if (options?.omitHiddenReasoningEnvelope === true) {
298
+ budget?.releaseRetained(currentRawReasoning.bytes, { kind: "reasoning" });
299
+ currentRawReasoning = emptyChunks();
300
+ return;
301
+ }
288
302
  // Same contract as the streaming path: no visible reasoning, txt-only envelope round-trip.
289
303
  pushOutput({
290
304
  type: "reasoning", id: `rs_${uuid()}`, summary: [],
package/src/bridge/sse.ts CHANGED
@@ -80,6 +80,8 @@ export function bridgeToResponsesSSE(
80
80
  */
81
81
  localUpstream?: boolean;
82
82
  hideThinkingSummary?: boolean;
83
+ /** Provider policy: suppress raw content-channel reasoning, keep provider-authored summaries. */
84
+ hideRawReasoning?: boolean;
83
85
  /**
84
86
  * Remote compaction v2 turn: accumulate all assistant text and, on done, emit ONE synthetic
85
87
  * `{type:"compaction", encrypted_content:"ocx1:"+base64(text)}` output item before
@@ -975,7 +977,7 @@ export function bridgeToResponsesSSE(
975
977
  break;
976
978
  }
977
979
  case "reasoning_raw_delta": {
978
- if (options?.hideThinkingSummary) {
980
+ if (options?.hideThinkingSummary || options?.hideRawReasoning) {
979
981
  hiddenRawReasoning = appendString(
980
982
  hiddenRawReasoning,
981
983
  event.text,
@@ -32,6 +32,7 @@ const NATIVE_ROUTE_PREFIX = "native/";
32
32
  */
33
33
  export const DESKTOP_PICKER_ID_SUGGESTIONS: readonly string[] = [
34
34
  "claude-opus-5-5",
35
+ "claude-sonnet-5-5",
35
36
  "claude-sonnet-5",
36
37
  "claude-fable-5-1",
37
38
  "claude-haiku-4-5",
@@ -1,7 +1,7 @@
1
1
  import { loadConfig } from "../config";
2
2
  import { isReservedCodexAccountWord, reportCodexAccountTargetError, resolveCodexAccountTarget } from "./account-target";
3
3
  import { hasPassiveAccountQuota } from "../providers/quota";
4
- import { closeSync, openSync, readSync } from "node:fs";
4
+ import { closeSync, openSync, readSync, readFileSync, statSync } from "node:fs";
5
5
  import {
6
6
  MAX_ACCOUNT_PRIORITY,
7
7
  MIN_ACCOUNT_PRIORITY,
@@ -47,6 +47,7 @@ const EXTENDED_USAGE = `Usage:
47
47
  ocx account pause-exhausted <provider> [--json]
48
48
  ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
49
49
  ocx account sticky <provider> [<1-100>] [--json]
50
+ ocx account routes anthropic [--file <json-file>|--clear] [--json]
50
51
  ocx account remove <provider> <id|alias|main> --yes [--json]
51
52
  ocx account clear-cooldown <provider> <id|alias|main> [--json]
52
53
  ocx account add-key <provider> [--label <label>] [--json]
@@ -1090,3 +1091,32 @@ export async function cmdAlias(args: string[], deps: AccountDeps): Promise<numbe
1090
1091
  else console.log(alias ? `${name}: ${requestedId} is now “${alias}”` : `${name}: cleared alias for ${requestedId}`);
1091
1092
  return 0;
1092
1093
  }
1094
+
1095
+ /** Read or replace the ordered Anthropic model routes through the unified settings API. */
1096
+ export async function cmdRoutes(args: string[], deps: AccountDeps): Promise<number> {
1097
+ const wantsJson = flag(args, "--json");
1098
+ const file = flagValue(args, "--file");
1099
+ const clear = flag(args, "--clear");
1100
+ if (args.shift() !== "anthropic" || args.length || (file.found && (!file.value || clear))) return usage();
1101
+ const baseUrl = await resolveBaseUrl(deps);
1102
+ if (!baseUrl) return proxyUnreachable();
1103
+ let routes: unknown;
1104
+ if (file.found) {
1105
+ try {
1106
+ const info = statSync(file.value!);
1107
+ if (!info.isFile() || info.size > 64 * 1024) return usage("Error: route file must be a regular JSON file of at most 64 KiB");
1108
+ routes = JSON.parse(readFileSync(file.value!, "utf8"));
1109
+ } catch {
1110
+ return usage("Error: could not read a valid JSON route file");
1111
+ }
1112
+ }
1113
+ const writing = file.found || clear;
1114
+ const response = await apiJson(deps, baseUrl, writing ? "PUT" : "GET",
1115
+ writing ? "/api/pool/settings" : "/api/pool/settings?provider=anthropic",
1116
+ writing ? { provider: "anthropic", routes: clear ? null : routes } : undefined);
1117
+ if (response.status === 0) return proxyUnreachable(response.transportError);
1118
+ if (response.status !== 200) return apiError(response.json, "failed to manage Anthropic routes", response.status);
1119
+ if (wantsJson) console.log(JSON.stringify(response.json, null, 2));
1120
+ else console.log(JSON.stringify(response.json.routes ?? [], null, 2));
1121
+ return 0;
1122
+ }
@@ -15,6 +15,7 @@ import {
15
15
  cmdRefresh,
16
16
  cmdRemove,
17
17
  cmdSticky,
18
+ cmdRoutes,
18
19
  cmdStrategy,
19
20
  } from "./account-extended";
20
21
  import { apiError, apiJson, classifyAccount, fetchRows, proxyUnreachable, resolveBaseUrl, type AccountDeps, type AccountRow, type AccountType, type ApiResult }
@@ -55,6 +56,7 @@ const ACCOUNT_USAGE = `Usage:
55
56
  ocx account pause-exhausted <provider> [--json]
56
57
  ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
57
58
  ocx account sticky <provider> [<1-100>] [--json]
59
+ ocx account routes anthropic [--file <json-file>|--clear] [--json]
58
60
  ocx account remove <provider> <account-or-key-id|alias|main> --yes [--json]
59
61
  ocx account clear-cooldown <provider> <account-id|alias|main> [--json]
60
62
  ocx account add-key <provider> [--label <label>] [--json]
@@ -417,6 +419,7 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
417
419
  if (sub === "pause-exhausted") return await cmdPauseExhausted(rest, deps);
418
420
  if (sub === "strategy") return await cmdStrategy(rest, deps);
419
421
  if (sub === "sticky") return await cmdSticky(rest, deps);
422
+ if (sub === "routes") return await cmdRoutes(rest, deps);
420
423
  if (sub === "remove") return await cmdRemove(rest, deps);
421
424
  if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
422
425
  if (sub === "add-key") return await cmdAddKey(rest, deps);
@@ -611,6 +611,19 @@ export const CAPABILITIES: readonly Capability[] = [
611
611
  json: "envelope",
612
612
  details: ["Only meaningful under the sticky-capable strategies; the pool strategy is the other half of this setting."],
613
613
  },
614
+ {
615
+ command: ["account", "routes"],
616
+ summary: "Read, replace, or clear Anthropic OAuth model account routes.",
617
+ routes: [{ method: "GET", path: "/api/pool/settings" }, { method: "PUT", path: "/api/pool/settings" }],
618
+ flags: [
619
+ { name: "--file", value: "string", summary: "Read a bounded JSON route array from a local file." },
620
+ { name: "--clear", value: "boolean", summary: "Remove the stored routes." },
621
+ { name: "--json", value: "boolean", summary: "Emit the unified settings response as JSON." },
622
+ ],
623
+ mutates: true,
624
+ json: "envelope",
625
+ details: ["Only anthropic is supported. The server validates route names, patterns, and account IDs."],
626
+ },
614
627
  {
615
628
  command: ["account", "auto-switch"],
616
629
  summary: "Show or set the usage percentage at which a pool moves to another account.",
@@ -0,0 +1,36 @@
1
+ import type { ServiceChildOwnershipDecision } from "../service/service-child-ownership";
2
+
3
+ /**
4
+ * Connected-client starts publish PID and runtime records too, so a supervised service child
5
+ * must not reach them past a foreign owner any more than the server path may. The server path
6
+ * rechecks inside `bindAndPublishStartOwnership`; this is the same fence for
7
+ * `startClientRuntime`, which binds, publishes and then blocks for the process lifetime.
8
+ *
9
+ * The lease is held from acquisition until the runtime reports its records published (or the
10
+ * start fails), and the ownership decision is re-read under it before anything binds. A refusal
11
+ * releases the lease and hands the refusal to `stayOut`, which exits.
12
+ */
13
+ export async function startClientRuntimeUnderOwnershipLease(deps: {
14
+ acquireLease: () => { release(): void };
15
+ decide: () => ServiceChildOwnershipDecision;
16
+ stayOut: (refusal: string) => never;
17
+ start: (afterPublish: () => void) => Promise<void>;
18
+ }): Promise<void> {
19
+ const lease = deps.acquireLease();
20
+ let released = false;
21
+ const release = () => {
22
+ if (released) return;
23
+ released = true;
24
+ lease.release();
25
+ };
26
+ try {
27
+ const decision = deps.decide();
28
+ if (decision.kind === "stay-out") {
29
+ release();
30
+ deps.stayOut(decision.refusal);
31
+ }
32
+ await deps.start(release);
33
+ } finally {
34
+ release();
35
+ }
36
+ }
@@ -1,3 +1,4 @@
1
+ import type { ProxyRestartStartOutcome } from "./tray-proxy";
1
2
  /**
2
3
  * Registry-driven command dispatch (Phase 3 of the CLI deepening).
3
4
  *
@@ -54,8 +55,8 @@ export interface CliDispatchDeps {
54
55
  handleResolve: (args: ResolveArgs) => Promise<number>;
55
56
  handleTrayProxyStart: (existingIsSuccess?: boolean) => Promise<boolean>;
56
57
  handleTrayProxyRestart: () => Promise<void>;
57
- handleRestartStartWhenStopped: () => Promise<boolean | "skipped">;
58
- handleProxyRestart: (startWhenStopped: () => Promise<boolean | "skipped">) => Promise<boolean>;
58
+ handleRestartStartWhenStopped: (recoveringLiveRestart?: boolean) => Promise<ProxyRestartStartOutcome>;
59
+ handleProxyRestart: (startWhenStopped: (recoveringLiveRestart: boolean) => Promise<ProxyRestartStartOutcome>) => Promise<boolean>;
59
60
  handleUninstall: () => Promise<void>;
60
61
  handleStatus: () => Promise<void>;
61
62
  handleRecoverHistory: () => Promise<void>;