-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(responses): support safe steering settings, public API transport and executable probes #4912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,7 +21,7 @@ runs helper features around provider requests. | |||||||||||||
| | `connectTimeoutMs?` | `number` | `200000` | Per-attempt DNS/TCP/TLS/final-header deadline; it ends before body generation. | | ||||||||||||||
| | `shutdownTimeoutMs?` | `number` | `5000` | Graceful drain deadline before active turns are aborted. | | ||||||||||||||
| | `websockets?` | `boolean` | `false` | Advertise and admit the client-facing Responses WebSocket path. False keeps clients on HTTP/SSE; it does not disable an eligible canonical ChatGPT upstream WS optimization. Complete-input requests may reuse an upstream connection within the same selected credential, account, thread and turn; changed handshake policy or missing identity keeps requests on separate connections. This does not trim HTTP input or create previous-response IDs. | | ||||||||||||||
| | `codexNativeSteering?` | `boolean` | `false` | Experimental, native-only mid-turn steering on the Responses WebSocket endpoint. Requires `websockets: true`, a compatible upstream/client, and unchanged model/settings for saved-tool-result continuations. Does not enable translated models or HTTP fallback. See [native steering](/guides/codex-integration/#experimental-native-mid-turn-steering). | | ||||||||||||||
| | `codexNativeSteering?` | `boolean` | `false` | Experimental, native-only mid-turn steering on the Responses WebSocket endpoint. Requires `websockets: true`, a compatible upstream/client, and a pinned account/model/tool surface. Validated generation settings can change in explicit saved-result continuations. Does not enable translated models or HTTP fallback. See [native steering](/guides/codex-integration/#experimental-native-mid-turn-steering). | | ||||||||||||||
| | `codexNativeInjection?` | `boolean` | `false` | Experimental saved function-result injection on compatible native multi-agent WebSocket turns. Requires `websockets: true`, explicit `multi_agent.enabled`, and an eligible provider. Separate from steering; no automatic tool rerun or recovery create. See [native injection](/guides/codex-integration/#experimental-native-function-result-injection). | | ||||||||||||||
| | `corsAllowOrigins?` | `string[]` | `[]` | Additional exact origins allowed by CORS. Loopback origins are always allowed. Authority-based browser extension origins such as `chrome-extension://<extension-id>` are supported; `*` is not a wildcard. Firefox and Safari regenerate the extension UUID (per install / per browser launch), so update the entry when the origin changes. | | ||||||||||||||
| | `apiKeys?` | `OcxApiKey[]` | `[]` | Generated `ocx_…` credentials accepted by management and data-plane auth on non-loopback binds. Dashboard-managed. | | ||||||||||||||
|
|
@@ -574,14 +574,10 @@ companion form is what makes a hub a single-port deployment, and it is refused o | |||||||||||||
| wildcard `hostname`, where the public listener already holds `127.0.0.1:<port>`. | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| The opt-in `codexNativeInjection` owner also accepts typed saved-result | ||||||||||||||
| continuations on the original account/socket. It does not widen | ||||||||||||||
| `response.inject` beyond string-valued function results, and multi-agent requests | ||||||||||||||
| never acquire the single-agent steering owner merely because injection is disabled. | ||||||||||||||
| See [the continuation contract](/guides/codex-integration/#rich-tool-results-and-explicit-approvals-after-response-completion). | ||||||||||||||
| ## Experimental native response controls | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| `codexNativeSteering` confirmation uses fixed absolute deadlines and retains | ||||||||||||||
| completed output for local continuation history. See | ||||||||||||||
| [steering confirmation deadlines and retained context](/guides/codex-integration/#steering-confirmation-deadlines-and-retained-context) | ||||||||||||||
| for phase timing, unknown-delivery recovery and live-comparison precautions. | ||||||||||||||
| `codexNativeSteering` and `codexNativeInjection` enable separate, default-off native | ||||||||||||||
| WebSocket control paths. See the canonical guide for | ||||||||||||||
| [supported steering routes and settings](../../guides/codex-integration.md#steering-continuation-settings-and-public-api), | ||||||||||||||
| [typed result and approval continuations](../../guides/codex-integration.md#rich-tool-results-and-explicit-approvals-after-response-completion), | ||||||||||||||
| and [confirmation deadlines and retained context](../../guides/codex-integration.md#steering-confirmation-deadlines-and-retained-context). | ||||||||||||||
|
Comment on lines
+581
to
+583
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: find docs-site -maxdepth 3 -iname 'astro.config.*' -o -iname '*starlight*'
rg -n '\]\(\.\./\.\./guides/codex-integration\.md#|\]\(/guides/codex-integration/?#' docs-site/src/content/docs docs-site 2>/dev/null | head -80
rg -n 'relative link|site-relative|Markdown link|\.md#' docs-site README.md CONTRIBUTING.md .github 2>/dev/null | head -100
sed -n '560,590p' docs-site/src/content/docs/reference/configuration/server.mdRepository: lidge-jun/opencodex Length of output: 32523 🏁 Script executed: set -eu
printf '%s\n' '--- docs-site/AGENTS.md ---'
cat docs-site/AGENTS.md
printf '%s\n' '--- docs-site/astro.config.mjs ---'
cat docs-site/astro.config.mjs
printf '%s\n' '--- docs-site/package.json ---'
cat docs-site/package.json
printf '%s\n' '--- relative Markdown links in docs-site ---'
rg -n --glob '*.md' '\]\(\.\.?/[^)]*\.md(?:#[^)]*)?\)' docs-site/src/content/docs | head -120
printf '%s\n' '--- relevant route/source files ---'
find docs-site/src/content/docs -path '*guides/codex-integration.md' -o -path '*reference/configuration/server.md'
printf '%s\n' '--- target headings ---'
rg -n '^#{1,6} .*?(Steering continuation settings and public API|Rich tool results and explicit approvals after response completion|Steering confirmation deadlines and retained context)$|^#{1,6} .*?(steering-continuation-settings-and-public-api|rich-tool-results-and-explicit-approvals-after-response-completion|steering-confirmation-deadlines-and-retained-context)' docs-site/src/content/docs/guides/codex-integration.mdRepository: lidge-jun/opencodex Length of output: 20814 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
Use extensionless site-relative documentation links. Astro/Starlight preserves Markdown link URLs as written. It does not remove -[supported steering routes and settings](../../guides/codex-integration.md#steering-continuation-settings-and-public-api),
-[typed result and approval continuations](../../guides/codex-integration.md#rich-tool-results-and-explicit-approvals-after-response-completion),
-and [confirmation deadlines and retained context](../../guides/codex-integration.md#steering-confirmation-deadlines-and-retained-context).
+[supported steering routes and settings](/guides/codex-integration/#steering-continuation-settings-and-public-api),
+[typed result and approval continuations](/guides/codex-integration/#rich-tool-results-and-explicit-approvals-after-response-completion),
+and [confirmation deadlines and retained context](/guides/codex-integration/#steering-confirmation-deadlines-and-retained-context).📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| type Frame = Record<string, any>; | ||
| export type ProbeScenario = "automatic" | "required-input"; | ||
| export type ProbeReport = { | ||
| scenario: ProbeScenario; outcome: "passed" | "failed" | "unknown" | "not_exercised"; | ||
| accepted: boolean; successorCreated: boolean; markerObserved: boolean; explicitContinuation: boolean; | ||
| sentControls: number; elapsedMs: number; code?: string; | ||
| }; | ||
| const MARKER = "STEERING_PROBE_OK"; | ||
| const safeCodes = new Set(["steering_not_supported", "response_not_active", "response_already_completed", | ||
| "invalid_input", "steering_settings_changed", "steering_settings_unsupported", "too_many_pending_steers"]); | ||
|
|
||
| /** Content-free, single-attempt probe state. It never executes external tools or approval decisions. */ | ||
| export class SteeringProbe { | ||
| private base?: Frame; | ||
| private root?: string; | ||
| private successor?: string; | ||
| private steerId?: string; | ||
| private callId?: string; | ||
| private rootEnded = false; | ||
| private sentSteer = false; | ||
| private reportValue?: ProbeReport; | ||
| private bytes = 0; | ||
| private frames = 0; | ||
| private markerObserved = false; | ||
| private explicit = false; | ||
| private sentControls = 0; | ||
| private textTail = ""; | ||
| private started = performance.now(); | ||
| constructor(readonly scenario: ProbeScenario, private readonly send: (frame: Frame) => void) {} | ||
|
|
||
| /** Only fixed synthetic prompts and a non-executing tool are sent by this harness. */ | ||
| request(model: string): Frame { | ||
| return this.base = { type: "response.create", model, store: false, reasoning: { effort: "low" }, | ||
| input: this.scenario === "automatic" | ||
| ? "Explain five techniques for organizing a fictional book collection. Work through each in detail." | ||
| : "Call steering_probe once, then use its saved result to answer briefly.", | ||
| ...(this.scenario === "required-input" ? { | ||
| tools: [{ type: "function", name: "steering_probe", description: "Returns a fixed synthetic fixture; performs no external action.", | ||
| parameters: { type: "object", properties: {}, required: [], additionalProperties: false }, strict: true }], | ||
| tool_choice: "auto", | ||
| } : {}), | ||
| }; | ||
| } | ||
| private steer(): void { | ||
| if (!this.root || this.sentSteer || this.rootEnded) return; | ||
| this.sentSteer = true; this.sentControls++; | ||
| this.send({ type: "response.steer", previous_response_id: this.root, input: `Change the answer: respond only with ${MARKER}. Do not run more tools.` }); | ||
| } | ||
| /** Stop with sanitized state, never returning IDs, model output, tokens or endpoint paths. */ | ||
| finish(outcome: ProbeReport["outcome"], code?: string): ProbeReport { | ||
| return this.reportValue ??= { scenario: this.scenario, outcome, accepted: !!this.steerId, | ||
| successorCreated: !!this.successor, markerObserved: this.markerObserved, explicitContinuation: this.explicit, | ||
| sentControls: this.sentControls, elapsedMs: Math.max(0, Math.round(performance.now() - this.started)), ...(code ? { code } : {}) }; | ||
| } | ||
| get report(): ProbeReport | undefined { return this.reportValue; } | ||
|
|
||
| /** Observe a bounded wire stream. Acceptance alone is never a passing probe. */ | ||
| receive(raw: string): ProbeReport | undefined { | ||
| if (this.reportValue) return this.reportValue; | ||
| this.bytes += Buffer.byteLength(raw); | ||
| if (++this.frames > 5000 || this.bytes > 2 * 1024 * 1024) return this.finish("unknown", "probe_budget_exceeded"); | ||
| let event: Frame; | ||
| try { event = JSON.parse(raw); } catch { return this.finish("failed", "invalid_event"); } | ||
| if (!event || typeof event !== "object" || Array.isArray(event)) return this.finish("failed", "invalid_event"); | ||
| const response = event.response; | ||
| if (event.type === "response.created") { | ||
| if (!response || typeof response.id !== "string" || (!response.id.length || response.id.length > 512)) return this.finish("failed", "invalid_identity"); | ||
| if (!this.root) { this.root = response.id; if (this.scenario === "automatic") this.steer(); } | ||
| else { | ||
| if (this.successor || response.id === this.root || !this.rootEnded || !this.steerId | ||
| || (response.previous_response_id != null && response.previous_response_id !== this.root)) return this.finish("failed", "unexpected_successor"); | ||
| this.successor = response.id; | ||
| } | ||
| } else if (event.type === "response.output_item.done" && !this.successor && this.scenario === "required-input") { | ||
| const item = event.item; | ||
| if (event.response_id != null && event.response_id !== this.root) return this.finish("failed", "output_identity_mismatch"); | ||
| if (item?.type === "function_call" && item.name === "steering_probe" && typeof item.call_id === "string") { | ||
| if (this.callId && item.call_id !== this.callId) return this.finish("failed", "unexpected_tool"); | ||
| this.callId = item.call_id; this.steer(); | ||
| } | ||
| } else if (event.type === "response.steer.accepted") { | ||
| if (!this.sentSteer || this.steerId || event.steer?.previous_response_id !== this.root || typeof event.steer?.id !== "string") { | ||
| return this.finish("failed", "unexpected_acceptance"); | ||
| } | ||
| this.steerId = event.steer.id; | ||
| } else if (event.type === "response.steer.pending") { | ||
| if (!this.steerId || event.steer?.id !== this.steerId || event.steer?.previous_response_id !== this.root || !this.rootEnded) { | ||
| return this.finish("failed", "unexpected_pending"); | ||
| } | ||
| if (this.explicit) return this.finish("failed", "duplicate_pending"); | ||
| const stubs = event.required_input; | ||
| if (event.reason !== "waiting_for_required_input" || !Array.isArray(stubs) || stubs.length !== 1 | ||
| || stubs[0]?.type !== "function_call_output" || stubs[0]?.call_id !== this.callId || !this.callId) { | ||
| return this.finish("not_exercised", "unsupported_required_input"); | ||
| } | ||
| this.explicit = true; this.sentControls++; | ||
| this.send({ ...this.base, type: "response.create", previous_response_id: this.root, | ||
| ...(event.stream_id !== undefined ? { stream_id: event.stream_id } : {}), | ||
| input: [{ type: "function_call_output", call_id: this.callId, output: "synthetic saved result; no action was executed" }], | ||
| reasoning: { effort: "medium" }, text: { verbosity: "low" } }); | ||
| } else if (event.type === "response.steer.failed" || event.type === "error") { | ||
| const code = event.error?.code; | ||
| return this.finish("failed", safeCodes.has(code) ? code : "upstream_rejection"); | ||
| } else if (["response.completed", "response.incomplete", "response.failed"].includes(event.type)) { | ||
| if (this.root && response?.id === this.root) { | ||
| this.rootEnded = true; | ||
| if (!this.sentSteer) return this.finish("not_exercised", "no_steering_window"); | ||
| } else if (this.successor && response?.id === this.successor) { | ||
| for (const item of Array.isArray(response.output) ? response.output : []) { | ||
| for (const part of Array.isArray(item?.content) ? item.content : []) if (typeof part?.text === "string" && part.text.includes(MARKER)) this.markerObserved = true; | ||
| } | ||
| if (event.type !== "response.completed") return this.finish("failed", "successor_not_completed"); | ||
| if (this.scenario === "required-input" && !this.explicit) return this.finish("not_exercised", "required_input_not_observed"); | ||
| return this.finish(this.markerObserved ? "passed" : "failed", this.markerObserved ? undefined : "marker_missing"); | ||
| } else return this.finish("failed", "terminal_identity_mismatch"); | ||
| } else if (event.type === "response.output_text.delta" && this.successor && typeof event.delta === "string") { | ||
| if (event.response_id != null && event.response_id !== this.successor) return this.finish("failed", "output_identity_mismatch"); | ||
| const text = this.textTail + event.delta; | ||
| this.markerObserved ||= text.includes(MARKER); this.textTail = text.slice(-MARKER.length); | ||
| } | ||
| return undefined; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 49092
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 48058
Document the adapter field without requiring a reserved provider ID.
Public API steering accepts a custom key-auth provider when it uses
adapter: "openai-responses",baseUrl: "https://api.openai.com/v1", andupstreamWebsocket: true. It does not require the provider identifieropenai-apikey.The current wording can be read as naming the provider
openai-responsesand does not state the required adapter field. Use wording that identifies a custom key-auth provider and its adapter:🤖 Prompt for AI Agents