@bitkyc08/opencodex 2.63.0 → 2.64.0-preview.20260923
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/AGENTS_INSTALL.md +4 -3
- package/README.md +46 -33
- package/assets/download-linux.svg +10 -0
- package/assets/download-macos.svg +10 -0
- package/assets/download-windows.svg +10 -0
- package/bin/ocx.mjs +12 -4
- package/gui/dist/assets/App-D5eN1TID.js +50 -0
- package/gui/dist/assets/App-I5AnaSLh.css +1 -0
- package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-Bh_ErQDh.js} +1 -1
- package/gui/dist/assets/index-BEq4OCOz.js +86 -0
- package/gui/dist/assets/index-DiBRuK-d.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +1 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/src/protocol.rs +5 -0
- package/package.json +4 -1
- package/src/adapters/anthropic.ts +66 -7
- package/src/adapters/codebuddy/adapter.ts +112 -16
- package/src/adapters/codebuddy/mcp-server.ts +180 -0
- package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
- package/src/adapters/codebuddy/tool-bridge.ts +597 -0
- package/src/adapters/coding-agent/protocol.ts +123 -10
- package/src/adapters/coding-agent/turn.ts +324 -2
- package/src/adapters/command-code-restored-schema.ts +112 -0
- package/src/adapters/command-code-tool-text.ts +589 -0
- package/src/adapters/command-code.ts +80 -6
- package/src/adapters/cursor/catalog.ts +12 -0
- package/src/adapters/cursor/current-request.ts +46 -0
- package/src/adapters/cursor/discovery.ts +1 -1
- package/src/adapters/cursor/effort-map.ts +4 -0
- package/src/adapters/cursor/native-exec.ts +15 -0
- package/src/adapters/cursor/protobuf-events.ts +40 -12
- package/src/adapters/cursor/protobuf-request.ts +83 -18
- package/src/adapters/cursor/request-builder.ts +5 -4
- package/src/adapters/cursor/tool-guidance.ts +1 -1
- package/src/adapters/devin/live-models.ts +7 -0
- package/src/adapters/inline-think-tags.ts +251 -0
- package/src/adapters/kiro/adapter.ts +1 -0
- package/src/adapters/kiro/stream.ts +5 -3
- package/src/adapters/kiro/usage.ts +4 -3
- package/src/adapters/mimo-free.ts +37 -18
- package/src/adapters/openai-chat/messages.ts +5 -5
- package/src/adapters/openai-chat/tool-schema.ts +124 -2
- package/src/adapters/openai-chat.ts +13 -3
- package/src/adapters/openai-responses/passthrough.ts +15 -2
- package/src/adapters/openai-responses/reasoning.ts +14 -1
- package/src/adapters/openai-responses/request-strips.ts +29 -3
- package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
- package/src/adapters/qoder/adapter.ts +15 -12
- package/src/adapters/qoder/scaffold-guard.ts +45 -13
- package/src/bridge/internal.ts +20 -0
- package/src/bridge/sse.ts +14 -5
- package/src/claude/agents-inject.ts +2 -1
- package/src/claude/desktop-applied-marker.ts +44 -0
- package/src/claude/desktop-profile.ts +22 -12
- package/src/claude/inbound.ts +8 -3
- package/src/cli/access.ts +22 -5
- package/src/cli/account-api.ts +6 -0
- package/src/cli/account-auth.ts +5 -2
- package/src/cli/account.ts +5 -6
- package/src/cli/aside-profiles.ts +61 -1
- package/src/cli/claude-desktop.ts +3 -2
- package/src/cli/claude.ts +3 -1
- package/src/cli/codex-cli-update.ts +2 -2
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +12 -4
- package/src/cli/index.ts +98 -10
- package/src/cli/registry.ts +1 -1
- package/src/cli/resolve.ts +113 -1
- package/src/cli/root.ts +5 -0
- package/src/cli/stop-approval.ts +186 -0
- package/src/cli/stop-report.ts +1 -1
- package/src/cli/system-restart-client.ts +19 -4
- package/src/client/hub-client.ts +25 -18
- package/src/clients/config-export.ts +12 -4
- package/src/codex/account-auto-switch.ts +59 -0
- package/src/codex/account-lifecycle.ts +7 -0
- package/src/codex/account-priority.ts +10 -0
- package/src/codex/account-usability.ts +9 -0
- package/src/codex/auth-api/account-list.ts +5 -0
- package/src/codex/auth-api/login-flow.ts +15 -5
- package/src/codex/auth-api/login-state.ts +5 -16
- package/src/codex/auth-api/pool-mode-gate.ts +30 -7
- package/src/codex/auth-api/routes.ts +39 -3
- package/src/codex/auth-context.ts +145 -17
- package/src/codex/catalog/build-entries.ts +2 -1
- package/src/codex/catalog/effort.ts +8 -2
- package/src/codex/catalog/metadata.ts +33 -7
- package/src/codex/catalog/native-models.ts +102 -5
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +19 -6
- package/src/codex/cli-installation-targets.ts +44 -40
- package/src/codex/codex-write-lock.ts +32 -2
- package/src/codex/inject/multi-agent-v2.ts +90 -0
- package/src/codex/inject/plan.ts +365 -0
- package/src/codex/inject.ts +237 -362
- package/src/codex/log-guard/maintenance.ts +30 -13
- package/src/codex/project-config-warnings.ts +47 -7
- package/src/codex/prompt-layers/encoding.ts +41 -0
- package/src/codex/prompt-layers/toml-read.ts +1 -1
- package/src/codex/prompt-layers.ts +17 -13
- package/src/codex/prompt-text-probe.ts +20 -10
- package/src/codex/quota-observation-freshness.ts +34 -0
- package/src/codex/quota-rejection.ts +8 -4
- package/src/codex/quota.ts +4 -2
- package/src/codex/routing/selection.ts +32 -9
- package/src/codex/routing.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +4 -3
- package/src/codex/sync.ts +11 -0
- package/src/codex/windows-installation-files.ts +33 -11
- package/src/codex/write-coordination.ts +4 -0
- package/src/combos/request.ts +6 -2
- package/src/companion/settings.ts +6 -1
- package/src/config/atomic-write.ts +12 -1
- package/src/config/derived-registries.ts +29 -0
- package/src/config/diagnostics.ts +17 -0
- package/src/config/live-reconcile.ts +11 -2
- package/src/config/load-degrade.ts +9 -2
- package/src/config/persist-unlocked.ts +4 -3
- package/src/config/persisted-mutation.ts +94 -0
- package/src/config/provider-validation.ts +1 -1
- package/src/config/proxy-env.ts +62 -12
- package/src/config/rebase-provenance.ts +80 -1
- package/src/config/schema/config-schema.ts +5 -0
- package/src/config/schema/leaf-validators.ts +50 -1
- package/src/config/subagent-models.ts +41 -15
- package/src/config.ts +18 -95
- package/src/generated/compatibility-version.json +330 -218
- package/src/generated/model-metadata.ts +8 -5
- package/src/github/star-state.ts +46 -6
- package/src/grok/inject.ts +74 -40
- package/src/grok/status.ts +11 -4
- package/src/integrations/cursor-effort-table.ts +50 -13
- package/src/integrations/raycast-detect.ts +19 -4
- package/src/integrations/serialize.ts +11 -1
- package/src/lib/bounded-body.ts +30 -0
- package/src/lib/crash-guard.ts +52 -4
- package/src/lib/local-aside-sync-contract.ts +41 -0
- package/src/lib/package-tree-integrity.ts +181 -9
- package/src/lib/proxy-env.ts +18 -1
- package/src/lib/request-failure-attribution.ts +1 -0
- package/src/lib/request-failure-model.ts +3 -0
- package/src/lib/service-secrets.ts +99 -2
- package/src/lib/socks5-fetch.ts +43 -14
- package/src/lib/token-estimate.ts +17 -2
- package/src/oauth/command-code.ts +3 -2
- package/src/oauth/generic-account-failover.ts +29 -1
- package/src/oauth/index.ts +24 -16
- package/src/oauth/login-flow-state.ts +5 -5
- package/src/oauth/meta-muse-device.ts +49 -7
- package/src/oauth/store.ts +74 -9
- package/src/providers/alibaba-region-backup.ts +16 -1
- package/src/providers/anthropic-fast.ts +89 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
- package/src/providers/anthropic-reset-grants.ts +329 -0
- package/src/providers/claude-cli-identity.ts +12 -0
- package/src/providers/command-code-efforts.ts +184 -102
- package/src/providers/derive.ts +11 -3
- package/src/providers/fastwire.ts +16 -3
- package/src/providers/label.ts +8 -3
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota/vendor-probes-oauth.ts +2 -1
- package/src/providers/reasoning-metadata.ts +38 -15
- package/src/providers/registry/entries-core.ts +95 -7
- package/src/providers/registry/entries-extended.ts +21 -10
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +31 -3
- package/src/providers/registry/types.ts +3 -1
- package/src/providers/resolved-model-policy-merge.ts +38 -8
- package/src/providers/resolved-model-policy.ts +4 -2
- package/src/providers/service-tier.ts +3 -1
- package/src/reasoning-effort.ts +6 -5
- package/src/responses/code-mode-helper-compat.ts +14 -4
- package/src/responses/code-mode-shell-input.ts +54 -0
- package/src/responses/custom-tool-compat.ts +173 -5
- package/src/responses/parser.ts +38 -2
- package/src/responses/reasoning-replay-cache.ts +26 -0
- package/src/router.ts +11 -1
- package/src/routing/history/indexer.ts +7 -2
- package/src/routing/history/schema.ts +3 -1
- package/src/server/adapter-resolve.ts +2 -2
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +14 -1
- package/src/server/chat-native.ts +17 -0
- package/src/server/claude-messages.ts +4 -3
- package/src/server/direct-local-http.ts +45 -19
- package/src/server/gui-session.ts +6 -15
- package/src/server/index/package-tree-guard.ts +53 -0
- package/src/server/index/serve-options.ts +17 -3
- package/src/server/index/startup-warnings.ts +17 -0
- package/src/server/index.ts +15 -17
- package/src/server/lifecycle.ts +8 -0
- package/src/server/live.ts +54 -4
- package/src/server/management/agent-settings-routes.ts +44 -8
- package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
- package/src/server/management/codex-prompt-routes.ts +5 -1
- package/src/server/management/config-routes.ts +14 -4
- package/src/server/management/oauth-account-routes.ts +17 -1
- package/src/server/management/provider-overwrite-carry.ts +164 -0
- package/src/server/management/provider-routes.ts +36 -6
- package/src/server/management/remote-workspace-routes.ts +2 -2
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/management/shadow-call-validation.ts +39 -0
- package/src/server/management/system-restart.ts +72 -6
- package/src/server/management-api.ts +16 -2
- package/src/server/management-auth.ts +52 -8
- package/src/server/proxy-liveness.ts +110 -4
- package/src/server/request-log.ts +26 -9
- package/src/server/request-metrics.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +6 -3
- package/src/server/responses/adapter-dispatch.ts +65 -1
- package/src/server/responses/compact.ts +31 -1
- package/src/server/responses/compaction-routing.ts +28 -3
- package/src/server/responses/core-codex-account.ts +85 -26
- package/src/server/responses/core-combo-failure.ts +16 -15
- package/src/server/responses/core-normalize.ts +5 -1
- package/src/server/responses/core-opaque-recovery.ts +50 -2
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core-replay.ts +7 -0
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/encrypted-payload.ts +23 -9
- package/src/server/responses/passthrough-delivery.ts +55 -35
- package/src/server/responses/passthrough-dispatch.ts +12 -13
- package/src/server/responses/policy-fallback.ts +12 -1
- package/src/server/responses/request-prepare.ts +46 -7
- package/src/server/responses/request-spend.ts +31 -15
- package/src/server/responses/run-turn-execution.ts +6 -5
- package/src/server/responses/shadow-target-availability.ts +61 -0
- package/src/server/responses-custom-tool-repair.ts +2 -0
- package/src/service/claim.ts +185 -0
- package/src/service/cli.ts +10 -1
- package/src/service/guarded-manager-target.ts +151 -0
- package/src/service/guards.ts +47 -48
- package/src/service/managing-cli.ts +170 -0
- package/src/service/orchestration.ts +3 -1
- package/src/service/state.ts +4 -0
- package/src/service/systemd.ts +16 -1
- package/src/service.ts +1 -1
- package/src/types/config.ts +8 -0
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +10 -0
- package/src/types/tools.ts +9 -1
- package/src/types/wire.ts +68 -4
- package/src/types.ts +1 -0
- package/src/update/job.ts +10 -16
- package/src/update/npm-invocation.mjs +17 -16
- package/src/update/transactional-install.d.mts +22 -1
- package/src/update/transactional-install.mjs +176 -19
- package/src/update/update-failure-guidance.d.mts +8 -0
- package/src/update/update-failure-guidance.mjs +47 -0
- package/src/usage/expected-prices.ts +54 -12
- package/src/usage/log.ts +117 -3
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/timeline.ts +34 -6
- package/src/usage/user-cost-overlay-reconciler.ts +3 -3
- package/src/vision/reasoning.ts +2 -4
- package/src/web-search/xai-executor.ts +14 -4
- package/gui/dist/assets/App-BqrsSrIR.js +0 -50
- package/gui/dist/assets/index-C6SJrh0N.js +0 -86
- package/gui/dist/assets/index-DdDunwDb.css +0 -1
- package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
- package/src/adapters/kiro-thinking.ts +0 -112
|
@@ -198,6 +198,37 @@ const MOONSHOT_MAX_REF_EXPANSIONS = 512;
|
|
|
198
198
|
*/
|
|
199
199
|
const MOONSHOT_MAX_SCHEMA_DEPTH = 64;
|
|
200
200
|
const MOONSHOT_MAX_SCHEMA_NODES = 4_096;
|
|
201
|
+
const MOONSHOT_MAX_INLINED_SCHEMA_BYTES = 1024 * 1024;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Measure only as far as the caller's remaining allowance. Keeping this iterative avoids
|
|
205
|
+
* reintroducing the deep-schema stack exhaustion that the normalizer's depth limit prevents.
|
|
206
|
+
*/
|
|
207
|
+
function serializedJsonBytesUpTo(value: unknown, limit: number): number {
|
|
208
|
+
const encoder = new TextEncoder();
|
|
209
|
+
const pending: unknown[] = [value];
|
|
210
|
+
let bytes = 0;
|
|
211
|
+
while (pending.length > 0 && bytes <= limit) {
|
|
212
|
+
const item = pending.pop();
|
|
213
|
+
if (Array.isArray(item)) {
|
|
214
|
+
bytes += 2 + Math.max(0, item.length - 1);
|
|
215
|
+
for (const child of item) pending.push(child);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (isXaiObjectSchema(item)) {
|
|
219
|
+
const entries = Object.entries(item);
|
|
220
|
+
bytes += 2 + Math.max(0, entries.length - 1);
|
|
221
|
+
for (const [key, child] of entries) {
|
|
222
|
+
bytes += encoder.encode(JSON.stringify(key)).byteLength + 1;
|
|
223
|
+
pending.push(child);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const encoded = JSON.stringify(item);
|
|
228
|
+
bytes += encoder.encode(encoded === undefined ? "null" : encoded).byteLength;
|
|
229
|
+
}
|
|
230
|
+
return bytes;
|
|
231
|
+
}
|
|
201
232
|
|
|
202
233
|
/**
|
|
203
234
|
* Assertion keywords whose meaning under a `$ref` is CONJUNCTION, not replacement. A node
|
|
@@ -309,8 +340,19 @@ function composeProperties(
|
|
|
309
340
|
return combined;
|
|
310
341
|
}
|
|
311
342
|
|
|
343
|
+
/**
|
|
344
|
+
* The inline-byte allowance for one request. Sharing it across tools matters: a per-tool
|
|
345
|
+
* budget would let a large catalog multiply the cap by its tool count, reintroducing the
|
|
346
|
+
* request amplification this bound exists to prevent.
|
|
347
|
+
*/
|
|
348
|
+
interface MoonshotInlineByteBudget {
|
|
349
|
+
remaining: number;
|
|
350
|
+
}
|
|
351
|
+
|
|
312
352
|
interface MoonshotNormalizeState {
|
|
313
353
|
activeRefs: Set<string>;
|
|
354
|
+
inlineSizeCache: WeakMap<Record<string, unknown>, number>;
|
|
355
|
+
inlineByteBudget: MoonshotInlineByteBudget;
|
|
314
356
|
remainingExpansions: number;
|
|
315
357
|
remainingNodes: number;
|
|
316
358
|
}
|
|
@@ -342,10 +384,37 @@ function normalizeMoonshotSchemaNode(
|
|
|
342
384
|
|
|
343
385
|
const target = lookupLocalJsonPointer(root, ref);
|
|
344
386
|
if (isXaiObjectSchema(target)) {
|
|
387
|
+
// Charge the referenced value before copying it. Object/node counts do not cover large
|
|
388
|
+
// maps of boolean schemas, which otherwise allow a small input to create hundreds of
|
|
389
|
+
// full copies before the final request is serialized.
|
|
390
|
+
let inlineBytes = state.inlineSizeCache.get(target);
|
|
391
|
+
if (inlineBytes === undefined) {
|
|
392
|
+
inlineBytes = serializedJsonBytesUpTo(target, MOONSHOT_MAX_INLINED_SCHEMA_BYTES);
|
|
393
|
+
state.inlineSizeCache.set(target, inlineBytes);
|
|
394
|
+
}
|
|
395
|
+
if (inlineBytes > state.inlineByteBudget.remaining) return { $ref: ref };
|
|
396
|
+
const bytesBefore = state.inlineByteBudget.remaining;
|
|
397
|
+
const expansionsBefore = state.remainingExpansions;
|
|
398
|
+
const nodesBefore = state.remainingNodes;
|
|
399
|
+
state.inlineByteBudget.remaining -= inlineBytes;
|
|
345
400
|
state.remainingExpansions -= 1;
|
|
346
401
|
state.activeRefs.add(ref);
|
|
347
402
|
const resolvedTarget = normalizeMoonshotSchemaNode(target, root, state, depth + 1);
|
|
348
403
|
state.activeRefs.delete(ref);
|
|
404
|
+
// Nested copies have already spent from the shared allowance. Charge only
|
|
405
|
+
// growth that their own charges do not cover.
|
|
406
|
+
const nestedCharges = bytesBefore - inlineBytes - state.inlineByteBudget.remaining;
|
|
407
|
+
const normalizedBytes = serializedJsonBytesUpTo(
|
|
408
|
+
resolvedTarget, bytesBefore,
|
|
409
|
+
);
|
|
410
|
+
const growthBytes = Math.max(0, normalizedBytes - inlineBytes - nestedCharges);
|
|
411
|
+
if (growthBytes > state.inlineByteBudget.remaining) {
|
|
412
|
+
state.inlineByteBudget.remaining = bytesBefore;
|
|
413
|
+
state.remainingExpansions = expansionsBefore;
|
|
414
|
+
state.remainingNodes = nodesBefore;
|
|
415
|
+
return { $ref: ref };
|
|
416
|
+
}
|
|
417
|
+
state.inlineByteBudget.remaining -= growthBytes;
|
|
349
418
|
const merged: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
350
419
|
if (isXaiObjectSchema(resolvedTarget)) {
|
|
351
420
|
for (const [key, value] of Object.entries(resolvedTarget)) merged[key] = value;
|
|
@@ -380,6 +449,20 @@ function normalizeMoonshotSchemaNode(
|
|
|
380
449
|
}
|
|
381
450
|
merged[key] = normalized;
|
|
382
451
|
}
|
|
452
|
+
|
|
453
|
+
// Re-normalize only composed properties that retain a $ref alongside sibling keywords
|
|
454
|
+
if (isXaiObjectSchema(merged.properties)) {
|
|
455
|
+
for (const [propName, propVal] of Object.entries(merged.properties as Record<string, unknown>)) {
|
|
456
|
+
if (isXaiObjectSchema(propVal) && typeof propVal.$ref === "string" && moonshotRefTargetKeys(propVal).length > 0) {
|
|
457
|
+
(merged.properties as Record<string, unknown>)[propName] = normalizeMoonshotSchemaNode(
|
|
458
|
+
propVal,
|
|
459
|
+
root,
|
|
460
|
+
state,
|
|
461
|
+
depth + 1,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
383
466
|
return merged;
|
|
384
467
|
}
|
|
385
468
|
|
|
@@ -397,13 +480,49 @@ function normalizeMoonshotSchemaNode(
|
|
|
397
480
|
? value
|
|
398
481
|
: normalizeMoonshotSchemaNode(value, root, state, depth + 1);
|
|
399
482
|
}
|
|
483
|
+
|
|
484
|
+
// Moonshot MFJS requirements:
|
|
485
|
+
// 1. Stamp "object" if properties are present, or if allOf defines object properties/variants,
|
|
486
|
+
// so Moonshot's validator recognizes the schema as a valid termination condition.
|
|
487
|
+
// 2. Infer scalar types for bare const and enum keywords.
|
|
488
|
+
if (out.type === undefined) {
|
|
489
|
+
const isObjectAllOf = Array.isArray(out.allOf) && out.allOf.some(
|
|
490
|
+
variant => isXaiObjectSchema(variant) && (
|
|
491
|
+
variant.type === "object" ||
|
|
492
|
+
variant.properties !== undefined ||
|
|
493
|
+
variant.additionalProperties !== undefined
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
if (out.properties !== undefined || out.additionalProperties !== undefined || isObjectAllOf) {
|
|
497
|
+
out.type = "object";
|
|
498
|
+
} else if (out.const !== undefined) {
|
|
499
|
+
const t = typeof out.const;
|
|
500
|
+
if (t === "string" || t === "number" || t === "boolean") {
|
|
501
|
+
out.type = t;
|
|
502
|
+
}
|
|
503
|
+
} else if (Array.isArray(out.enum) && out.enum.length > 0) {
|
|
504
|
+
if (out.enum.every(x => typeof x === "string")) {
|
|
505
|
+
out.type = "string";
|
|
506
|
+
} else if (out.enum.every(x => typeof x === "number")) {
|
|
507
|
+
out.type = "number";
|
|
508
|
+
} else if (out.enum.every(x => typeof x === "boolean")) {
|
|
509
|
+
out.type = "boolean";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
400
514
|
return out;
|
|
401
515
|
}
|
|
402
516
|
|
|
403
|
-
function normalizeMoonshotToolParameters(
|
|
517
|
+
function normalizeMoonshotToolParameters(
|
|
518
|
+
parameters: unknown,
|
|
519
|
+
inlineByteBudget: MoonshotInlineByteBudget,
|
|
520
|
+
): Record<string, unknown> {
|
|
404
521
|
const rooted = ensureRootObjectType(parameters);
|
|
405
522
|
const normalized = normalizeMoonshotSchemaNode(rooted, rooted, {
|
|
406
523
|
activeRefs: new Set<string>(),
|
|
524
|
+
inlineSizeCache: new WeakMap<Record<string, unknown>, number>(),
|
|
525
|
+
inlineByteBudget,
|
|
407
526
|
remainingExpansions: MOONSHOT_MAX_REF_EXPANSIONS,
|
|
408
527
|
remainingNodes: MOONSHOT_MAX_SCHEMA_NODES,
|
|
409
528
|
});
|
|
@@ -420,11 +539,14 @@ export function toolsToChatFormat(
|
|
|
420
539
|
if (tools.length === 0) return undefined;
|
|
421
540
|
const xaiTarget = isXaiSchemaTarget(provider);
|
|
422
541
|
const moonshotTarget = !xaiTarget && isMoonshotSchemaTarget(provider);
|
|
542
|
+
const moonshotInlineByteBudget: MoonshotInlineByteBudget = {
|
|
543
|
+
remaining: MOONSHOT_MAX_INLINED_SCHEMA_BYTES,
|
|
544
|
+
};
|
|
423
545
|
const formatted = tools.flatMap(t => {
|
|
424
546
|
const normalized = xaiTarget
|
|
425
547
|
? normalizeXaiToolParameters(t.parameters)
|
|
426
548
|
: moonshotTarget
|
|
427
|
-
? normalizeMoonshotToolParameters(t.parameters)
|
|
549
|
+
? normalizeMoonshotToolParameters(t.parameters, moonshotInlineByteBudget)
|
|
428
550
|
: ensureRootObjectType(t.parameters);
|
|
429
551
|
const parameters = stripUnicodePropertyPatterns(stripResponsesOnlyEncryptedMarker(normalized));
|
|
430
552
|
|
|
@@ -4,6 +4,7 @@ import { applyExplicitChatReasoningWirePolicy } from "./openai-chat/reasoning-wi
|
|
|
4
4
|
import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
|
|
5
5
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig, OcxUsage } from "../types";
|
|
6
6
|
import { modelInList } from "../types";
|
|
7
|
+
import { createInlineThinkContentSplitter, splitInlineThinkContent } from "./inline-think-tags";
|
|
7
8
|
import { mapReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
8
9
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
9
10
|
import { sseFieldValue } from "../lib/sse-decoder";
|
|
@@ -355,6 +356,12 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
355
356
|
// Gate on the routed model, not list length: a mixed openai-chat provider
|
|
356
357
|
// can list MiniMax ids without putting every sibling on MiniMax semantics.
|
|
357
358
|
const reasoningDetailsOptIn = modelInList(provider.reasoningDetailsModels, lastRequestedModelId ?? "");
|
|
359
|
+
// A gateway with no server-side reasoning parser leaves thinking inline in `content` as
|
|
360
|
+
// <think> blocks, which would otherwise render as the answer. Passthrough unless opted in.
|
|
361
|
+
const inlineThink = createInlineThinkContentSplitter(provider.inlineThinkTagModels, lastRequestedModelId, budget);
|
|
362
|
+
const emitContent = function* (events: AdapterEvent[]): Generator<AdapterEvent> {
|
|
363
|
+
for (const event of events) { if (event.type === "text_delta") sawUserFacingOutput = true; yield event; }
|
|
364
|
+
};
|
|
358
365
|
|
|
359
366
|
const handleDataLine = function* (line: string): Generator<AdapterEvent, "continue" | "terminate"> {
|
|
360
367
|
const rawPayload = sseFieldValue(line, "data");
|
|
@@ -362,6 +369,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
362
369
|
const payload = rawPayload.trim();
|
|
363
370
|
if (payload.length === 0) return "continue";
|
|
364
371
|
if (payload === "[DONE]") {
|
|
372
|
+
yield* emitContent(inlineThink.flush());
|
|
365
373
|
if ((yield* flushToolCalls()) === "terminate") return "terminate";
|
|
366
374
|
const stopReason = stopReasonFor(finishReason);
|
|
367
375
|
yield { type: "done", usage: pendingUsage, ...(stopReason ? { stopReason } : {}) };
|
|
@@ -422,8 +430,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
422
430
|
if (reasoningText !== undefined) yield { type: "reasoning_raw_delta", text: reasoningText };
|
|
423
431
|
}
|
|
424
432
|
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
425
|
-
|
|
426
|
-
yield { type: "text_delta", text: delta.content };
|
|
433
|
+
yield* emitContent(inlineThink.feed(delta.content));
|
|
427
434
|
}
|
|
428
435
|
|
|
429
436
|
const rawToolCalls = delta.tool_calls;
|
|
@@ -562,6 +569,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
562
569
|
}
|
|
563
570
|
|
|
564
571
|
if (typeof choice.finish_reason === "string" && choice.finish_reason) {
|
|
572
|
+
yield* emitContent(inlineThink.flush());
|
|
565
573
|
if ((yield* flushToolCalls()) === "terminate") return "terminate";
|
|
566
574
|
}
|
|
567
575
|
return "continue";
|
|
@@ -605,6 +613,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
605
613
|
if (buffer.length > 0) {
|
|
606
614
|
if ((yield* handleDataLine(buffer)) === "terminate") return;
|
|
607
615
|
}
|
|
616
|
+
yield* emitContent(inlineThink.flush());
|
|
608
617
|
const sawFinish = finishReason !== undefined;
|
|
609
618
|
if (!sawFinish && pendingToolCalls.length > 0) {
|
|
610
619
|
// Some OpenAI-compatible gateways close immediately after a complete function-call
|
|
@@ -652,6 +661,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
652
661
|
} finally {
|
|
653
662
|
budget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
654
663
|
reasoningDetailTracker.release();
|
|
664
|
+
inlineThink.dispose();
|
|
655
665
|
closeToolCalls();
|
|
656
666
|
reader.releaseLock();
|
|
657
667
|
}
|
|
@@ -738,7 +748,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
738
748
|
if (segments.length > 0) reasoningText = segments.map(s => s.text).join("");
|
|
739
749
|
}
|
|
740
750
|
if (reasoningText !== undefined) events.push({ type: "reasoning_raw_delta", text: reasoningText });
|
|
741
|
-
if (typeof msg.content === "string") events.push(
|
|
751
|
+
if (typeof msg.content === "string") events.push(...splitInlineThinkContent(provider.inlineThinkTagModels, lastRequestedModelId, budget, msg.content));
|
|
742
752
|
const rawToolCalls = msg.tool_calls;
|
|
743
753
|
if (rawToolCalls !== undefined && rawToolCalls !== null) {
|
|
744
754
|
if (!Array.isArray(rawToolCalls)) {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isOpenAiOperatedResponsesDestination,
|
|
16
16
|
} from "../../providers/openai-tiers";
|
|
17
17
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
18
|
-
import { rewriteRoutedCustomToolsForUpstream } from "../../responses/custom-tool-compat";
|
|
18
|
+
import { rewriteRoutedCustomToolsForUpstream, validateFinalCustomToolCompatibility } from "../../responses/custom-tool-compat";
|
|
19
19
|
import { rewriteRoutedToolSearchForUpstream } from "../../responses/tool-search-compat";
|
|
20
20
|
import { rewriteRoutedNamespaceToolsForUpstream } from "../../responses/namespace-tool-compat";
|
|
21
21
|
import { repairLegacyDottedToolCallNames } from "../../responses/legacy-dotted-tool-name-repair";
|
|
@@ -295,7 +295,15 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
295
295
|
}
|
|
296
296
|
const synthesizeMissingCallOutputs = !forward && (stateless || pairedToolResults);
|
|
297
297
|
if (forward || stateless || pairedToolResults) {
|
|
298
|
-
|
|
298
|
+
// A stateful destination can resolve an output-only delta against the call stored behind
|
|
299
|
+
// an unexpanded previous_response_id. All other shapes have no hidden call to preserve.
|
|
300
|
+
const repairOrphanOutputs = forward || stateless || !unexpandedMiss;
|
|
301
|
+
outBody = repairOrphanedInputItems(
|
|
302
|
+
outBody,
|
|
303
|
+
unexpandedMiss,
|
|
304
|
+
synthesizeMissingCallOutputs,
|
|
305
|
+
repairOrphanOutputs,
|
|
306
|
+
);
|
|
299
307
|
}
|
|
300
308
|
if (provider.dropResponsesReasoningItems === true) {
|
|
301
309
|
outBody = dropResponsesReasoningInputItems(outBody);
|
|
@@ -450,11 +458,13 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
450
458
|
preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true,
|
|
451
459
|
dropNullContentChannel: !isOpenAiOperatedResponsesDestination(provider),
|
|
452
460
|
stripEncryptedContent: threadServingIdentityChanged || requiresPlaintextReasoningReplay(provider),
|
|
461
|
+
dropForeignItemId: parsed._dropForeignReasoningItemIds === true,
|
|
453
462
|
},
|
|
454
463
|
),
|
|
455
464
|
provider,
|
|
456
465
|
),
|
|
457
466
|
),
|
|
467
|
+
isXaiResponsesDestination(provider),
|
|
458
468
|
),
|
|
459
469
|
isXaiSchemaTarget(provider),
|
|
460
470
|
);
|
|
@@ -503,6 +513,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
503
513
|
// HTTP and the WebSocket outbound, because the WS path transports this same request
|
|
504
514
|
// instead of rebuilding it.
|
|
505
515
|
observeOutbound(parsed._rawBody, finalBody, headers);
|
|
516
|
+
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
517
|
+
validateFinalCustomToolCompatibility(finalBody, provider.supportsResponsesCustomTools);
|
|
518
|
+
}
|
|
506
519
|
const body = JSON.stringify(finalBody);
|
|
507
520
|
const releaseBodyObservation = translatorBudget.observeExternallyCapped(
|
|
508
521
|
"passthrough_serialization",
|
|
@@ -35,6 +35,11 @@ export function sanitizeReasoningInputContent(
|
|
|
35
35
|
preserveRawReasoningContent?: boolean;
|
|
36
36
|
dropNullContentChannel?: boolean;
|
|
37
37
|
stripEncryptedContent?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Remove `id` from every reasoning item, with or without a blob, because the ids name items in a
|
|
40
|
+
* store this destination cannot read; see `OcxParsedRequest._dropForeignReasoningItemIds`.
|
|
41
|
+
*/
|
|
42
|
+
dropForeignItemId?: boolean;
|
|
38
43
|
},
|
|
39
44
|
): unknown {
|
|
40
45
|
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
|
|
@@ -55,6 +60,10 @@ export function sanitizeReasoningInputContent(
|
|
|
55
60
|
const missingSummary = !Object.prototype.hasOwnProperty.call(rec, "summary");
|
|
56
61
|
const stripEncryptedContent = hasOcxEnvelope
|
|
57
62
|
|| (opts?.stripEncryptedContent === true && hasEncryptedContent);
|
|
63
|
+
// An id-only item is as foreign as one with a blob: a stateful destination still resolves it
|
|
64
|
+
// against its own store.
|
|
65
|
+
const dropItemId = opts?.dropForeignItemId === true
|
|
66
|
+
&& Object.prototype.hasOwnProperty.call(rec, "id");
|
|
58
67
|
// Codex serializes an absent reasoning content channel as `"content": null`. The field is
|
|
59
68
|
// optional and null carries nothing, but a strict gateway rejects the item on its declared type
|
|
60
69
|
// — xAI answers `Could not decode the compaction blob`, naming the sibling `encrypted_content`
|
|
@@ -76,7 +85,10 @@ export function sanitizeReasoningInputContent(
|
|
|
76
85
|
const blankContent = !dropNullContentChannel
|
|
77
86
|
&& !opts?.preserveRawReasoningContent
|
|
78
87
|
&& (hasRawContent || hasOcxEnvelope);
|
|
79
|
-
if (
|
|
88
|
+
if (
|
|
89
|
+
!blankContent && !stripOutputStatus && !stripEncryptedContent && !dropNullContentChannel
|
|
90
|
+
&& !missingSummary && !dropItemId
|
|
91
|
+
) {
|
|
80
92
|
return item;
|
|
81
93
|
}
|
|
82
94
|
changed = true;
|
|
@@ -85,6 +97,7 @@ export function sanitizeReasoningInputContent(
|
|
|
85
97
|
if (dropNullContentChannel) delete next.content;
|
|
86
98
|
if (stripOutputStatus) delete next.status;
|
|
87
99
|
if (stripEncryptedContent) delete next.encrypted_content;
|
|
100
|
+
if (dropItemId) delete next.id;
|
|
88
101
|
// Routed models can produce raw `reasoning_text` output items. Codex echoes those in later
|
|
89
102
|
// native GPT requests, but ChatGPT's Responses backend accepts reasoning input only with empty
|
|
90
103
|
// `content`; keep summaries/ids and drop the raw content so native passthrough does not 400.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "../../responses/compaction";
|
|
3
|
+
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
2
4
|
import { isPlainObject } from "./internal";
|
|
3
5
|
import { activateDeferredTool } from "./tool-schema";
|
|
4
6
|
import { stripOpenAiOnlyWebSearchFields } from "./web-search";
|
|
@@ -169,13 +171,37 @@ export function stripCanonicalOnlyTopLevelFields(body: unknown): unknown {
|
|
|
169
171
|
* exist, producing a 404. Strip all item IDs in this case — `call_id` pairing is unaffected.
|
|
170
172
|
* Matches codex-rs behavior (core/src/client.rs:918-925).
|
|
171
173
|
*/
|
|
172
|
-
export function stripItemIdsWhenUnstored(body: unknown): unknown {
|
|
173
|
-
|
|
174
|
+
export function stripItemIdsWhenUnstored(body: unknown, requireCustomCallIds = false): unknown {
|
|
175
|
+
const repairCustomCallIds = requireCustomCallIds === true;
|
|
176
|
+
if (!isPlainObject(body) || (body.store !== false && !repairCustomCallIds)) return body;
|
|
174
177
|
if (!Array.isArray(body.input)) return body;
|
|
175
178
|
|
|
176
179
|
let changed = false;
|
|
177
180
|
const input = body.input.map(item => {
|
|
178
|
-
if (!isPlainObject(item)
|
|
181
|
+
if (!isPlainObject(item)) return item;
|
|
182
|
+
if (repairCustomCallIds && item.type === "custom_tool_call") {
|
|
183
|
+
try {
|
|
184
|
+
if (typeof item.id === "string" && item.id.startsWith("ctc_")) return item;
|
|
185
|
+
if (
|
|
186
|
+
typeof item.call_id !== "string"
|
|
187
|
+
|| typeof item.name !== "string"
|
|
188
|
+
|| typeof item.input !== "string"
|
|
189
|
+
) return item;
|
|
190
|
+
const digest = createHash("sha256")
|
|
191
|
+
.update(JSON.stringify([item.call_id, item.name, item.input]))
|
|
192
|
+
.digest("hex")
|
|
193
|
+
.slice(0, 40);
|
|
194
|
+
changed = true;
|
|
195
|
+
debugProviderDiagnostic("openai-responses", "xai-custom-tool-call-id-repaired", {
|
|
196
|
+
hadId: typeof item.id === "string",
|
|
197
|
+
});
|
|
198
|
+
return { ...item, id: `ctc_${digest}` };
|
|
199
|
+
} catch {
|
|
200
|
+
debugProviderDiagnostic("openai-responses", "xai-custom-tool-call-id-unrepaired", {});
|
|
201
|
+
return item;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (body.store !== false || !("id" in item)) return item;
|
|
179
205
|
changed = true;
|
|
180
206
|
const next = { ...item };
|
|
181
207
|
delete next.id;
|
|
@@ -194,7 +194,8 @@ export function repairUnidentifiedToolOutputItems(body: unknown): unknown {
|
|
|
194
194
|
* reasoning-bearing assistant turn (#1477). Gated on
|
|
195
195
|
* `synthesizeMissingCallOutputs` (stateless AND non-forward wires); forward replay keeps
|
|
196
196
|
* fail-closed behavior.
|
|
197
|
-
* - `function_call_output`/`custom_tool_call_output` without their paired call item
|
|
197
|
+
* - `function_call_output`/`custom_tool_call_output` without their paired call item, when
|
|
198
|
+
* `repairOrphanOutputs` is enabled
|
|
198
199
|
* ("No tool call found for function call output with call_id ..."). Converted to user
|
|
199
200
|
* messages so the result text survives. `function_call_output` also pairs with
|
|
200
201
|
* `local_shell_call` (codex-rs emits shell outputs as function_call_output).
|
|
@@ -342,7 +343,12 @@ export function restoreBridgedWebSearchCalls(body: unknown, destinationScope: st
|
|
|
342
343
|
return changed ? { ...body, input: restored } : body;
|
|
343
344
|
}
|
|
344
345
|
|
|
345
|
-
export function repairOrphanedInputItems(
|
|
346
|
+
export function repairOrphanedInputItems(
|
|
347
|
+
body: unknown,
|
|
348
|
+
dropReasoning: boolean,
|
|
349
|
+
synthesizeMissingCallOutputs = false,
|
|
350
|
+
repairOrphanOutputs = true,
|
|
351
|
+
): unknown {
|
|
346
352
|
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
347
353
|
const input = body.input;
|
|
348
354
|
|
|
@@ -381,7 +387,7 @@ export function repairOrphanedInputItems(body: unknown, dropReasoning: boolean,
|
|
|
381
387
|
// incomplete. With no call id and no output, preserve the invalid item so validation fails
|
|
382
388
|
// closed rather than pretending any tool result exists.
|
|
383
389
|
const knownNullOutput = callId.length > 0 && item.output == null;
|
|
384
|
-
if (!paired && (knownNullOutput || usableOutput)) {
|
|
390
|
+
if (repairOrphanOutputs && !paired && (knownNullOutput || usableOutput)) {
|
|
385
391
|
changed = true;
|
|
386
392
|
repaired.push({
|
|
387
393
|
type: "message",
|
|
@@ -8,8 +8,10 @@ import { QoderScaffoldFilter, QODER_SCAFFOLD_ERROR_CODE, qoderScaffoldErrorMessa
|
|
|
8
8
|
|
|
9
9
|
export type QoderAdapterDeps = CodingAgentDeps;
|
|
10
10
|
|
|
11
|
-
export function buildQoderChildEnv(profile: QoderProfile, apiKey: string): Record<string, string> {
|
|
12
|
-
|
|
11
|
+
export function buildQoderChildEnv(profile: QoderProfile, apiKey: string, systemPrompt?: string): Record<string, string> {
|
|
12
|
+
const promptEnv = profile.region === "cn" ? "QODERCN_APPEND_SYSTEM_PROMPT" : "QODER_APPEND_SYSTEM_PROMPT";
|
|
13
|
+
return { ...baseScopedEnv(), NO_COLOR: "1", [profile.tokenEnv]: apiKey,
|
|
14
|
+
...(systemPrompt ? { [promptEnv]: systemPrompt } : {}) };
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/** Single-shot, tools-disabled Qoder CLI invocation; Codex remains the tool owner. */
|
|
@@ -27,8 +29,6 @@ export function buildQoderArgs(parsed: OcxParsedRequest, provider: OcxProviderCo
|
|
|
27
29
|
];
|
|
28
30
|
const effort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
29
31
|
if (effort) args.push("--reasoning-effort", effort);
|
|
30
|
-
const system = buildSystemPrompt(parsed);
|
|
31
|
-
if (system) args.push("--append-system-prompt", system);
|
|
32
32
|
return args;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -123,15 +123,18 @@ export function createQoderAdapter(provider: OcxProviderConfig, deps: QoderAdapt
|
|
|
123
123
|
});
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
+
// Qoder documents QODER_APPEND_SYSTEM_PROMPT for scoped child configuration.
|
|
127
|
+
// Keep the folded prompt out of argv and never inherit an ambient vendor prompt.
|
|
128
|
+
const system = buildSystemPrompt(parsed);
|
|
126
129
|
await runCodingAgentTurn({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
profiles: QODER_PROFILES,
|
|
131
|
+
provider,
|
|
132
|
+
parsed,
|
|
133
|
+
incoming,
|
|
134
|
+
emit: guardQoderScaffolding(emit),
|
|
135
|
+
buildArgs: (_profile, req, prov) => buildQoderArgs(req, prov),
|
|
136
|
+
buildEnv: (profile, apiKey) => buildQoderChildEnv(profile as QoderProfile, apiKey, system),
|
|
137
|
+
deps,
|
|
135
138
|
});
|
|
136
139
|
},
|
|
137
140
|
};
|
|
@@ -54,11 +54,44 @@ const MAX_MARKER_LENGTH = Math.max(...ALL_MARKERS.map(marker => marker.length));
|
|
|
54
54
|
* refuse the turn. A stem running to the end of the buffer still counts: more text may be
|
|
55
55
|
* arriving, and reading it as prose is the one reading that could release the block body.
|
|
56
56
|
*/
|
|
57
|
-
function reminderOpensHere(
|
|
58
|
-
const after =
|
|
57
|
+
function reminderOpensHere(text: string, at: number): boolean {
|
|
58
|
+
const after = text[at + REMINDER_OPEN.length];
|
|
59
59
|
return after === undefined || /[\s/>]/.test(after);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Fold one UTF-16 code unit the way `toLowerCase()` does, when that yields one code unit.
|
|
64
|
+
*
|
|
65
|
+
* This keeps every match the lowercased scan used to make. U+212A KELVIN SIGN lowercases to an
|
|
66
|
+
* ASCII `k`, so `<invo\u212Ae>` was treated as tool markup; an ASCII-only fold would release it.
|
|
67
|
+
* A character whose lowercase form is longer (such as U+0130) is left as-is.
|
|
68
|
+
*/
|
|
69
|
+
function foldCodeUnit(code: number): number {
|
|
70
|
+
if (code >= 65 && code <= 90) return code + 32;
|
|
71
|
+
if (code < 128) return code;
|
|
72
|
+
const lowered = String.fromCharCode(code).toLowerCase();
|
|
73
|
+
return lowered.length === 1 ? lowered.charCodeAt(0) : code;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find a lowercase ASCII marker without transforming `text`.
|
|
78
|
+
*
|
|
79
|
+
* Marker offsets must remain offsets into the original string. Unicode lowercasing can expand
|
|
80
|
+
* one code unit into several (for example, `İ` becomes `i` plus a combining dot), so an index
|
|
81
|
+
* obtained from `text.toLowerCase()` is unsafe to reuse with `text.slice()`. Folding one code
|
|
82
|
+
* unit at a time keeps the offsets and the matches.
|
|
83
|
+
*/
|
|
84
|
+
function indexOfMarker(text: string, marker: string, from = 0): number {
|
|
85
|
+
const last = text.length - marker.length;
|
|
86
|
+
outer: for (let at = Math.max(0, from); at <= last; at++) {
|
|
87
|
+
for (let offset = 0; offset < marker.length; offset++) {
|
|
88
|
+
if (foldCodeUnit(text.charCodeAt(at + offset)) !== marker.charCodeAt(offset)) continue outer;
|
|
89
|
+
}
|
|
90
|
+
return at;
|
|
91
|
+
}
|
|
92
|
+
return -1;
|
|
93
|
+
}
|
|
94
|
+
|
|
62
95
|
/**
|
|
63
96
|
* Ceiling on a suppressed block before it is treated as unterminated.
|
|
64
97
|
*
|
|
@@ -80,9 +113,10 @@ export interface ScaffoldFilterResult {
|
|
|
80
113
|
function heldSuffixLength(text: string): number {
|
|
81
114
|
const limit = Math.min(MAX_MARKER_LENGTH - 1, text.length);
|
|
82
115
|
for (let length = limit; length > 0; length--) {
|
|
83
|
-
const suffix = text.slice(text.length - length).toLowerCase();
|
|
84
116
|
for (const marker of ALL_MARKERS) {
|
|
85
|
-
if (marker.length > length && marker.
|
|
117
|
+
if (marker.length > length && indexOfMarker(text, marker.slice(0, length), text.length - length) >= 0) {
|
|
118
|
+
return length;
|
|
119
|
+
}
|
|
86
120
|
}
|
|
87
121
|
}
|
|
88
122
|
return 0;
|
|
@@ -114,7 +148,6 @@ export class QoderScaffoldFilter {
|
|
|
114
148
|
for (;;) {
|
|
115
149
|
if (this.mode === "suppress") {
|
|
116
150
|
const scan = this.suppressedTail + buffer;
|
|
117
|
-
const scanned = scan.toLowerCase();
|
|
118
151
|
// Unwind nesting rather than ending at the first closer. A reminder containing another
|
|
119
152
|
// reminder would otherwise hand the outer block's remaining body — the MCP server list
|
|
120
153
|
// in the reported leak — to the client as the model's answer, with a successful
|
|
@@ -122,11 +155,11 @@ export class QoderScaffoldFilter {
|
|
|
122
155
|
let cursor = 0;
|
|
123
156
|
let close = -1;
|
|
124
157
|
for (;;) {
|
|
125
|
-
const nextClose =
|
|
158
|
+
const nextClose = indexOfMarker(scan, REMINDER_CLOSE, cursor);
|
|
126
159
|
if (nextClose < 0) break;
|
|
127
|
-
let nextOpen =
|
|
128
|
-
while (nextOpen >= 0 && !reminderOpensHere(
|
|
129
|
-
nextOpen =
|
|
160
|
+
let nextOpen = indexOfMarker(scan, REMINDER_OPEN, cursor);
|
|
161
|
+
while (nextOpen >= 0 && !reminderOpensHere(scan, nextOpen)) {
|
|
162
|
+
nextOpen = indexOfMarker(scan, REMINDER_OPEN, nextOpen + 1);
|
|
130
163
|
}
|
|
131
164
|
if (nextOpen >= 0 && nextOpen < nextClose) {
|
|
132
165
|
this.suppressDepth += 1;
|
|
@@ -159,11 +192,10 @@ export class QoderScaffoldFilter {
|
|
|
159
192
|
|
|
160
193
|
let earliest = -1;
|
|
161
194
|
let found = "";
|
|
162
|
-
const lowered = buffer.toLowerCase();
|
|
163
195
|
for (const marker of ALL_MARKERS) {
|
|
164
|
-
let at =
|
|
165
|
-
while (at >= 0 && marker === REMINDER_OPEN && !reminderOpensHere(
|
|
166
|
-
at =
|
|
196
|
+
let at = indexOfMarker(buffer, marker);
|
|
197
|
+
while (at >= 0 && marker === REMINDER_OPEN && !reminderOpensHere(buffer, at)) {
|
|
198
|
+
at = indexOfMarker(buffer, marker, at + 1);
|
|
167
199
|
}
|
|
168
200
|
if (at < 0) continue;
|
|
169
201
|
// A closer sitting exactly where an opener starts cannot happen, so ties are impossible.
|
package/src/bridge/internal.ts
CHANGED
|
@@ -114,6 +114,26 @@ export function toolCallArgumentsUsable(args: string): boolean {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Whether an in-progress function-call argument buffer could still become valid JSON.
|
|
119
|
+
* The first non-whitespace byte must be one that can begin a JSON value. A stream that
|
|
120
|
+
* already lost its leading `{"` (observed from coding-agent CLIs as `code":"…}`) can only
|
|
121
|
+
* fail `toolCallArgumentsUsable` at completion, so streaming those fragments publishes
|
|
122
|
+
* bytes a failed item cannot take back — the same #765 rule that refuses completion.
|
|
123
|
+
*/
|
|
124
|
+
export function toolCallArgumentsCouldBeJson(args: string): boolean {
|
|
125
|
+
const first = args.trimStart().charAt(0);
|
|
126
|
+
if (first === "") return true;
|
|
127
|
+
return first === "{"
|
|
128
|
+
|| first === "["
|
|
129
|
+
|| first === "\""
|
|
130
|
+
|| first === "-"
|
|
131
|
+
|| (first >= "0" && first <= "9")
|
|
132
|
+
|| first === "t"
|
|
133
|
+
|| first === "f"
|
|
134
|
+
|| first === "n";
|
|
135
|
+
}
|
|
136
|
+
|
|
117
137
|
export function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
|
|
118
138
|
const message = redactSecretString(event.message);
|
|
119
139
|
if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
|
package/src/bridge/sse.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { progressiveFreeformInput } from "../responses/progressive-freeform-input";
|
|
25
25
|
import { encodeCompactionSummary } from "../responses/compaction";
|
|
26
26
|
import { compileCodeModeHelperInput, resolveCodeModeHelperName } from "../responses/code-mode-helper-compat";
|
|
27
|
+
import { mayBecomeCodeModeShellInput } from "../responses/code-mode-shell-input";
|
|
27
28
|
import { isTruncatedStopReason, truncationReasonFor } from "../responses/truncated-stop-reason";
|
|
28
29
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "../responses/reasoning-envelope";
|
|
29
30
|
import { rememberReasoningForCall } from "../responses/reasoning-replay-cache";
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
type TranslatorBudget,
|
|
48
49
|
type TranslatorBufferKind,
|
|
49
50
|
} from "../lib/translator-budget";
|
|
50
|
-
import { adapterFailureFromEvent, emptyChunks, joinChunks, ownedBudgetAbandonedMs, responsesUsage, toolCallArgumentsUsable, uuid, webSearchAction } from "./internal";
|
|
51
|
+
import { adapterFailureFromEvent, emptyChunks, joinChunks, ownedBudgetAbandonedMs, responsesUsage, toolCallArgumentsCouldBeJson, toolCallArgumentsUsable, uuid, webSearchAction } from "./internal";
|
|
51
52
|
import type { OutputItem, StringChunks } from "./internal";
|
|
52
53
|
|
|
53
54
|
function sseEvent(name: string, data: Record<string, unknown>): string {
|
|
@@ -1055,10 +1056,17 @@ export function bridgeToResponsesSSE(
|
|
|
1055
1056
|
currentToolCall.callId,
|
|
1056
1057
|
));
|
|
1057
1058
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1059
|
+
// Hold fragments whose accumulated buffer can never parse as JSON. Fragments
|
|
1060
|
+
// already streamed are retained by the client as history even when the item
|
|
1061
|
+
// fails at completion (the poisoned-replay loop behind inbound "non-JSON
|
|
1062
|
+
// arguments" warnings); holding costs nothing for healthy streams because the
|
|
1063
|
+
// completed item still carries the full arguments.
|
|
1064
|
+
if (toolCallArgumentsCouldBeJson(currentToolCall.args)) {
|
|
1065
|
+
emit("response.function_call_arguments.delta", {
|
|
1066
|
+
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
1067
|
+
delta: event.arguments,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1062
1070
|
}
|
|
1063
1071
|
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
|
|
1064
1072
|
// `progressiveFreeformInput` holds while the buffer is still an ambiguous prefix
|
|
@@ -1089,6 +1097,7 @@ export function bridgeToResponsesSSE(
|
|
|
1089
1097
|
// replaced by the normalized ones.
|
|
1090
1098
|
const mayNormalize = ownsFreeformGrammar && currentToolCall.name === "apply_patch";
|
|
1091
1099
|
if (!((mayCompile || mayNormalize) && mayBecomePatchEnvelope(full))
|
|
1100
|
+
&& !(mayCompile && mayBecomeCodeModeShellInput(currentToolCall.args, full))
|
|
1092
1101
|
&& full.startsWith(emitted) && full.length > emitted.length) {
|
|
1093
1102
|
emit("response.custom_tool_call_input.delta", {
|
|
1094
1103
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|