@bitkyc08/opencodex 2.82.0 → 2.84.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.
- package/gui/dist/assets/App-CZtqiGcw.js +56 -0
- package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-BbEeRfPY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +47 -15
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +5 -0
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +16 -14
- package/src/bridge/errors.ts +26 -1
- package/src/bridge.ts +1 -1
- package/src/claude/context-windows.ts +57 -4
- package/src/claude/desktop-picker.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +44 -12
- package/src/cli/account.ts +41 -5
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-base.ts +13 -1
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/claude.ts +15 -6
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +16 -3
- package/src/cli/export-command.ts +14 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +13 -4
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +5 -4
- package/src/cli/resolve.ts +1 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +47 -17
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/autostart-health.ts +3 -1
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/effort.ts +2 -2
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/native-models.ts +17 -3
- package/src/codex/catalog/provider-models.ts +25 -0
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/main-account.ts +6 -7
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-overlay.ts +8 -4
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +16 -0
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +43 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +2 -1
- package/src/config/paths.ts +3 -0
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +2 -0
- package/src/generated/compatibility-version.json +373 -217
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +293 -57
- package/src/grok/reset-coupons.ts +15 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/integrations/mutation-plan.ts +13 -7
- package/src/integrations/registry.ts +45 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +14 -6
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/errors.ts +25 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/shadow-call.ts +44 -5
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +25 -16
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro.ts +48 -5
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +3 -1
- package/src/plugins/loader.ts +11 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +29 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +20 -20
- package/src/providers/registry/entries-extended.ts +4 -1
- package/src/providers/registry/model-seeds.ts +20 -3
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +7 -1
- package/src/server/auth-cors.ts +42 -0
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +21 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +33 -5
- package/src/server/management/agent-settings-routes.ts +28 -10
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/grok-coupon-routes.ts +333 -57
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +92 -390
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +7 -5
- package/src/server/management/shared.ts +3 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native.ts +68 -42
- package/src/server/relay-eager.ts +4 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +243 -0
- package/src/server/request-log.ts +15 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +293 -81
- package/src/server/responses/code-mode-collab-surface.ts +205 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +129 -50
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +14 -0
- package/src/server/responses/core-combo.ts +7 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +4 -1
- package/src/server/responses/passthrough-dispatch.ts +130 -64
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +6 -4
- package/src/server/responses/request-transport.ts +131 -13
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +16 -2
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/startup-action-control.ts +22 -2
- package/src/server/startup-health-cache.ts +4 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +147 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +447 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +54 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/types/config.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +5 -1
- package/src/usage/log.ts +19 -1
- package/gui/dist/assets/App-Ds_qL7o8.js +0 -54
- package/gui/dist/assets/Tray-BqHF5Trd.js +0 -1
- package/gui/dist/assets/index-D3FBCNiP.js +0 -94
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V1 collaboration evidence nested inside Codex's code-mode `exec` tool (#6844).
|
|
3
|
+
*
|
|
4
|
+
* In code-mode-only turns codex-rs does not put the collaboration tools on the wire. When they are
|
|
5
|
+
* ENABLED it renders each one into the custom `exec` description instead
|
|
6
|
+
* (code-mode-protocol/src/description.rs, build_exec_tool_description): a top-level heading line,
|
|
7
|
+
* the tool's own description, then an "exec tool declaration:" ts fence holding
|
|
8
|
+
* "declare const tools: { <name>(args: ...): Promise<...>; };". A namespaced tool is flattened to
|
|
9
|
+
* <namespace>__<name> (tools/src/code_mode.rs, code_mode_name_for_tool_name), and V1 always
|
|
10
|
+
* registers under the fixed namespace multi_agent_v1, so a rendered V1 surface reads
|
|
11
|
+
* multi_agent_v1__spawn_agent.
|
|
12
|
+
*
|
|
13
|
+
* That rendering is the only proof of V1 inside exec itself. When the model supports tool search,
|
|
14
|
+
* codex-rs registers V1 as DEFERRED and leaves it out of the description entirely, and every
|
|
15
|
+
* OpenCodex catalog row advertises tool search. Such a turn carries no V1 evidence here; a bare
|
|
16
|
+
* exec, the ALL_TOOLS mention, and the saved OpenCodex mode prove nothing about the conversation
|
|
17
|
+
* (codex-rs pins the version per session). The deferred case has its own, separate evidence source
|
|
18
|
+
* in deferred-collab-surface.ts, which uses this file's genuine-exec check as a host guard.
|
|
19
|
+
*
|
|
20
|
+
* A tool description is copied into exec verbatim, so one that reproduces a complete top-level V1
|
|
21
|
+
* section is indistinguishable from the host's own rendering. This parser cannot authenticate the
|
|
22
|
+
* host; it only refuses everything short of that exact shape.
|
|
23
|
+
*/
|
|
24
|
+
import { CODE_MODE_EXEC_TOOL_NAME, declaresCodeModeExec, type OcxTool } from "../../types";
|
|
25
|
+
|
|
26
|
+
export type CodeModeV1Evidence = "v1" | "ambiguous" | null;
|
|
27
|
+
|
|
28
|
+
/** What a request's code-mode exec proves about collaboration, before any combination policy. */
|
|
29
|
+
export interface CodeModeExecAnalysis {
|
|
30
|
+
/** A codex-rs code-mode exec is declared: bare, freeform, template-led, no bare shell bridge. */
|
|
31
|
+
genuineExec: boolean;
|
|
32
|
+
/** That exec renders a complete multi_agent_v1__spawn_agent section. */
|
|
33
|
+
renderedV1: boolean;
|
|
34
|
+
/** That exec mentions a spawn_agent or V2 companion that is not V1's, quoted or not. */
|
|
35
|
+
foreignCollabClaim: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** First line of codex-rs EXEC_DESCRIPTION_TEMPLATE; an overridden or foreign exec fails closed. */
|
|
39
|
+
const CODE_MODE_EXEC_TEMPLATE_HEADER = "Run JavaScript code to orchestrate/compose tool calls";
|
|
40
|
+
const MULTI_AGENT_V1_SPAWN = "multi_agent_v1__spawn_agent";
|
|
41
|
+
const SPAWN = "spawn_agent";
|
|
42
|
+
const V2_COMPANIONS = ["send_message", "followup_task", "interrupt_agent", "list_agents"] as const;
|
|
43
|
+
const DECLARATION_LEAD = "exec tool declaration:";
|
|
44
|
+
|
|
45
|
+
const TOOL_HEADING = /^### `([A-Za-z_$][\w$]*)`(?: \(`([^`]+)`\))?$/;
|
|
46
|
+
const DECLARATION_FENCE = "```ts";
|
|
47
|
+
// CommonMark fences: up to three spaces of indentation, three or more backticks or tildes. A fence
|
|
48
|
+
// closes only on the same character, at least as long, with nothing but whitespace after it.
|
|
49
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
50
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
51
|
+
|
|
52
|
+
interface Fence { char: string; length: number }
|
|
53
|
+
|
|
54
|
+
function openedFence(line: string): Fence | undefined {
|
|
55
|
+
const match = FENCE_OPEN.exec(line);
|
|
56
|
+
if (!match) return undefined;
|
|
57
|
+
const run = match[1]!;
|
|
58
|
+
// A backtick fence's info string may not contain a backtick (that line is inline code).
|
|
59
|
+
if (run[0] === "`" && match[2]!.includes("`")) return undefined;
|
|
60
|
+
return { char: run[0]!, length: run.length };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function closesFence(line: string, fence: Fence): boolean {
|
|
64
|
+
const match = FENCE_CLOSE.exec(line);
|
|
65
|
+
return match !== null && match[1]![0] === fence.char && match[1]!.length >= fence.length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isCodeModeExec(tool: OcxTool): boolean {
|
|
69
|
+
return tool.name === CODE_MODE_EXEC_TOOL_NAME && !tool.namespace && tool.freeform === true
|
|
70
|
+
&& typeof tool.description === "string"
|
|
71
|
+
&& tool.description.startsWith(CODE_MODE_EXEC_TEMPLATE_HEADER);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A nested tool rendered as a heading plus its own declaration; `raw` is the pre-normalization name. */
|
|
75
|
+
interface RenderedTool { name: string; raw: string }
|
|
76
|
+
|
|
77
|
+
/** The declaration fence codex-rs opens: three backticks and "ts". */
|
|
78
|
+
const DECLARATION_FENCE_SHAPE: Fence = { char: "`", length: 3 };
|
|
79
|
+
|
|
80
|
+
/** A complete declaration as render_code_mode_sample emits it, single- or multi-line. */
|
|
81
|
+
function isCompleteDeclaration(body: readonly string[], name: string): boolean {
|
|
82
|
+
const text = body.join("\n");
|
|
83
|
+
const prefix = "declare const tools: { " + name + "(";
|
|
84
|
+
if (!text.startsWith(prefix)) return false;
|
|
85
|
+
// A named, typed parameter (`args` for functions, `input` for freeform) and a non-empty return type.
|
|
86
|
+
return /^(?:args|input): \S[\s\S]*\): Promise<[^>\s][\s\S]*>; \};$/.test(text.slice(prefix.length));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** codex-rs normalize_code_mode_identifier: invalid identifier characters become `_`. */
|
|
90
|
+
function normalizeCodeModeIdentifier(raw: string): string {
|
|
91
|
+
let identifier = "";
|
|
92
|
+
for (const [index, ch] of [...raw].entries()) {
|
|
93
|
+
const valid = index === 0 ? /[A-Za-z_$]/.test(ch) : /[A-Za-z0-9_$]/.test(ch);
|
|
94
|
+
identifier += valid ? ch : "_";
|
|
95
|
+
}
|
|
96
|
+
return identifier === "" ? "_" : identifier;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A heading as render_tool_heading emits it: a raw suffix only when normalization changed the name. */
|
|
100
|
+
function renderedHeading(match: RegExpExecArray): RenderedTool | undefined {
|
|
101
|
+
const name = match[1]!;
|
|
102
|
+
const raw = match[2];
|
|
103
|
+
if (raw === undefined) return { name, raw: name };
|
|
104
|
+
return raw !== name && normalizeCodeModeIdentifier(raw) === name ? { name, raw } : undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Nested tools whose top-level heading is followed by the complete declaration codex-rs renders for
|
|
109
|
+
* the same name. A section ends at its own declaration, not at the next heading, because tool and
|
|
110
|
+
* namespace descriptions (V1 spawn's guidance, a configured usage hint) carry their own Markdown,
|
|
111
|
+
* including example headings and example declarations. Pending headings form a stack: a
|
|
112
|
+
* declaration is owned by the outermost pending heading with its name, which is removed together
|
|
113
|
+
* with anything opened after it; a declaration nobody owns is an example and leaves the stack
|
|
114
|
+
* alone. Lines inside a quoted fence can neither open a section nor declare a tool.
|
|
115
|
+
*/
|
|
116
|
+
function renderedNestedTools(description: string): RenderedTool[] {
|
|
117
|
+
const lines = description.split("\n");
|
|
118
|
+
const rendered: RenderedTool[] = [];
|
|
119
|
+
const pending: RenderedTool[] = [];
|
|
120
|
+
let fence: Fence | undefined;
|
|
121
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
122
|
+
const line = lines[i]!;
|
|
123
|
+
if (fence) {
|
|
124
|
+
if (closesFence(line, fence)) fence = undefined;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const match = TOOL_HEADING.exec(line);
|
|
128
|
+
if (match) {
|
|
129
|
+
const heading = renderedHeading(match);
|
|
130
|
+
if (heading) pending.push(heading);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (line === DECLARATION_LEAD && lines[i + 1] === DECLARATION_FENCE) {
|
|
134
|
+
let close = i + 2;
|
|
135
|
+
while (close < lines.length && !closesFence(lines[close]!, DECLARATION_FENCE_SHAPE)) close += 1;
|
|
136
|
+
if (close >= lines.length) return rendered; // unterminated: nothing after it is structure
|
|
137
|
+
const body = lines.slice(i + 2, close);
|
|
138
|
+
// The outermost pending heading with the declared name owns it, so a same-name heading quoted
|
|
139
|
+
// inside the tool's own description cannot take over the genuine section.
|
|
140
|
+
const owner = pending.findIndex(tool => isCompleteDeclaration(body, tool.name));
|
|
141
|
+
if (owner >= 0) {
|
|
142
|
+
rendered.push(pending[owner]!);
|
|
143
|
+
pending.length = owner;
|
|
144
|
+
}
|
|
145
|
+
i = close;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
fence = openedFence(line);
|
|
149
|
+
}
|
|
150
|
+
return rendered;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A nested name that claims a collaboration surface other than V1's. */
|
|
154
|
+
function isForeignCollabName(name: string): boolean {
|
|
155
|
+
if (name.startsWith("mcp__")) return false; // MCP server tools are never native collaboration
|
|
156
|
+
if (name === MULTI_AGENT_V1_SPAWN) return false;
|
|
157
|
+
if (name === SPAWN || name.endsWith("__" + SPAWN)) return true;
|
|
158
|
+
return V2_COMPANIONS.some(companion => name === companion || name.endsWith("__" + companion));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Every name a heading or declaration line mentions, quoted or not. */
|
|
162
|
+
function mentionedToolNames(description: string): string[] {
|
|
163
|
+
const names: string[] = [];
|
|
164
|
+
for (const line of description.split("\n")) {
|
|
165
|
+
const heading = TOOL_HEADING.exec(line);
|
|
166
|
+
if (heading) {
|
|
167
|
+
names.push(heading[1]!);
|
|
168
|
+
if (heading[2] !== undefined) names.push(heading[2]);
|
|
169
|
+
}
|
|
170
|
+
for (const match of line.matchAll(/declare const tools: \{ ([A-Za-z_$][\w$]*)\(/g)) names.push(match[1]!);
|
|
171
|
+
}
|
|
172
|
+
return names;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Reads every genuine code-mode exec once. The two collaboration halves are deliberately
|
|
177
|
+
* asymmetric. V1 evidence needs a rendered section (a heading followed by its complete
|
|
178
|
+
* declaration). The contradiction check reads every heading and declaration line, inside fences or
|
|
179
|
+
* not and whoever "owns" it, so text copied into the listing can only push a turn toward null; it
|
|
180
|
+
* cannot hide another surface. The contradiction is reported even without rendered V1, because
|
|
181
|
+
* deferred V1 evidence must also yield to a rendered foreign surface.
|
|
182
|
+
*/
|
|
183
|
+
export function analyzeCodeModeExec(tools: readonly OcxTool[] | undefined): CodeModeExecAnalysis {
|
|
184
|
+
const analysis: CodeModeExecAnalysis = { genuineExec: false, renderedV1: false, foreignCollabClaim: false };
|
|
185
|
+
if (!tools || tools.length === 0) return analysis;
|
|
186
|
+
if (!declaresCodeModeExec(new Set(tools.filter(tool => !tool.namespace).map(tool => tool.name)))) return analysis;
|
|
187
|
+
for (const tool of tools) {
|
|
188
|
+
if (!isCodeModeExec(tool)) continue;
|
|
189
|
+
analysis.genuineExec = true;
|
|
190
|
+
// A rendered V1 spawn heading carries no raw-name suffix: the identifier needs no normalization.
|
|
191
|
+
if (renderedNestedTools(tool.description).some(rendered => rendered.raw === MULTI_AGENT_V1_SPAWN)) analysis.renderedV1 = true;
|
|
192
|
+
if (mentionedToolNames(tool.description).some(isForeignCollabName)) analysis.foreignCollabClaim = true;
|
|
193
|
+
}
|
|
194
|
+
return analysis;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* "v1" when a genuine code-mode exec renders the V1 spawn tool and nothing that claims another
|
|
199
|
+
* collaboration surface; "ambiguous" when it renders both; null when it proves nothing.
|
|
200
|
+
*/
|
|
201
|
+
export function codeModeV1Evidence(tools: readonly OcxTool[] | undefined): CodeModeV1Evidence {
|
|
202
|
+
const analysis = analyzeCodeModeExec(tools);
|
|
203
|
+
if (!analysis.renderedV1) return null;
|
|
204
|
+
return analysis.foreignCollabClaim ? "ambiguous" : "v1";
|
|
205
|
+
}
|
|
@@ -11,7 +11,9 @@ import type { CodexWsSession } from "./codex-ws-session";
|
|
|
11
11
|
import { UPGRADE_DEADLINE_MS, CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES,
|
|
12
12
|
MAX_CODEX_WS_QUEUE_BYTES, markCodexWsResponse, markCodexWsPreludeProjection, normalizeResponsesWsRelayEvent, closedBeforeTerminalMessage,
|
|
13
13
|
codexWsCreateFrameExceedsLimit, codexWsFailureDetail, codexWsPreResponseFailure, markCodexWsStage, codexWsOcxVersion,
|
|
14
|
-
markCodexWsSocketDeath, type CodexWsFailureStage, type CodexWsStageRecord
|
|
14
|
+
markCodexWsSocketDeath, codexWsWrapperStatus, isCodexWsTerminalEvent, type CodexWsFailureStage, type CodexWsStageRecord,
|
|
15
|
+
type CodexWsTerminalEvent, codexWsPreOutputRefusalStatus, isEmptyCodexWsLifecycleEvent, codexWsFrameResponseIds,
|
|
16
|
+
markCodexWsPreOutputRefusal, type CodexWsPreOutputRefusalHold } from "./codex-ws-wire";
|
|
15
17
|
|
|
16
18
|
interface ExchangeOptions {
|
|
17
19
|
nativeControl?: NativeResponseControl;
|
|
@@ -26,6 +28,13 @@ interface ExchangeOptions {
|
|
|
26
28
|
onPhysicalDispatch?: () => void;
|
|
27
29
|
/** Bun version string the caller gated on; stamped onto the stage record. */
|
|
28
30
|
bunVersion?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Opt-in (#6969): how long empty lifecycle frames may be held so an authoritative upstream
|
|
33
|
+
* refusal that follows them, before any output, can settle as a replayable gateway status instead
|
|
34
|
+
* of a committed 200. Absent or zero keeps the exchange exactly as it was. Ignored on native
|
|
35
|
+
* control exchanges and on transports without the metadata channel.
|
|
36
|
+
*/
|
|
37
|
+
preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
const HTTP_HEADER_TOKEN = /^[!#$%&'*+.^_`|~0-9a-z-]+$/i;
|
|
@@ -75,6 +84,8 @@ function rejectionHeaders(source: Record<string, unknown>, prelude: Headers): He
|
|
|
75
84
|
* responses_websocket.rs accepts status/status_code and scalar header values;
|
|
76
85
|
* unlike its native client, this relay converts only precommit 4xx. Returning a
|
|
77
86
|
* post-send 5xx or fetch rejection could cause the outer retry wrapper to resend.
|
|
87
|
+
* The one 5xx exception is the opt-in pre-output refusal below (#6969): a structured upstream
|
|
88
|
+
* refusal with nothing relayed but empty lifecycle frames, which the caller may replay.
|
|
78
89
|
*/
|
|
79
90
|
function wrappedRejectionResponse(payload: Record<string, unknown>, prelude: Headers): Response | null {
|
|
80
91
|
if (payload.type !== "error" || payload.stream_id !== undefined) return null;
|
|
@@ -92,9 +103,20 @@ function wrappedRejectionResponse(payload: Record<string, unknown>, prelude: Hea
|
|
|
92
103
|
}), { status, headers });
|
|
93
104
|
}
|
|
94
105
|
|
|
106
|
+
/** The client-facing error of a pre-output refusal projection, from the refusal's own error object. */
|
|
107
|
+
function refusalErrorBody(source: unknown): Record<string, string> {
|
|
108
|
+
const error = record(source) ? source : {};
|
|
109
|
+
return {
|
|
110
|
+
type: typeof error.type === "string" ? error.type : "server_error",
|
|
111
|
+
...(typeof error.code === "string" ? { code: error.code } : {}),
|
|
112
|
+
message: typeof error.message === "string" ? error.message : "Upstream declined the request before any output",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
95
116
|
/** The sole SSE exchange state machine for both one-shot and retained sockets. */
|
|
96
117
|
export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
97
|
-
const { session, url, init, prepared, sseFallback, onQuota, beforeDispatch, bunVersion, nativeControl, beforeContinuation
|
|
118
|
+
const { session, url, init, prepared, sseFallback, onQuota, beforeDispatch, bunVersion, nativeControl, beforeContinuation,
|
|
119
|
+
preOutputRefusalHold } = options;
|
|
98
120
|
const { frameText, headers } = prepared;
|
|
99
121
|
const signal = init.signal ?? undefined;
|
|
100
122
|
return new Promise<Response>((resolve, reject) => {
|
|
@@ -120,6 +142,9 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
120
142
|
// Numeric close code for the durable stage record; the reason string stays
|
|
121
143
|
// out of it on purpose (#4191 content-free contract).
|
|
122
144
|
let closeCode: number | null = null;
|
|
145
|
+
// #6740: which terminal frame the upstream itself sent, before any synthetic tail.
|
|
146
|
+
let terminalEvent: CodexWsTerminalEvent | null = null;
|
|
147
|
+
let terminalErrorStatus: number | null = null;
|
|
123
148
|
let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
124
149
|
const encoder = new TextEncoder();
|
|
125
150
|
const metadata = url === CODEX_RESPONSES_HTTP_URL ? new CodexWsMetadata(onQuota) : null;
|
|
@@ -135,6 +160,18 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
135
160
|
// The resolved 200, retained so a later body failure can replace its
|
|
136
161
|
// success-shaped stage record with the failure-shaped one (#4191).
|
|
137
162
|
let committedResponse: Response | null = null;
|
|
163
|
+
// #6969: the pre-output refusal hold. While it is open, empty lifecycle frames are queued on the
|
|
164
|
+
// not-yet-exposed stream without resolving it. Every settle other than an eligible refusal
|
|
165
|
+
// commits first (deferredCommit), so the outcome is exactly what the exchange produced before.
|
|
166
|
+
const holdEnabled = (preOutputRefusalHold?.maxMs ?? 0) > 0 && metadata !== null && !nativeControl;
|
|
167
|
+
// Drops for good on any identity the exchange cannot tie to one response: a stream id, two
|
|
168
|
+
// different ids in one frame, or an id that differs from the first one seen. Independent of
|
|
169
|
+
// socket reuse, so a cold one-shot socket is held to the same rule as a pooled one.
|
|
170
|
+
let refusalEligible = holdEnabled;
|
|
171
|
+
let refusalResponseId: string | null = null;
|
|
172
|
+
let heldHeaders: Headers | null = null;
|
|
173
|
+
let deferredCommit = false;
|
|
174
|
+
let holdTimer: ReturnType<typeof setTimeout> | undefined;
|
|
138
175
|
const stream = new ReadableStream<Uint8Array>({
|
|
139
176
|
start(c) { controller = c; },
|
|
140
177
|
cancel() {
|
|
@@ -149,6 +186,7 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
149
186
|
clearTimeout(upgradeTimer);
|
|
150
187
|
clearTimeout(silenceTimer);
|
|
151
188
|
clearTimeout(pingTimer);
|
|
189
|
+
clearTimeout(holdTimer);
|
|
152
190
|
signal?.removeEventListener("abort", onAbort);
|
|
153
191
|
metadata?.finish();
|
|
154
192
|
correlation?.finish();
|
|
@@ -200,14 +238,20 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
200
238
|
reused: session.reused,
|
|
201
239
|
ocxVersion: codexWsOcxVersion(),
|
|
202
240
|
bunVersion: bunVersion ?? "unknown",
|
|
241
|
+
terminalEvent,
|
|
242
|
+
terminalErrorStatus,
|
|
203
243
|
});
|
|
204
244
|
|
|
205
245
|
const commitResponse = () => {
|
|
206
246
|
if (responseCommitted) return;
|
|
207
247
|
responseCommitted = true;
|
|
248
|
+
deferredCommit = false;
|
|
249
|
+
clearTimeout(holdTimer);
|
|
208
250
|
clearTimeout(silenceTimer);
|
|
209
251
|
clearTimeout(pingTimer);
|
|
210
|
-
|
|
252
|
+
// A held preamble froze the prelude headers at the first Responses event, which is the moment
|
|
253
|
+
// they were frozen before the hold existed.
|
|
254
|
+
const responseHeaders = heldHeaders ? new Headers(heldHeaders) : metadata?.snapshot() ?? new Headers();
|
|
211
255
|
responseHeaders.set("content-type", "text/event-stream; charset=utf-8");
|
|
212
256
|
const response = new Response(stream, { status: 200, headers: responseHeaders });
|
|
213
257
|
metadata?.commit();
|
|
@@ -218,9 +262,73 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
218
262
|
resolve(response);
|
|
219
263
|
};
|
|
220
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Open the hold at the first empty lifecycle frame: freeze the prelude and bound the wait by
|
|
267
|
+
* both the hold's own maximum and its absolute end. False when no time is left, so the frame
|
|
268
|
+
* commits exactly as it would without the hold.
|
|
269
|
+
*/
|
|
270
|
+
const beginHold = (): boolean => {
|
|
271
|
+
if (heldHeaders) return true;
|
|
272
|
+
if (!metadata || !preOutputRefusalHold) return false;
|
|
273
|
+
const waitMs = Math.min(preOutputRefusalHold.maxMs, preOutputRefusalHold.until - Date.now());
|
|
274
|
+
if (waitMs <= 0) return false;
|
|
275
|
+
heldHeaders = metadata.snapshot();
|
|
276
|
+
metadata.commit();
|
|
277
|
+
holdTimer = setTimeout(() => {
|
|
278
|
+
if (!terminal && !responseCommitted) commitResponse();
|
|
279
|
+
}, waitMs);
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const noteRefusalIdentity = (payload: Record<string, unknown>) => {
|
|
284
|
+
const ids = codexWsFrameResponseIds(payload);
|
|
285
|
+
if (payload.stream_id !== undefined || ids === null || ids.length > 1) {
|
|
286
|
+
refusalEligible = false;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const id = ids[0];
|
|
290
|
+
if (id === undefined) return;
|
|
291
|
+
if (refusalResponseId === null) refusalResponseId = id;
|
|
292
|
+
else if (id !== refusalResponseId) refusalEligible = false;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Settle an authoritative pre-output refusal (#6969) as the gateway status the upstream stated,
|
|
297
|
+
* without the non-replayable marker: nothing but empty lifecycle frames was relayed, and the
|
|
298
|
+
* upstream ended that response itself. The caller's transient ladder and pool move may send the
|
|
299
|
+
* turn again; if neither does, the registered restore hands the client the original stream,
|
|
300
|
+
* which is already complete on `stream`.
|
|
301
|
+
*/
|
|
302
|
+
const resolveRefusal = (status: number, payload: Record<string, unknown>, headers: Headers, prelude: Headers) => {
|
|
303
|
+
responseCommitted = true;
|
|
304
|
+
deferredCommit = false;
|
|
305
|
+
clearTimeout(holdTimer);
|
|
306
|
+
const stage = stageRecord(null);
|
|
307
|
+
const source = payload.type === "error"
|
|
308
|
+
? payload.error
|
|
309
|
+
: record(payload.response) ? payload.response.error : undefined;
|
|
310
|
+
const projection = new Response(JSON.stringify({ error: refusalErrorBody(source) }), { status, headers });
|
|
311
|
+
markCodexWsPreludeProjection(projection);
|
|
312
|
+
markCodexWsStage(projection, stage);
|
|
313
|
+
const observed = Boolean(metadata && onQuota);
|
|
314
|
+
markCodexWsPreOutputRefusal(projection, () => {
|
|
315
|
+
const restoredHeaders = new Headers(prelude);
|
|
316
|
+
restoredHeaders.set("content-type", "text/event-stream; charset=utf-8");
|
|
317
|
+
const restored = new Response(stream, { status: 200, headers: restoredHeaders });
|
|
318
|
+
markCodexWsResponse(restored, observed);
|
|
319
|
+
// The same record object, so the attempt that adopted it keeps reading one stage.
|
|
320
|
+
markCodexWsStage(restored, stage);
|
|
321
|
+
return restored;
|
|
322
|
+
});
|
|
323
|
+
resolve(projection);
|
|
324
|
+
};
|
|
325
|
+
|
|
221
326
|
const failStream = (error: unknown, status: 502 | 504 = 502, { socketDied = false } = {}) => {
|
|
222
327
|
if (terminal) return;
|
|
223
328
|
terminal = true;
|
|
329
|
+
// A held preamble or refusal was a commit deferred, not avoided: settle as the committed
|
|
330
|
+
// body failure it would have been, never as a new pre-response gateway status.
|
|
331
|
+
if (deferredCommit) commitResponse();
|
|
224
332
|
if (sent && !responseCommitted && metadata) {
|
|
225
333
|
// Nothing has been promised to the client yet, so the honest answer is a gateway
|
|
226
334
|
// status, not a 200 whose body then fails. The frame may already be executing
|
|
@@ -305,7 +413,7 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
305
413
|
reject(reason);
|
|
306
414
|
return;
|
|
307
415
|
}
|
|
308
|
-
if (!responseCommitted && metadata) {
|
|
416
|
+
if (!responseCommitted && metadata && !deferredCommit) {
|
|
309
417
|
// Sent, unacknowledged. The proxy's own connect deadline is an origin-silence
|
|
310
418
|
// verdict and settles like one; a caller abort is the caller's decision, so the
|
|
311
419
|
// exchange rejects with that reason and disposing the socket cancels the turn.
|
|
@@ -497,16 +605,23 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
497
605
|
}
|
|
498
606
|
if (!controlFrame && !type.startsWith("response.") && type !== "error") return;
|
|
499
607
|
let steeringEnded = false;
|
|
608
|
+
let refusalStatus: number | undefined;
|
|
609
|
+
let refusalHeaders: Headers | undefined;
|
|
610
|
+
let refusalPrelude: Headers | undefined;
|
|
500
611
|
if (!controlFrame) {
|
|
501
612
|
firstResponseAt ??= Date.now();
|
|
502
613
|
try {
|
|
503
614
|
if (nativeControl) steeringEnded = nativeControl.observe(normalized.payload);
|
|
504
615
|
else correlation?.accept(normalized.payload);
|
|
505
616
|
} catch (error) { failStream(error); return; }
|
|
617
|
+
const holdOpen = refusalEligible && sent && !responseCommitted;
|
|
618
|
+
if (holdOpen) noteRefusalIdentity(normalized.payload);
|
|
506
619
|
// Correlation must run first: a reused socket's foreign-stream error settles as a
|
|
507
620
|
// non-replayable 502 above, never as the refused-create 4xx projection below, which
|
|
508
621
|
// is the one status family that could authorize an account replay.
|
|
509
|
-
|
|
622
|
+
// Only before anything was held: after an empty preamble a 4xx frame keeps today's
|
|
623
|
+
// in-body delivery, so a held 429 can never become a quota rotation it was not before.
|
|
624
|
+
if (metadata && sent && !responseCommitted && !deferredCommit && type === "error") {
|
|
510
625
|
let rejection: Response | null;
|
|
511
626
|
try { rejection = wrappedRejectionResponse(normalized.payload, metadata.snapshot()); }
|
|
512
627
|
catch (error) { failStream(error); return; }
|
|
@@ -520,7 +635,30 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
520
635
|
return;
|
|
521
636
|
}
|
|
522
637
|
}
|
|
523
|
-
|
|
638
|
+
let holdFrame = false;
|
|
639
|
+
if (holdOpen && refusalEligible) {
|
|
640
|
+
refusalStatus = codexWsPreOutputRefusalStatus(normalized.payload);
|
|
641
|
+
if (refusalStatus !== undefined) {
|
|
642
|
+
// Built before anything settles: a refusal whose metadata cannot be projected within the
|
|
643
|
+
// header bounds is delivered the way it was before, in the committed body.
|
|
644
|
+
try {
|
|
645
|
+
refusalPrelude = heldHeaders ?? metadata?.snapshot() ?? new Headers();
|
|
646
|
+
const declared = normalized.payload.headers;
|
|
647
|
+
refusalHeaders = rejectionHeaders(record(declared) ? declared : {}, refusalPrelude);
|
|
648
|
+
} catch {
|
|
649
|
+
refusalStatus = undefined;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
holdFrame = refusalStatus === undefined && isEmptyCodexWsLifecycleEvent(normalized.payload) && beginHold();
|
|
653
|
+
}
|
|
654
|
+
if (refusalStatus === undefined && !holdFrame) commitResponse();
|
|
655
|
+
else deferredCommit = true;
|
|
656
|
+
// Record the terminal the upstream sent as soon as it is accepted, before the relay-size and
|
|
657
|
+
// queue checks below: a body failure that follows is still an exchange that received it.
|
|
658
|
+
if (!nativeControl && isCodexWsTerminalEvent(type)) {
|
|
659
|
+
terminalEvent = type;
|
|
660
|
+
terminalErrorStatus = type === "error" ? (codexWsWrapperStatus(normalized.payload) ?? null) : null;
|
|
661
|
+
}
|
|
524
662
|
}
|
|
525
663
|
const prefix = encoder.encode(`event: ${type}\ndata: `);
|
|
526
664
|
const suffix = encoder.encode("\n\n");
|
|
@@ -558,6 +696,9 @@ export function codexWsExchange(options: ExchangeOptions): Promise<Response> {
|
|
|
558
696
|
markCodexWsStage(committedResponse, stageRecord(null));
|
|
559
697
|
}
|
|
560
698
|
session.release(completedId);
|
|
699
|
+
if (refusalStatus !== undefined && refusalHeaders && refusalPrelude && !responseCommitted) {
|
|
700
|
+
resolveRefusal(refusalStatus, normalized.payload, refusalHeaders, refusalPrelude);
|
|
701
|
+
}
|
|
561
702
|
}
|
|
562
703
|
};
|
|
563
704
|
|