mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Normalize formatting in docs, web assets, and sample scripts (#52256)
## What changed - Reformat Markdown tables and lists, login pages, inline visualization HTML, and sample JavaScript scripts. - Escape union-type pipes in Python SDK API tables so they render within their cells. - Keep the formatting-recipe test focused on the working directory and formatter commands by removing assertions on comment text. GitOrigin-RevId: ae46d5b40fd013553de8b5ccf2c41d76be65aba8
This commit is contained in:
committed by
copyberry
parent
75e0b4088e
commit
3b6ab3471a
@@ -19,7 +19,7 @@ runs:
|
||||
release_tag="rusty-v8-v${version}"
|
||||
base_url="https://github.com/openai/codex/releases/download/${release_tag}"
|
||||
binding_dir="${RUNNER_TEMP}/rusty_v8"
|
||||
|
||||
|
||||
profile="ptrcomp_sandbox_release"
|
||||
|
||||
if [[ "$TARGET" == *-pc-windows-msvc ]]; then
|
||||
|
||||
@@ -49,9 +49,11 @@ Eligible managed daemons check for updates after five minutes, then hourly by
|
||||
default. Edit `CODEX_HOME/app-server-daemon/settings.json` to change this:
|
||||
|
||||
```json
|
||||
{"remoteControlEnabled": false,
|
||||
"shutdownGraceSeconds": 60,
|
||||
"updater": {"autoUpdateEnabled": false, "updateIntervalMinutes": 120}}
|
||||
{
|
||||
"remoteControlEnabled": false,
|
||||
"shutdownGraceSeconds": 60,
|
||||
"updater": { "autoUpdateEnabled": false, "updateIntervalMinutes": 120 }
|
||||
}
|
||||
```
|
||||
|
||||
Positive minute intervals have no configured cap. `daemon restart` applies the
|
||||
@@ -114,11 +116,11 @@ compatible dedicated package before stopping the legacy updater and daemon,
|
||||
selecting the new package, and restarting only a previously running daemon.
|
||||
The old CLI package files and selection remain unchanged.
|
||||
|
||||
| Situation | What starts | Does this daemon fetch new binaries? | Does a running app-server eventually move to a newer binary on its own? |
|
||||
| --- | --- | --- | --- |
|
||||
| Latest-channel installer has run; `start` or `bootstrap` is used with automatic updates enabled | Managed binary and detached updater when supported | When supported, the platform's installer runs on the configured cadence. | When supported, the running server restarts with the new binary before the updater replaces itself. |
|
||||
| Installer selected an explicit release; `bootstrap` is used | Managed binary only | No; the selected release stays pinned. | No; an explicit restart uses the selected binary. |
|
||||
| Another tool updates the managed binary | A fresh start or explicit restart uses it; a running server is reused. | Yes, when a latest-channel updater is running, on the configured cadence. | An updater that was running through the change compares binary contents on its next successful installer pass and refreshes the server first. |
|
||||
| Situation | What starts | Does this daemon fetch new binaries? | Does a running app-server eventually move to a newer binary on its own? |
|
||||
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Latest-channel installer has run; `start` or `bootstrap` is used with automatic updates enabled | Managed binary and detached updater when supported | When supported, the platform's installer runs on the configured cadence. | When supported, the running server restarts with the new binary before the updater replaces itself. |
|
||||
| Installer selected an explicit release; `bootstrap` is used | Managed binary only | No; the selected release stays pinned. | No; an explicit restart uses the selected binary. |
|
||||
| Another tool updates the managed binary | A fresh start or explicit restart uses it; a running server is reused. | Yes, when a latest-channel updater is running, on the configured cadence. | An updater that was running through the change compares binary contents on its next successful installer pass and refreshes the server first. |
|
||||
|
||||
### Managed packages
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# App Server Test Client
|
||||
|
||||
Quickstart for running and hitting `codex app-server`.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@@ -165,13 +165,13 @@ Local UI clients use five methods. They require the existing
|
||||
`unavailable/providerUnavailable` on unsupported platforms or without the required
|
||||
ChatGPT account identity.
|
||||
|
||||
| Method | Params | Result |
|
||||
| --- | --- | --- |
|
||||
| `userVerification/status` | `{}` | `{credentialId, unavailableReason, unavailableMessage}` |
|
||||
| `userVerification/enroll` | `{}` | `{credentialId, algorithm?, publicKey?}` |
|
||||
| `userVerification/delete` | `{}` | `{}` |
|
||||
| `userVerification/verify` | `{challenge, title, description}` | `{proof: {credentialId, signature}}` |
|
||||
| `userVerification/cancel` | `{requestId}` | `{}` |
|
||||
| Method | Params | Result |
|
||||
| ------------------------- | --------------------------------- | ------------------------------------------------------- |
|
||||
| `userVerification/status` | `{}` | `{credentialId, unavailableReason, unavailableMessage}` |
|
||||
| `userVerification/enroll` | `{}` | `{credentialId, algorithm?, publicKey?}` |
|
||||
| `userVerification/delete` | `{}` | `{}` |
|
||||
| `userVerification/verify` | `{challenge, title, description}` | `{proof: {credentialId, signature}}` |
|
||||
| `userVerification/cancel` | `{requestId}` | `{}` |
|
||||
|
||||
Status reads local readiness without prompting or contacting a backend. A null
|
||||
`unavailableReason` means local checks passed, not that registration is valid.
|
||||
@@ -277,6 +277,7 @@ the entire network configuration.
|
||||
- `thread/attachmentOwner/list` — find stored threads with an exact attachment type and identity key, with cursor pagination and an optional archive filter.
|
||||
- `thread/attachment/remove` — remove an attachment by its thread, attachment type, and identity key; returns `{}`.
|
||||
- `thread/attachment/updated` — notification broadcast after an attachment is created or removed; contains the thread, attachment identity, attachment id, and operation.
|
||||
|
||||
### Example: Manage stored thread attachments
|
||||
|
||||
Attachments record the resources currently associated with a thread, independently of conversation history. Clients can add, remove, and list attachments for one stored thread at a time without resuming those threads. Adding or removing an attachment does not create or delete the underlying resource or rewrite history. An attachment is idempotently identified by its thread, `attachmentType`, and `identityKey`. For pull requests, clients should reuse the canonical application identity `JSON.stringify([canonicalHostname, lowercaseOwner, lowercaseRepository, pullRequestNumber])` so addition and removal agree across surfaces.
|
||||
|
||||
@@ -12,6 +12,7 @@ Typed clients for Codex/OpenAI APIs built on top of the generic transport in `co
|
||||
The public interface of this crate is intentionally small and uniform:
|
||||
|
||||
- **Responses endpoint**
|
||||
|
||||
- Input:
|
||||
- `ResponsesApiRequest` for the request body (`model`, `input`, `tools`, `parallel_tool_calls`, reasoning/text controls).
|
||||
- `ResponsesOptions` for transport/header concerns (`conversation_id`, `session_source`, `extra_headers`, `compression`, `turn_state`).
|
||||
|
||||
+23
-23
@@ -91,36 +91,36 @@ only enumerate labels and do not need remote caches or execution.
|
||||
|
||||
The `Cache/BES` host is also used for remote downloads.
|
||||
|
||||
| Invocation/config | Key Required | Cache/BES | Build exec | Test exec |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `bazel ...` | No | None | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-generic` | Yes | `remote.buildbuddy.io` | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-generic-rbe` | Yes | `remote.buildbuddy.io` | Remote | Remote |
|
||||
| `bazel ... --config=buildbuddy-openai` | Yes | `openai.buildbuddy.io` | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-openai-rbe` | Yes | `openai.buildbuddy.io` | Remote | Remote |
|
||||
| Invocation/config | Key Required | Cache/BES | Build exec | Test exec |
|
||||
| ------------------------------------------- | ------------ | ---------------------- | ---------- | --------- |
|
||||
| `bazel ...` | No | None | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-generic` | Yes | `remote.buildbuddy.io` | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-generic-rbe` | Yes | `remote.buildbuddy.io` | Remote | Remote |
|
||||
| `bazel ... --config=buildbuddy-openai` | Yes | `openai.buildbuddy.io` | Local | Local |
|
||||
| `bazel ... --config=buildbuddy-openai-rbe` | Yes | `openai.buildbuddy.io` | Remote | Remote |
|
||||
|
||||
Without an API key, the wrapper removes remote CI configurations and runs
|
||||
locally. With a key, workflows choose the host as follows:
|
||||
|
||||
| Run | Key | Uses OpenAI BuildBuddy Host |
|
||||
| --- | --- | --- |
|
||||
| Push to `main` in `openai/codex` | Yes | Yes |
|
||||
| `workflow_dispatch` in `openai/codex` | Yes | Yes |
|
||||
| Same-repository pull request in `openai/codex` | Yes | Yes |
|
||||
| Fork pull request into `openai/codex` | No | No; local |
|
||||
| Push or `workflow_dispatch` in a fork with a key | Yes | No; generic host |
|
||||
| Pull request run in a fork repository with a key | Yes | No; generic host |
|
||||
| Run | Key | Uses OpenAI BuildBuddy Host |
|
||||
| ------------------------------------------------ | --- | --------------------------- |
|
||||
| Push to `main` in `openai/codex` | Yes | Yes |
|
||||
| `workflow_dispatch` in `openai/codex` | Yes | Yes |
|
||||
| Same-repository pull request in `openai/codex` | Yes | Yes |
|
||||
| Fork pull request into `openai/codex` | No | No; local |
|
||||
| Push or `workflow_dispatch` in a fork with a key | Yes | No; generic host |
|
||||
| Pull request run in a fork repository with a key | Yes | No; generic host |
|
||||
|
||||
CI configurations determine whether builds and tests execute remotely:
|
||||
|
||||
| CI config | Remote config | Build exec | Test exec |
|
||||
| --- | --- | --- | --- |
|
||||
| `ci-linux` | `*-rbe` | Remote host | Remote host |
|
||||
| `ci-v8` | `*-rbe` | Remote host | Remote host |
|
||||
| `ci-macos` | `*-rbe` | Remote host | Local |
|
||||
| `ci-windows-cross` | `*-rbe` | Remote host | Local |
|
||||
| `ci-windows` | non-RBE | Local | Local |
|
||||
| Keyless CI fallback | none | Local | Local |
|
||||
| CI config | Remote config | Build exec | Test exec |
|
||||
| ------------------- | ------------- | ----------- | ----------- |
|
||||
| `ci-linux` | `*-rbe` | Remote host | Remote host |
|
||||
| `ci-v8` | `*-rbe` | Remote host | Remote host |
|
||||
| `ci-macos` | `*-rbe` | Remote host | Local |
|
||||
| `ci-windows-cross` | `*-rbe` | Remote host | Local |
|
||||
| `ci-windows` | non-RBE | Local | Local |
|
||||
| Keyless CI fallback | none | Local | Local |
|
||||
|
||||
To exercise the generic remote configuration with your key:
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ Since only 1 `Task` can be run at a time, for parallel tasks it is recommended t
|
||||
For complete documentation of the `Op` and `EventMsg` variants, refer to [protocol.rs](../protocol/src/protocol.rs). Some example payload types:
|
||||
|
||||
- `Op`
|
||||
|
||||
- `Op::UserTurn` – Any input from the user to kick off a `Turn`, including full per-turn context such as cwd, model, sandbox, approval policy, and optional `approvals_reviewer`
|
||||
- `Op::Interrupt` – Interrupts a running turn
|
||||
- `Op::ExecApproval` – Approve or deny code execution
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
Everything becomes a good contributor design, which contributors do we need?
|
||||
|
||||
git attribution Context
|
||||
memories Context + Tool + Output
|
||||
guardian Context + Request
|
||||
goal Tool + Runtime
|
||||
image generation Tool + Output
|
||||
skills Context + Turn
|
||||
personality Context
|
||||
plugins / apps / connectors Context + Turn
|
||||
shell snapshot Runtime
|
||||
web search Tool
|
||||
AGENTS.md Context (Runtime too only if you want eager refresh/cache behavior)
|
||||
future sandboxing probably Request + Runtime
|
||||
| Area | Contributors |
|
||||
| --------------------------- | ------------------------------------------------------------------- |
|
||||
| git attribution | Context |
|
||||
| memories | Context + Tool + Output |
|
||||
| guardian | Context + Request |
|
||||
| goal | Tool + Runtime |
|
||||
| image generation | Tool + Output |
|
||||
| skills | Context + Turn |
|
||||
| personality | Context |
|
||||
| plugins / apps / connectors | Context + Turn |
|
||||
| shell snapshot | Runtime |
|
||||
| web search | Tool |
|
||||
| AGENTS.md | Context (Runtime too only if you want eager refresh/cache behavior) |
|
||||
| future sandboxing | probably Request + Runtime |
|
||||
|
||||
@@ -22,6 +22,7 @@ it cannot create the required user namespaces, so Codex rejects sandboxed shell
|
||||
commands that would enter the bubblewrap path.
|
||||
|
||||
**Current Behavior**
|
||||
|
||||
- Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported.
|
||||
- Bubblewrap is the default filesystem sandbox.
|
||||
- If `bwrap` is present on `PATH` outside the current working directory, the
|
||||
@@ -108,4 +109,5 @@ commands that would enter the bubblewrap path.
|
||||
remain in force. The default `isolate` mode retains PID isolation.
|
||||
|
||||
**Notes**
|
||||
|
||||
- The CLI surface is `codex sandbox`; the host OS selects the sandbox backend.
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Codex Sign-in Error</title>
|
||||
<link rel="icon" href='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"%3E%3Cpath stroke="%23000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/%3E%3C/svg%3E' type="image/svg+xml">
|
||||
<link
|
||||
rel="icon"
|
||||
href='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"%3E%3Cpath stroke="%23000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/%3E%3C/svg%3E'
|
||||
type="image/svg+xml"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
@@ -11,8 +15,18 @@
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
background: radial-gradient(circle at top, #f7f8fb 0%, #ffffff 48%);
|
||||
color: #0d0d0d;
|
||||
}
|
||||
@@ -80,8 +94,9 @@
|
||||
color: #5d5d5d;
|
||||
}
|
||||
code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
.help {
|
||||
@@ -96,7 +111,20 @@
|
||||
<div class="card">
|
||||
<div class="brand">
|
||||
<div class="logo" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 32 32"><path stroke="#000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"></path></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
stroke="#000"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.484"
|
||||
d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="brand-title">Codex login</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -11,7 +11,9 @@
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--font-use: "SF Pro", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-use:
|
||||
"SF Pro", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", sans-serif;
|
||||
--gray-0: #ffffff;
|
||||
--gray-50: #f9f9f9;
|
||||
--gray-100: #ededed;
|
||||
@@ -88,7 +90,9 @@
|
||||
.message {
|
||||
margin: 0;
|
||||
color: var(--text-secondary, #5d5d5d);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@@ -108,7 +112,9 @@
|
||||
color: var(--interactive-label-secondary-default, #0d0d0d);
|
||||
cursor: pointer;
|
||||
font-family: var(--font-use, "SF Pro");
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-feature-settings:
|
||||
"liga" off,
|
||||
"clig" off;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 510;
|
||||
@@ -161,7 +167,9 @@
|
||||
<div class="wordmark" aria-label="ChatGPT">ChatGPT</div>
|
||||
<main>
|
||||
<div class="content">
|
||||
<p class="message" id="status-message">You’re signed in and may close this tab</p>
|
||||
<p class="message" id="status-message">
|
||||
You’re signed in and may close this tab
|
||||
</p>
|
||||
<button class="button" type="button" id="open-codex">
|
||||
<svg
|
||||
class="codex-mark"
|
||||
@@ -181,7 +189,10 @@
|
||||
</button>
|
||||
<div class="setup-box" id="setup-box">
|
||||
<p class="setup-title">Finish setting up your API organization</p>
|
||||
<p class="setup-description">Add a payment method to use your organization. Redirecting in <span id="countdown">3</span>s...</p>
|
||||
<p class="setup-description">
|
||||
Add a payment method to use your organization. Redirecting in
|
||||
<span id="countdown">3</span>s...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -191,7 +202,8 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const preview = params.get("preview") === "true";
|
||||
const needsSetup = params.get("needs_setup") === "true";
|
||||
const platformUrl = params.get("platform_url") || "https://platform.openai.com";
|
||||
const platformUrl =
|
||||
params.get("platform_url") || "https://platform.openai.com";
|
||||
const orgId = params.get("org_id");
|
||||
const projectId = params.get("project_id");
|
||||
const planType = params.get("plan_type");
|
||||
@@ -203,7 +215,9 @@
|
||||
window.location.href = CODEX_URL;
|
||||
}
|
||||
|
||||
document.getElementById("open-codex").addEventListener("click", openCodex);
|
||||
document
|
||||
.getElementById("open-codex")
|
||||
.addEventListener("click", openCodex);
|
||||
if (!preview) {
|
||||
setTimeout(openCodex, 250);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Sign into Codex</title>
|
||||
<link rel="icon" href='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"%3E%3Cpath stroke="%23000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/%3E%3C/svg%3E' type="image/svg+xml">
|
||||
<link
|
||||
rel="icon"
|
||||
href='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"%3E%3Cpath stroke="%23000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"/%3E%3C/svg%3E'
|
||||
type="image/svg+xml"
|
||||
/>
|
||||
<style>
|
||||
.container {
|
||||
margin: auto;
|
||||
@@ -14,7 +18,18 @@
|
||||
position: relative;
|
||||
background: white;
|
||||
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
}
|
||||
.inner-container {
|
||||
width: 400px;
|
||||
@@ -38,7 +53,7 @@
|
||||
}
|
||||
.title {
|
||||
text-align: center;
|
||||
color: var(--text-primary, #0D0D0D);
|
||||
color: var(--text-primary, #0d0d0d);
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: 40px;
|
||||
@@ -50,7 +65,7 @@
|
||||
background: var(--bg-primary, white);
|
||||
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 16px;
|
||||
outline: 1px var(--border-default, rgba(13, 13, 13, 0.10)) solid;
|
||||
outline: 1px var(--border-default, rgba(13, 13, 13, 0.1)) solid;
|
||||
outline-offset: -1px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
@@ -74,7 +89,7 @@
|
||||
}
|
||||
.setup-title {
|
||||
align-self: stretch;
|
||||
color: var(--text-primary, #0D0D0D);
|
||||
color: var(--text-primary, #0d0d0d);
|
||||
font-size: 14px;
|
||||
font-weight: 510;
|
||||
line-height: 20px;
|
||||
@@ -82,7 +97,7 @@
|
||||
}
|
||||
.setup-description {
|
||||
align-self: stretch;
|
||||
color: var(--text-secondary, #5D5D5D);
|
||||
color: var(--text-secondary, #5d5d5d);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
@@ -98,7 +113,7 @@
|
||||
.redirect-button {
|
||||
height: 28px;
|
||||
padding: 8px 16px;
|
||||
background: var(--interactive-bg-primary-default, #0D0D0D);
|
||||
background: var(--interactive-bg-primary-default, #0d0d0d);
|
||||
border-radius: 999px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -121,7 +136,7 @@
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 16px;
|
||||
border: .5px solid rgba(0, 0, 0, 0.1);
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 16px 0px;
|
||||
box-sizing: border-box;
|
||||
background-color: rgb(255, 255, 255);
|
||||
@@ -133,21 +148,47 @@
|
||||
<div class="inner-container">
|
||||
<div class="content">
|
||||
<div class="logo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"><path stroke="#000" stroke-linecap="round" stroke-width="2.484" d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"></path></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
fill="none"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
stroke="#000"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.484"
|
||||
d="M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="title">Signed in to Codex</div>
|
||||
</div>
|
||||
<div class="close-box" style="display: none;">
|
||||
<div class="close-box" style="display: none">
|
||||
<div class="setup-description">You may now close this page</div>
|
||||
</div>
|
||||
<div class="setup-box" style="display: none;">
|
||||
<div class="setup-box" style="display: none">
|
||||
<div class="setup-content">
|
||||
<div class="setup-text">
|
||||
<div class="setup-title">Finish setting up your API organization</div>
|
||||
<div class="setup-description">Add a payment method to use your organization.</div>
|
||||
<div class="setup-title">
|
||||
Finish setting up your API organization
|
||||
</div>
|
||||
<div class="setup-description">
|
||||
Add a payment method to use your organization.
|
||||
</div>
|
||||
</div>
|
||||
<div class="redirect-box">
|
||||
<div data-hasendicon="false" data-hasstarticon="false" data-ishovered="false" data-isinactive="false" data-ispressed="false" data-size="large" data-type="primary" class="redirect-button">
|
||||
<div
|
||||
data-hasendicon="false"
|
||||
data-hasstarticon="false"
|
||||
data-ishovered="false"
|
||||
data-isinactive="false"
|
||||
data-ispressed="false"
|
||||
data-size="large"
|
||||
data-type="primary"
|
||||
class="redirect-button"
|
||||
>
|
||||
<div class="redirect-text">Redirecting in 3s...</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,34 +199,34 @@
|
||||
<script>
|
||||
(function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const needsSetup = params.get('needs_setup') === 'true';
|
||||
const platformUrl = params.get('platform_url') || 'https://platform.openai.com';
|
||||
const orgId = params.get('org_id');
|
||||
const projectId = params.get('project_id');
|
||||
const planType = params.get('plan_type');
|
||||
const idToken = params.get('id_token');
|
||||
const needsSetup = params.get("needs_setup") === "true";
|
||||
const platformUrl =
|
||||
params.get("platform_url") || "https://platform.openai.com";
|
||||
const orgId = params.get("org_id");
|
||||
const projectId = params.get("project_id");
|
||||
const planType = params.get("plan_type");
|
||||
const idToken = params.get("id_token");
|
||||
// Show different message and optional redirect when setup is required
|
||||
if (needsSetup) {
|
||||
// The login server selects one of these platform origins from the OAuth issuer.
|
||||
if (
|
||||
platformUrl !== 'https://platform.openai.com' &&
|
||||
platformUrl !== 'https://platform.api.openai.org'
|
||||
platformUrl !== "https://platform.openai.com" &&
|
||||
platformUrl !== "https://platform.api.openai.org"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const setupBox = document.querySelector('.setup-box');
|
||||
setupBox.style.display = 'flex';
|
||||
const redirectUrlObj = new URL('/org-setup', platformUrl);
|
||||
redirectUrlObj.searchParams.set('p', planType);
|
||||
redirectUrlObj.searchParams.set('t', idToken);
|
||||
redirectUrlObj.searchParams.set('with_org', orgId);
|
||||
redirectUrlObj.searchParams.set('project_id', projectId);
|
||||
const setupBox = document.querySelector(".setup-box");
|
||||
setupBox.style.display = "flex";
|
||||
const redirectUrlObj = new URL("/org-setup", platformUrl);
|
||||
redirectUrlObj.searchParams.set("p", planType);
|
||||
redirectUrlObj.searchParams.set("t", idToken);
|
||||
redirectUrlObj.searchParams.set("with_org", orgId);
|
||||
redirectUrlObj.searchParams.set("project_id", projectId);
|
||||
const redirectUrl = redirectUrlObj.toString();
|
||||
const message = document.querySelector('.redirect-text');
|
||||
const message = document.querySelector(".redirect-text");
|
||||
let countdown = 3;
|
||||
function tick() {
|
||||
message.textContent =
|
||||
'Redirecting in ' + countdown + 's…';
|
||||
message.textContent = "Redirecting in " + countdown + "s…";
|
||||
if (countdown === 0) {
|
||||
window.location.replace(redirectUrl);
|
||||
} else {
|
||||
@@ -195,10 +236,10 @@
|
||||
}
|
||||
tick();
|
||||
} else {
|
||||
const closeBox = document.querySelector('.close-box');
|
||||
closeBox.style.display = 'flex';
|
||||
const closeBox = document.querySelector(".close-box");
|
||||
closeBox.style.display = "flex";
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -6,13 +6,13 @@ production dependency is needed.
|
||||
|
||||
## Supported subsets
|
||||
|
||||
| Family | Supported syntax |
|
||||
| --- | --- |
|
||||
| `flowchart`, `graph` | TD/TB (default), BT, LR, RL; rectangle `[label]`, decision `{label}`, and stadium `([label])` nodes; solid `-->`/`---`/`<-->` and dashed `-.->`/`-.-`/`<-.->` edges; pipe labels and spaced directed infix labels (`-- text -->`, `-. text .->`); `&` fan-out/fan-in, chains, branches, merges, loops |
|
||||
| `sequenceDiagram` | Implicit participants, `participant`/`actor`, aliases, `->`, `->>`, `-->`, `-->>`, `-x`, `--x`, self-messages, `Note over A[,B]`, nested `loop`/`alt`/`opt`/`critical`/`break`, one labeled `else` per `alt` |
|
||||
| `stateDiagram-v2`, `stateDiagram` | Flat states, `state "label" as ID`, descriptions, directed transitions with optional labels, initial/final `[*]`, direction declarations |
|
||||
| `classDiagram` | `class ID`, multiline member bodies, `ID : member`, solid/dashed links, association, inheritance, composition, aggregation, dependency, realization, quoted endpoint cardinalities, relationship labels, direction declarations |
|
||||
| `erDiagram` | Entities, multiline attribute bodies, `type name [PK, FK, UK] ["comment"]`, all four endpoint cardinalities, identifying/non-identifying relationships, relationship labels, direction declarations |
|
||||
| Family | Supported syntax |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `flowchart`, `graph` | TD/TB (default), BT, LR, RL; rectangle `[label]`, decision `{label}`, and stadium `([label])` nodes; solid `-->`/`---`/`<-->` and dashed `-.->`/`-.-`/`<-.->` edges; pipe labels and spaced directed infix labels (`-- text -->`, `-. text .->`); `&` fan-out/fan-in, chains, branches, merges, loops |
|
||||
| `sequenceDiagram` | Implicit participants, `participant`/`actor`, aliases, `->`, `->>`, `-->`, `-->>`, `-x`, `--x`, self-messages, `Note over A[,B]`, nested `loop`/`alt`/`opt`/`critical`/`break`, one labeled `else` per `alt` |
|
||||
| `stateDiagram-v2`, `stateDiagram` | Flat states, `state "label" as ID`, descriptions, directed transitions with optional labels, initial/final `[*]`, direction declarations |
|
||||
| `classDiagram` | `class ID`, multiline member bodies, `ID : member`, solid/dashed links, association, inheritance, composition, aggregation, dependency, realization, quoted endpoint cardinalities, relationship labels, direction declarations |
|
||||
| `erDiagram` | Entities, multiline attribute bodies, `type name [PK, FK, UK] ["comment"]`, all four endpoint cardinalities, identifying/non-identifying relationships, relationship labels, direction declarations |
|
||||
|
||||
Identifiers are ASCII letters followed by letters, digits, or underscores. Text
|
||||
supports ordinary Unicode and CJK. Full-line `%%` comments and
|
||||
@@ -76,6 +76,5 @@ family, relationship endpoints, all ER cardinalities, width/error bounds,
|
||||
truncated input, and reconstruction of every edge in all 512 directed three-node
|
||||
graphs in each of the four layout directions.
|
||||
|
||||
|
||||
`render_spans` returns the same layout as lines of semantic `Node`, `Edge`, and
|
||||
`Text` spans. Callers apply their own theme; the crate never emits ANSI escapes.
|
||||
|
||||
@@ -257,11 +257,11 @@ what it can reasonably guarantee.
|
||||
- the HTTP proxy listener clamps non-loopback binds unless explicitly enabled via
|
||||
`dangerously_allow_non_loopback_proxy`
|
||||
- when unix socket proxying is enabled, all proxy listeners are forced to loopback to avoid turning the
|
||||
proxy into a remote bridge into local daemons.
|
||||
proxy into a remote bridge into local daemons.
|
||||
- `dangerously_allow_all_unix_sockets = true` bypasses the unix socket allowlist entirely (still
|
||||
macOS-only and absolute-path-only). Use only in tightly controlled environments.
|
||||
- `enabled` is enforced at runtime; when false the proxy no-ops and does not bind listeners.
|
||||
Limitations:
|
||||
Limitations:
|
||||
|
||||
- DNS rebinding is hard to fully prevent without pinning the resolved IP(s) all the way down to the
|
||||
transport layer. If your threat model includes hostile DNS, enforce network egress at a lower
|
||||
|
||||
@@ -233,13 +233,13 @@ metrics.shutdown()?; // flushes in-memory exporter
|
||||
failed sends. It carries existing session tags plus `mode` (`incremental`/`full`),
|
||||
`phase` (`warmup`/`generation`), and `reason`:
|
||||
|
||||
| Reason | Meaning |
|
||||
| --- | --- |
|
||||
| `incremental` | Send the previous response ID and new input. |
|
||||
| `no_previous_request` | First request from a fresh client. |
|
||||
| `restored_history` | First request after loading resumed or forked history. |
|
||||
| `connection_closed` | Full input after observing the previous socket closed. |
|
||||
| `other` | Full input for another reason, such as changed input/settings or unavailable response state. |
|
||||
| Reason | Meaning |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `incremental` | Send the previous response ID and new input. |
|
||||
| `no_previous_request` | First request from a fresh client. |
|
||||
| `restored_history` | First request after loading resumed or forked history. |
|
||||
| `connection_closed` | Full input after observing the previous socket closed. |
|
||||
| `other` | Full input for another reason, such as changed input/settings or unavailable response state. |
|
||||
|
||||
Per-socket backend metrics label a resend after reconnect as `initial`; this client
|
||||
metric retains the first reset reason through reconnect failures and turn boundaries.
|
||||
|
||||
@@ -16,7 +16,8 @@ import process from "node:process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { inspect, promisify } from "node:util";
|
||||
|
||||
const DEFAULT_MANUAL_URL = "https://developers.openai.com/codex/codex-manual.md";
|
||||
const DEFAULT_MANUAL_URL =
|
||||
"https://developers.openai.com/codex/codex-manual.md";
|
||||
const DEFAULT_CACHE_DIR_NAME = "openai-docs-cache";
|
||||
const CACHE_FILE_NAME = "codex-manual.md";
|
||||
const OUTLINE_FILE_NAME = "codex-manual.outline.md";
|
||||
@@ -79,7 +80,7 @@ const parseCurlHeaders = (rawHeaders) => {
|
||||
const statusMatch = /^HTTP\/\S+\s+(\d{3})/.exec(statusLine);
|
||||
if (!statusMatch) {
|
||||
throw new ManualFetchError(
|
||||
`Could not parse HTTP status from curl response: ${statusLine}`
|
||||
`Could not parse HTTP status from curl response: ${statusLine}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ const tempFilePath = (cacheDir, suffix) =>
|
||||
cacheDir,
|
||||
`.fetch-codex-manual-${process.pid}-${Date.now()}-${Math.random()
|
||||
.toString(16)
|
||||
.slice(2)}${suffix}`
|
||||
.slice(2)}${suffix}`,
|
||||
);
|
||||
|
||||
const requestManualWithCurl = async (url, { cacheDir, method, timeoutMs }) => {
|
||||
@@ -166,7 +167,7 @@ const requestManualWithCurl = async (url, { cacheDir, method, timeoutMs }) => {
|
||||
const requestManualWithFetch = async (url, { method, timeoutMs }) => {
|
||||
if (typeof fetch !== "function") {
|
||||
throw new ManualFetchError(
|
||||
"Native fetch is unavailable in this Node runtime."
|
||||
"Native fetch is unavailable in this Node runtime.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -177,7 +178,7 @@ const requestManualWithFetch = async (url, { method, timeoutMs }) => {
|
||||
headers: { "User-Agent": USER_AGENT },
|
||||
signal,
|
||||
}),
|
||||
timeoutMs
|
||||
timeoutMs,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -199,7 +200,7 @@ const requestManual = async (url, { cacheDir, method, timeoutMs }) => {
|
||||
const response = await transport();
|
||||
if (!response.ok) {
|
||||
throw new ManualFetchError(
|
||||
`${method} ${url} failed with HTTP ${response.status}.`
|
||||
`${method} ${url} failed with HTTP ${response.status}.`,
|
||||
);
|
||||
}
|
||||
return response;
|
||||
@@ -307,7 +308,10 @@ const manualLines = (manual) => {
|
||||
};
|
||||
|
||||
const sectionTitle = (rawTitle) =>
|
||||
rawTitle.replace(/\s+#+\s*$/, "").replace(/\s+/g, " ").trim();
|
||||
rawTitle
|
||||
.replace(/\s+#+\s*$/, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
const buildOutline = (manual) => {
|
||||
const lines = manualLines(manual);
|
||||
@@ -366,7 +370,8 @@ const buildOutline = (manual) => {
|
||||
};
|
||||
};
|
||||
|
||||
const outlineMarkdown = (outline) => `# Codex Manual Outline\n\n${outline.text}\n`;
|
||||
const outlineMarkdown = (outline) =>
|
||||
`# Codex Manual Outline\n\n${outline.text}\n`;
|
||||
|
||||
const manualStatusLine = (status) =>
|
||||
status.cacheStatus === "hit"
|
||||
@@ -413,7 +418,7 @@ const fetchCodexManual = async ({
|
||||
const resolvedCacheDir = await resolveCacheDir(cacheDir);
|
||||
if (!resolvedCacheDir) {
|
||||
throw new ManualFetchError(
|
||||
"Manual cache directory is unavailable; pass --cache-dir to override or use OpenAI Docs MCP fallback."
|
||||
"Manual cache directory is unavailable; pass --cache-dir to override or use OpenAI Docs MCP fallback.",
|
||||
);
|
||||
}
|
||||
await mkdir(resolvedCacheDir, { recursive: true });
|
||||
@@ -460,7 +465,7 @@ const fetchCodexManual = async ({
|
||||
const getHeaderSha256 = readHeaderSha(getResponse);
|
||||
if (getHeaderSha256 !== expectedSha256) {
|
||||
throw new ManualFetchError(
|
||||
`${HASH_HEADER} changed between HEAD and GET for ${manualUrl}.`
|
||||
`${HASH_HEADER} changed between HEAD and GET for ${manualUrl}.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -469,7 +474,7 @@ const fetchCodexManual = async ({
|
||||
const manualHashMatches = actualSha256 === expectedSha256;
|
||||
if (!manualHashMatches) {
|
||||
throw new ManualFetchError(
|
||||
`${HASH_HEADER} did not match the fetched manual body for ${manualUrl}.`
|
||||
`${HASH_HEADER} did not match the fetched manual body for ${manualUrl}.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -49,7 +49,9 @@ async function readSource(source) {
|
||||
return response.text();
|
||||
}
|
||||
|
||||
lastError = new Error("failed to fetch " + source + ": " + response.status);
|
||||
lastError = new Error(
|
||||
"failed to fetch " + source + ": " + response.status,
|
||||
);
|
||||
if (response.status < 500 && response.status !== 429) {
|
||||
break;
|
||||
}
|
||||
@@ -101,7 +103,7 @@ function parseFlatInfo(block) {
|
||||
function extractLatestModelInfo(markdown) {
|
||||
const lines = markdown.split(/\r?\n/);
|
||||
const latestModelInfoIndex = lines.findIndex((line) =>
|
||||
/^latestModelInfo:\s*$/.test(line)
|
||||
/^latestModelInfo:\s*$/.test(line),
|
||||
);
|
||||
|
||||
if (latestModelInfoIndex >= 0) {
|
||||
@@ -109,7 +111,7 @@ function extractLatestModelInfo(markdown) {
|
||||
}
|
||||
|
||||
const commentMatch = markdown.match(
|
||||
/<!--\s*latestModelInfo\s*\n([\s\S]*?)\n\s*-->/m
|
||||
/<!--\s*latestModelInfo\s*\n([\s\S]*?)\n\s*-->/m,
|
||||
);
|
||||
if (commentMatch) {
|
||||
return parseFlatInfo(commentMatch[1]);
|
||||
@@ -133,7 +135,7 @@ function normalizeInfo(info, baseUrl) {
|
||||
|
||||
if (!model || !migrationGuide || !promptingGuide) {
|
||||
throw new Error(
|
||||
"latestModelInfo must include model, migrationGuide, and promptingGuide"
|
||||
"latestModelInfo must include model, migrationGuide, and promptingGuide",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ async function main() {
|
||||
}
|
||||
|
||||
process.stdout.write(
|
||||
`${JSON.stringify(normalizeInfo(info, baseUrl), null, 2)}\n`
|
||||
`${JSON.stringify(normalizeInfo(info, baseUrl), null, 2)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,238 +2,242 @@
|
||||
<script src="https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js"></script>
|
||||
<script src="https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
if (window.FloatingUIDOM == null) {
|
||||
return;
|
||||
}
|
||||
const { autoUpdate, computePosition, flip, offset, shift, size } =
|
||||
window.FloatingUIDOM;
|
||||
const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
|
||||
const tooltipId = `codex-visualization-tooltip-${
|
||||
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
|
||||
}`;
|
||||
const tooltipGap = 5;
|
||||
const hoverDelay = 700;
|
||||
const skipDelay = 300;
|
||||
let activeTrigger = null;
|
||||
let pendingTrigger = null;
|
||||
let tooltip = null;
|
||||
let openTimeout = null;
|
||||
let autoUpdateCleanup = null;
|
||||
let positionRequest = 0;
|
||||
let skipDelayUntil = 0;
|
||||
(() => {
|
||||
if (window.FloatingUIDOM == null) {
|
||||
return;
|
||||
}
|
||||
const { autoUpdate, computePosition, flip, offset, shift, size } =
|
||||
window.FloatingUIDOM;
|
||||
const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
|
||||
const tooltipId = `codex-visualization-tooltip-${
|
||||
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
|
||||
}`;
|
||||
const tooltipGap = 5;
|
||||
const hoverDelay = 700;
|
||||
const skipDelay = 300;
|
||||
let activeTrigger = null;
|
||||
let pendingTrigger = null;
|
||||
let tooltip = null;
|
||||
let openTimeout = null;
|
||||
let autoUpdateCleanup = null;
|
||||
let positionRequest = 0;
|
||||
let skipDelayUntil = 0;
|
||||
|
||||
const getTrigger = (target) =>
|
||||
target?.nodeType === Node.ELEMENT_NODE
|
||||
? target.closest("[data-tooltip]")
|
||||
: null;
|
||||
const containsTarget = (trigger, target) =>
|
||||
target?.nodeType != null && trigger.contains(target);
|
||||
const getContent = (trigger) =>
|
||||
trigger.getAttribute("data-tooltip")?.trim() ?? "";
|
||||
const getPlacement = (trigger) => {
|
||||
const placement = trigger.getAttribute("data-tooltip-placement");
|
||||
return tooltipPlacements.has(placement) ? placement : "top";
|
||||
};
|
||||
const getTooltip = () => {
|
||||
if (tooltip == null) {
|
||||
tooltip = document.createElement("div");
|
||||
tooltip.id = tooltipId;
|
||||
tooltip.className = "tooltip";
|
||||
tooltip.setAttribute("role", "tooltip");
|
||||
}
|
||||
return tooltip;
|
||||
};
|
||||
const setDescribedBy = (trigger, described) => {
|
||||
const ids = new Set(
|
||||
(trigger.getAttribute("aria-describedby") ?? "")
|
||||
.split(/\s+/)
|
||||
.filter(Boolean),
|
||||
);
|
||||
if (described) {
|
||||
ids.add(tooltipId);
|
||||
} else {
|
||||
ids.delete(tooltipId);
|
||||
}
|
||||
if (ids.size === 0) {
|
||||
trigger.removeAttribute("aria-describedby");
|
||||
} else {
|
||||
trigger.setAttribute("aria-describedby", [...ids].join(" "));
|
||||
}
|
||||
};
|
||||
const clearOpenTimeout = () => {
|
||||
if (openTimeout != null) {
|
||||
window.clearTimeout(openTimeout);
|
||||
openTimeout = null;
|
||||
}
|
||||
pendingTrigger = null;
|
||||
};
|
||||
const closeTooltip = (activateSkipDelay = true) => {
|
||||
clearOpenTimeout();
|
||||
positionRequest += 1;
|
||||
autoUpdateCleanup?.();
|
||||
autoUpdateCleanup = null;
|
||||
if (activeTrigger == null) {
|
||||
return;
|
||||
}
|
||||
setDescribedBy(activeTrigger, false);
|
||||
activeTrigger = null;
|
||||
tooltip?.remove();
|
||||
if (activateSkipDelay) {
|
||||
skipDelayUntil = Date.now() + skipDelay;
|
||||
}
|
||||
};
|
||||
const updatePosition = (trigger, floating) => {
|
||||
const request = ++positionRequest;
|
||||
void computePosition(trigger, floating, {
|
||||
middleware: [
|
||||
offset(tooltipGap),
|
||||
flip({ padding: tooltipGap }),
|
||||
shift({ padding: tooltipGap }),
|
||||
size({
|
||||
padding: tooltipGap,
|
||||
apply({ availableHeight, availableWidth, elements }) {
|
||||
elements.floating.style.setProperty(
|
||||
"--tooltip-available-width",
|
||||
`${Math.max(0, availableWidth)}px`,
|
||||
);
|
||||
elements.floating.style.setProperty(
|
||||
"--tooltip-available-height",
|
||||
`${Math.max(0, availableHeight)}px`,
|
||||
);
|
||||
},
|
||||
}),
|
||||
],
|
||||
placement: getPlacement(trigger),
|
||||
strategy: "fixed",
|
||||
})
|
||||
.then(({ x, y }) => {
|
||||
if (activeTrigger !== trigger || request !== positionRequest) {
|
||||
return;
|
||||
}
|
||||
const scale = window.devicePixelRatio || 1;
|
||||
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
|
||||
floating.style.visibility = "visible";
|
||||
})
|
||||
.catch(() => {
|
||||
if (activeTrigger === trigger && request === positionRequest) {
|
||||
closeTooltip(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
const openTooltip = (trigger) => {
|
||||
const content = getContent(trigger);
|
||||
if (!trigger.isConnected || content.length === 0) {
|
||||
return;
|
||||
}
|
||||
clearOpenTimeout();
|
||||
if (activeTrigger === trigger) {
|
||||
return;
|
||||
}
|
||||
closeTooltip();
|
||||
const floating = getTooltip();
|
||||
floating.textContent = content;
|
||||
floating.style.visibility = "hidden";
|
||||
floating.style.transform = "translate(0, 0)";
|
||||
document.body.appendChild(floating);
|
||||
activeTrigger = trigger;
|
||||
setDescribedBy(trigger, true);
|
||||
autoUpdateCleanup = autoUpdate(trigger, floating, () => {
|
||||
updatePosition(trigger, floating);
|
||||
});
|
||||
};
|
||||
const requestOpen = (trigger, immediate = false) => {
|
||||
if (
|
||||
getContent(trigger).length === 0 ||
|
||||
activeTrigger === trigger ||
|
||||
pendingTrigger === trigger
|
||||
) {
|
||||
return;
|
||||
}
|
||||
clearOpenTimeout();
|
||||
pendingTrigger = trigger;
|
||||
const delay =
|
||||
immediate || activeTrigger != null || Date.now() < skipDelayUntil
|
||||
? 0
|
||||
: hoverDelay;
|
||||
if (delay === 0) {
|
||||
openTooltip(trigger);
|
||||
return;
|
||||
}
|
||||
openTimeout = window.setTimeout(() => {
|
||||
openTimeout = null;
|
||||
const getTrigger = (target) =>
|
||||
target?.nodeType === Node.ELEMENT_NODE
|
||||
? target.closest("[data-tooltip]")
|
||||
: null;
|
||||
const containsTarget = (trigger, target) =>
|
||||
target?.nodeType != null && trigger.contains(target);
|
||||
const getContent = (trigger) =>
|
||||
trigger.getAttribute("data-tooltip")?.trim() ?? "";
|
||||
const getPlacement = (trigger) => {
|
||||
const placement = trigger.getAttribute("data-tooltip-placement");
|
||||
return tooltipPlacements.has(placement) ? placement : "top";
|
||||
};
|
||||
const getTooltip = () => {
|
||||
if (tooltip == null) {
|
||||
tooltip = document.createElement("div");
|
||||
tooltip.id = tooltipId;
|
||||
tooltip.className = "tooltip";
|
||||
tooltip.setAttribute("role", "tooltip");
|
||||
}
|
||||
return tooltip;
|
||||
};
|
||||
const setDescribedBy = (trigger, described) => {
|
||||
const ids = new Set(
|
||||
(trigger.getAttribute("aria-describedby") ?? "")
|
||||
.split(/\s+/)
|
||||
.filter(Boolean),
|
||||
);
|
||||
if (described) {
|
||||
ids.add(tooltipId);
|
||||
} else {
|
||||
ids.delete(tooltipId);
|
||||
}
|
||||
if (ids.size === 0) {
|
||||
trigger.removeAttribute("aria-describedby");
|
||||
} else {
|
||||
trigger.setAttribute("aria-describedby", [...ids].join(" "));
|
||||
}
|
||||
};
|
||||
const clearOpenTimeout = () => {
|
||||
if (openTimeout != null) {
|
||||
window.clearTimeout(openTimeout);
|
||||
openTimeout = null;
|
||||
}
|
||||
pendingTrigger = null;
|
||||
openTooltip(trigger);
|
||||
}, delay);
|
||||
};
|
||||
const handlePointerOpen = (event) => {
|
||||
if (event.pointerType === "touch") {
|
||||
return;
|
||||
}
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
requestOpen(trigger);
|
||||
};
|
||||
const handleLeave = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
||||
};
|
||||
const closeTooltip = (activateSkipDelay = true) => {
|
||||
clearOpenTimeout();
|
||||
positionRequest += 1;
|
||||
autoUpdateCleanup?.();
|
||||
autoUpdateCleanup = null;
|
||||
if (activeTrigger == null) {
|
||||
return;
|
||||
}
|
||||
setDescribedBy(activeTrigger, false);
|
||||
activeTrigger = null;
|
||||
tooltip?.remove();
|
||||
if (activateSkipDelay) {
|
||||
skipDelayUntil = Date.now() + skipDelay;
|
||||
}
|
||||
};
|
||||
const updatePosition = (trigger, floating) => {
|
||||
const request = ++positionRequest;
|
||||
void computePosition(trigger, floating, {
|
||||
middleware: [
|
||||
offset(tooltipGap),
|
||||
flip({ padding: tooltipGap }),
|
||||
shift({ padding: tooltipGap }),
|
||||
size({
|
||||
padding: tooltipGap,
|
||||
apply({ availableHeight, availableWidth, elements }) {
|
||||
elements.floating.style.setProperty(
|
||||
"--tooltip-available-width",
|
||||
`${Math.max(0, availableWidth)}px`,
|
||||
);
|
||||
elements.floating.style.setProperty(
|
||||
"--tooltip-available-height",
|
||||
`${Math.max(0, availableHeight)}px`,
|
||||
);
|
||||
},
|
||||
}),
|
||||
],
|
||||
placement: getPlacement(trigger),
|
||||
strategy: "fixed",
|
||||
})
|
||||
.then(({ x, y }) => {
|
||||
if (activeTrigger !== trigger || request !== positionRequest) {
|
||||
return;
|
||||
}
|
||||
const scale = window.devicePixelRatio || 1;
|
||||
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
|
||||
floating.style.visibility = "visible";
|
||||
})
|
||||
.catch(() => {
|
||||
if (activeTrigger === trigger && request === positionRequest) {
|
||||
closeTooltip(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
const openTooltip = (trigger) => {
|
||||
const content = getContent(trigger);
|
||||
if (!trigger.isConnected || content.length === 0) {
|
||||
return;
|
||||
}
|
||||
clearOpenTimeout();
|
||||
if (activeTrigger === trigger) {
|
||||
return;
|
||||
}
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const handleFocusIn = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger?.matches(":focus-visible")) {
|
||||
requestOpen(trigger, true);
|
||||
}
|
||||
};
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const handleContextMenu = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const destroy = () => {
|
||||
closeTooltip(false);
|
||||
document.removeEventListener("pointerover", handlePointerOpen);
|
||||
document.removeEventListener("pointerout", handleLeave);
|
||||
document.removeEventListener("focusin", handleFocusIn);
|
||||
document.removeEventListener("focusout", handleLeave);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.removeEventListener("contextmenu", handleContextMenu);
|
||||
tooltip = null;
|
||||
};
|
||||
const floating = getTooltip();
|
||||
floating.textContent = content;
|
||||
floating.style.visibility = "hidden";
|
||||
floating.style.transform = "translate(0, 0)";
|
||||
document.body.appendChild(floating);
|
||||
activeTrigger = trigger;
|
||||
setDescribedBy(trigger, true);
|
||||
autoUpdateCleanup = autoUpdate(trigger, floating, () => {
|
||||
updatePosition(trigger, floating);
|
||||
});
|
||||
};
|
||||
const requestOpen = (trigger, immediate = false) => {
|
||||
if (
|
||||
getContent(trigger).length === 0 ||
|
||||
activeTrigger === trigger ||
|
||||
pendingTrigger === trigger
|
||||
) {
|
||||
return;
|
||||
}
|
||||
clearOpenTimeout();
|
||||
pendingTrigger = trigger;
|
||||
const delay =
|
||||
immediate || activeTrigger != null || Date.now() < skipDelayUntil
|
||||
? 0
|
||||
: hoverDelay;
|
||||
if (delay === 0) {
|
||||
openTooltip(trigger);
|
||||
return;
|
||||
}
|
||||
openTimeout = window.setTimeout(() => {
|
||||
openTimeout = null;
|
||||
pendingTrigger = null;
|
||||
openTooltip(trigger);
|
||||
}, delay);
|
||||
};
|
||||
const handlePointerOpen = (event) => {
|
||||
if (event.pointerType === "touch") {
|
||||
return;
|
||||
}
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
requestOpen(trigger);
|
||||
};
|
||||
const handleLeave = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const handleFocusIn = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (trigger?.matches(":focus-visible")) {
|
||||
requestOpen(trigger, true);
|
||||
}
|
||||
};
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const handleContextMenu = (event) => {
|
||||
const trigger = getTrigger(event.target);
|
||||
if (activeTrigger === trigger || pendingTrigger === trigger) {
|
||||
closeTooltip();
|
||||
}
|
||||
};
|
||||
const destroy = () => {
|
||||
closeTooltip(false);
|
||||
document.removeEventListener("pointerover", handlePointerOpen);
|
||||
document.removeEventListener("pointerout", handleLeave);
|
||||
document.removeEventListener("focusin", handleFocusIn);
|
||||
document.removeEventListener("focusout", handleLeave);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.removeEventListener("contextmenu", handleContextMenu);
|
||||
tooltip = null;
|
||||
};
|
||||
|
||||
document.addEventListener("pointerover", handlePointerOpen);
|
||||
document.addEventListener("pointerout", handleLeave);
|
||||
document.addEventListener("focusin", handleFocusIn);
|
||||
document.addEventListener("focusout", handleLeave);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener("contextmenu", handleContextMenu);
|
||||
window.addEventListener("pagehide", destroy, { once: true });
|
||||
})();
|
||||
document.addEventListener("pointerover", handlePointerOpen);
|
||||
document.addEventListener("pointerout", handleLeave);
|
||||
document.addEventListener("focusin", handleFocusIn);
|
||||
document.addEventListener("focusout", handleLeave);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener("contextmenu", handleContextMenu);
|
||||
window.addEventListener("pagehide", destroy, { once: true });
|
||||
})();
|
||||
</script>
|
||||
<script id="codex-visualization-lucide" async src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"></script>
|
||||
<script
|
||||
id="codex-visualization-lucide"
|
||||
async
|
||||
src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"
|
||||
></script>
|
||||
<script>
|
||||
(() => {
|
||||
const initialize = () => {
|
||||
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
|
||||
};
|
||||
if (globalThis.lucide != null) {
|
||||
initialize();
|
||||
return;
|
||||
}
|
||||
document
|
||||
.getElementById("codex-visualization-lucide")
|
||||
?.addEventListener("load", initialize, { once: true });
|
||||
})();
|
||||
(() => {
|
||||
const initialize = () => {
|
||||
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
|
||||
};
|
||||
if (globalThis.lucide != null) {
|
||||
initialize();
|
||||
return;
|
||||
}
|
||||
document
|
||||
.getElementById("codex-visualization-lucide")
|
||||
?.addEventListener("load", initialize, { once: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- **Keyboard hints:** Use `key_hint` span helpers for compact, uniformly bold shortcut labels
|
||||
and readable secondary descriptions. Include `+`, `/`, and chord separators in the label's
|
||||
emphasis. The `?` shortcut reference uses readable accent-colored keys
|
||||
without bold. Separate composer footer actions with ` · `.
|
||||
without bold. Separate composer footer actions with `·`.
|
||||
|
||||
# Foreground colors
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ behavior consistent in local and remote builds on all platforms. Bazel sets
|
||||
`runfiles` crate to resolve runfiles via that manifest.
|
||||
|
||||
Function behavior:
|
||||
|
||||
- `cargo_bin`: reads `CARGO_BIN_EXE_*` environment variables (set by Cargo or
|
||||
Bazel) and resolves them via the runfiles manifest when `RUNFILES_MANIFEST_FILE`
|
||||
is present. When not under runfiles, it only accepts absolute paths from
|
||||
@@ -16,5 +17,6 @@ Function behavior:
|
||||
falls back to a `CARGO_MANIFEST_DIR`-relative path for Cargo runs.
|
||||
|
||||
Background:
|
||||
|
||||
- https://bazel.build/docs/runfiles
|
||||
- https://bazel.build/docs/runfiles#runfiles-manifest
|
||||
|
||||
@@ -94,4 +94,4 @@ assert_eq!(out.extracted[0].content, "x");
|
||||
|
||||
- Tags are matched literally and case-sensitively
|
||||
- No nested tag support
|
||||
- A stream can return empty objects.
|
||||
- A stream can return empty objects.
|
||||
|
||||
@@ -221,12 +221,12 @@ Use `turn(...)` when you need low-level turn control (`stream()`, `steer()`,
|
||||
|
||||
These options have the same behavior on sync and async `run(...)` and `turn(...)`:
|
||||
|
||||
| Option | Behavior |
|
||||
| --- | --- |
|
||||
| `personality: Personality \| None = None` | `Personality.friendly` and `Personality.pragmatic` are deprecated and no longer select a style. See [deprecated personality selection](#deprecated-personality-selection). |
|
||||
| `service_tier: str | None = None` | Sets the thread's service tier for this and subsequent turns. |
|
||||
| `turn_service_tier: str | None = None` | Overrides the tier for a newly started turn only. `None` inherits the thread setting; `"default"` selects standard speed. Does not change the thread default and is ignored when input joins an active turn. |
|
||||
| `source: str | None = None` | Labels the caller that initiated a new turn, such as `"review_ui"`. This is metadata; it does not schedule work or grant authority. Ignored when input joins an active turn. |
|
||||
| Option | Behavior |
|
||||
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `personality: Personality \| None = None` | `Personality.friendly` and `Personality.pragmatic` are deprecated and no longer select a style. See [deprecated personality selection](#deprecated-personality-selection). |
|
||||
| `service_tier: str \| None = None` | Sets the thread's service tier for this and subsequent turns. |
|
||||
| `turn_service_tier: str \| None = None` | Overrides the tier for a newly started turn only. `None` inherits the thread setting; `"default"` selects standard speed. Does not change the thread default and is ignored when input joins an active turn. |
|
||||
| `source: str \| None = None` | Labels the caller that initiated a new turn, such as `"review_ui"`. This is metadata; it does not schedule work or grant authority. Ignored when input joins an active turn. |
|
||||
|
||||
`ExternalMessage`, `turn_service_tier`, `source`, and explicit `include_turns`
|
||||
on resume/fork require Codex CLI 0.151.0 or newer. The SDK raises `CodexError`
|
||||
@@ -325,11 +325,11 @@ message = ExternalMessage(
|
||||
result = thread.run(message)
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `tool_name: str` | Required, nonempty name of the tool or application delivering the message. |
|
||||
| `content` | Required text, or a sequence of structured content dictionaries or generated `FunctionCallOutputContentItem` models. Structured image content requires inline data URLs. |
|
||||
| `namespace: str | None = None` | Optional namespace for the tool name. |
|
||||
| Field | Meaning |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `tool_name: str` | Required, nonempty name of the tool or application delivering the message. |
|
||||
| `content` | Required text, or a sequence of structured content dictionaries or generated `FunctionCallOutputContentItem` models. Structured image content requires inline data URLs. |
|
||||
| `namespace: str \| None = None` | Optional namespace for the tool name. |
|
||||
|
||||
Pass one `ExternalMessage` as the complete input to `run(...)` or `turn(...)`.
|
||||
It starts a turn when the thread is idle or joins an active regular turn. It
|
||||
|
||||
@@ -115,26 +115,18 @@ def test_root_fmt_recipes_use_shared_formatter_driver() -> None:
|
||||
)
|
||||
actual = {
|
||||
"working_directory": lines[0],
|
||||
"fmt_comment": next(line for line in reversed(lines[:fmt_index]) if line.startswith("#")),
|
||||
"fmt_commands": [
|
||||
line.strip()
|
||||
for line in lines[fmt_index + 1 : fmt_check_index]
|
||||
if line.strip() and not line.startswith("#")
|
||||
],
|
||||
"fmt_check_comment": next(
|
||||
line for line in reversed(lines[:fmt_check_index]) if line.startswith("#")
|
||||
),
|
||||
"fmt_check_commands": [
|
||||
line.strip() for line in lines[fmt_check_index + 1 : next_recipe_index] if line.strip()
|
||||
],
|
||||
}
|
||||
expected = {
|
||||
"working_directory": 'set working-directory := "codex-rs"',
|
||||
"fmt_comment": (
|
||||
"# Format the justfile, Rust, Bazel/Starlark, Python SDK code, and Python scripts."
|
||||
),
|
||||
"fmt_commands": ["@{{ python }} ../scripts/format.py"],
|
||||
"fmt_check_comment": "# Check formatting without modifying files.",
|
||||
"fmt_check_commands": ["@{{ python }} ../scripts/format.py --check"],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user