Compare commits

..

2 Commits

Author SHA1 Message Date
Michael Zeng
3a22e247d1 Uncouple git-utils from exec-server 2026-04-27 15:19:58 -07:00
Michael Zeng
50ecfd2660 Extract executor filesystem abstraction 2026-04-27 15:16:19 -07:00
706 changed files with 20396 additions and 56334 deletions

View File

@@ -7,7 +7,7 @@ description: Run a GitHub issue digest for openai/codex by feature-area labels,
## Objective
Produce a headline-first, insight-oriented digest of `openai/codex` issues for the requested feature-area labels over the previous 24 hours by default. Honor a different duration when the user asks for one, for example "past week" or "48 hours". Default to a summary-only response; include details only when requested.
Produce a concise, insight-oriented digest of `openai/codex` issues for the requested feature-area labels over the previous 24 hours by default. Honor a different duration when the user asks for one, for example "past week" or "48 hours".
Include only issues that currently have `bug` or `enhancement` plus at least one requested owner label. If the user asks for all areas or all labels, collect `bug`/`enhancement` issues across all labels.
@@ -29,46 +29,21 @@ python3 .codex/skills/codex-issue-digest/scripts/collect_issue_digest.py --label
Use `--window "past week"` or `--window-hours 168` when the user asks for a non-default duration. Use `--all-labels` when the user says all areas or all labels.
2. Use the JSON as the source of truth. It includes new issues, new issue comments, new reactions/upvotes, current labels, current reaction counts, model-ready `summary_inputs`, and detailed `digest_rows`.
3. Choose the output mode from the user's request:
- Default mode: start the report with `## Summary` and do not emit `## Details`.
- Details-upfront mode: if the user asks for details, a table, a full digest, "include details", or similar, start with `## Summary`, then include `## Details`.
- Follow-up details mode: if the user asks for more detail after a summary-only digest, produce `## Details` from the existing collector JSON when it is still available; otherwise rerun the collector.
4. In `## Summary`, write a headline-first executive summary:
- The first nonblank line under `## Summary` must be a single-line headline or judgment, not a bullet. It should be useful even if the reader stops there.
- On quiet days, prefer exactly: `No major issues reported by users.` Use this when there are no elevated rows, no newly repeated theme, and nothing that needs owner action.
- When users are surfacing notable issues, make the headline name the count or theme, for example `Two issues are being surfaced by users:`.
- Immediately under an active headline, list only the issues or themes driving attention, ordered by importance. Start each line with the row's `attention_marker` when present, then a concise owner-readable description and inline issue refs.
- Treat `🔥🔥` as headline-worthy and `🔥` as elevated. Do not add fire emoji yourself; only copy the row's `attention_marker`.
- Keep any extra summary detail after the headline to 1-3 terse lines, only when it adds a decision-relevant caveat, repeated theme, or owner action.
- Do not include routine counts, broad stats, or low-signal table summaries in `## Summary` unless they change the headline. Put metadata and optional counts in `## Details` or the footer.
- In default mode, end the report with a concise prompt such as `Want details? I can expand this into the issue table.` Keep this separate from the summary headline so the headline stays clean.
3. Start the report with `## Summary`, then `## Details`.
4. In `## Summary`, write skim-first headlines:
- Lead with the most important fact or judgment. Do not start with aggregate counts unless the aggregate itself is the story.
- Make the first 1-3 bullets answer "what should owners pay attention to right now?"
- Bold only the critical insight phrase in each high-priority bullet, for example `**GPT-5.5 context is the dominant pressure point**`.
- Keep summary bullets short enough to scan in about 20 seconds.
- Put broad stats near the end of the summary, after the owner-relevant takeaways.
- Say clearly when there is nothing significant to act on.
- Call out any areas or themes receiving lots of user attention.
- Cluster and name themes yourself from `summary_inputs`; the collector intentionally does not hard-code issue categories.
- Use a cluster only when the issues genuinely share the same product problem. If several issues merely share a broad platform or label, describe them individually.
- Do not omit a repeated theme just because its individual issues fall below the details table cutoff. Several similar reports should be called out as a repeated customer concern.
- For single-issue rows, summarize the concern directly instead of calling it a cluster.
- Use inline numbered issue links from each relevant row's `ref_markdown`.
- Example quiet summary:
```markdown
## Summary
No major issues reported by users.
Source: collector v4, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
Want details? I can expand this into the issue table.
```
- Example active summary:
```markdown
## Summary
Two issues are being surfaced by users:
🔥🔥 Terminal launch hangs on startup [1](https://github.com/openai/codex/issues/123)
🔥 Resume switches model providers unexpectedly [2](https://github.com/openai/codex/issues/456)
Source: collector v4, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`.
Want details? I can expand this into the issue table.
```
5. In `## Details`, when details are requested, include a compact table only when useful:
5. In `## Details`, include a compact table only when useful:
- Prefer rows from `digest_rows`; include a `Refs` column using each row's `ref_markdown`.
- Keep the table short; omit low-signal rows when the summary already covers them.
- Use compact columns such as marker, area, type, description, interactions, and refs.
@@ -77,7 +52,7 @@ Want details? I can expand this into the issue table.
6. Use the JSON `attention_marker` exactly. It is empty for normal rows, `🔥` for elevated rows, and `🔥🔥` for very high-attention rows. The actual cutoffs are in `attention_thresholds`.
7. Use inline numbered references where a row or bullet points to issues, for example `Compaction bugs [1](https://github.com/openai/codex/issues/123), [2](https://github.com/openai/codex/issues/456)`. Do not add a separate footnotes section.
8. Label `interactions` as `Interactions`; it counts posts/comments/reactions during the requested window, not unique people.
9. Mention the collector `script_version`, repo checkout `git_head`, and time window in one compact source line. In default mode, put this before the details prompt so the final line still asks whether the user wants details. In details-upfront mode, it can be the footer.
9. Mention the collector `script_version`, repo checkout `git_head`, and time window in the digest footer or final line.
## Reaction Handling
@@ -89,7 +64,7 @@ GitHub issue search is still seeded by issue `updated_at`, so a purely reaction-
## Attention Markers
The collector scales attention markers by the requested time window. The baseline is 5 human user interactions for `🔥` and 10 for `🔥🔥` over 24 hours; longer or shorter windows scale those cutoffs linearly and round up. For example, a one-week report uses 35 and 70 interactions. Human user interactions are human-authored new issue posts, human-authored new comments, and human reactions created during the window, including upvotes. Bot posts and bot reactions are excluded. In prose, explain this as high user interaction rather than naming the emoji.
The collector scales attention markers by the requested time window. The baseline is 10 human user interactions for `🔥` and 20 for `🔥🔥` over 24 hours; longer or shorter windows scale those cutoffs linearly and round up. For example, a one-week report uses 70 and 140 interactions. Human user interactions are human-authored new issue posts, human-authored new comments, and human reactions created during the window, including upvotes. Bot posts and bot reactions are excluded. In prose, explain this as high user interaction rather than naming the emoji.
## Freshness

View File

@@ -11,12 +11,12 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from urllib.parse import quote
SCRIPT_VERSION = 4
SCRIPT_VERSION = 2
QUALIFYING_KIND_LABELS = ("bug", "enhancement")
REACTION_KEYS = ("+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes")
BASE_ATTENTION_WINDOW_HOURS = 24.0
ONE_ATTENTION_INTERACTION_THRESHOLD = 5
TWO_ATTENTION_INTERACTION_THRESHOLD = 10
ONE_ATTENTION_INTERACTION_THRESHOLD = 10
TWO_ATTENTION_INTERACTION_THRESHOLD = 20
ALL_LABEL_PHRASES = {"all", "all areas", "all labels", "all-areas", "all-labels", "*"}
@@ -305,7 +305,6 @@ def search_issue_numbers(queries, limit):
numbers = {}
for query in queries:
page = 1
seen_for_query = 0
while True:
payload = gh_json(
[
@@ -316,10 +315,6 @@ def search_issue_numbers(queries, limit):
"-f",
f"q={query}",
"-f",
"sort=updated",
"-f",
"order=desc",
"-f",
"per_page=100",
"-f",
f"page={page}",
@@ -336,8 +331,7 @@ def search_issue_numbers(queries, limit):
number = item.get("number")
if isinstance(number, int):
numbers[number] = str(item.get("updated_at") or "")
seen_for_query += 1
if len(items) < 100 or seen_for_query >= limit:
if len(items) < 100 or len(numbers) >= limit:
break
page += 1
ordered = sorted(

View File

@@ -51,77 +51,6 @@ def test_normalize_requested_labels_accepts_all_area_phrases():
)
def test_search_issue_numbers_requests_updated_sort(monkeypatch):
calls = []
def fake_gh_json(args):
calls.append(args)
return {
"items": [
{"number": 1, "updated_at": "2026-04-25T00:00:00Z"},
]
}
monkeypatch.setattr(collect_issue_digest, "gh_json", fake_gh_json)
assert collect_issue_digest.search_issue_numbers(["query"], limit=10) == [1]
assert "-f" in calls[0]
assert "sort=updated" in calls[0]
assert "order=desc" in calls[0]
def test_search_issue_numbers_applies_limit_per_query(monkeypatch):
calls = []
def fake_gh_json(args):
calls.append(args)
query = next(
value.removeprefix("q=") for value in args if value.startswith("q=")
)
page = int(
next(
value.removeprefix("page=")
for value in args
if value.startswith("page=")
)
)
base = 10_000 if query == "first" else 20_000
offset = (page - 1) * 100
return {
"items": [
{
"number": base + offset + idx,
"updated_at": f"2026-04-25T00:{idx:02d}:00Z",
}
for idx in range(100)
]
}
monkeypatch.setattr(collect_issue_digest, "gh_json", fake_gh_json)
collect_issue_digest.search_issue_numbers(["first", "second"], limit=150)
queried_pages = [
(
next(
value.removeprefix("q=") for value in args if value.startswith("q=")
),
next(
value.removeprefix("page=")
for value in args
if value.startswith("page=")
),
)
for args in calls
]
assert queried_pages == [
("first", "1"),
("first", "2"),
("second", "1"),
("second", "2"),
]
def test_summarize_issue_keeps_new_comments_and_reaction_signals():
since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since")
until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until")
@@ -298,19 +227,19 @@ def test_parse_duration_hours_accepts_common_phrases():
def test_attention_thresholds_scale_by_window_length():
one_day = collect_issue_digest.attention_thresholds_for_window(24)
assert one_day["elevated"] == 5
assert one_day["very_high"] == 10
assert one_day["elevated"] == 10
assert one_day["very_high"] == 20
half_day = collect_issue_digest.attention_thresholds_for_window(12)
assert half_day["elevated"] == 3
assert half_day["very_high"] == 5
assert half_day["elevated"] == 5
assert half_day["very_high"] == 10
week = collect_issue_digest.attention_thresholds_for_window(168)
assert week["elevated"] == 35
assert week["very_high"] == 70
assert collect_issue_digest.attention_marker_for(34, week) == ""
assert collect_issue_digest.attention_marker_for(35, week) == "🔥"
assert collect_issue_digest.attention_marker_for(70, week) == "🔥🔥"
assert week["elevated"] == 70
assert week["very_high"] == 140
assert collect_issue_digest.attention_marker_for(69, week) == ""
assert collect_issue_digest.attention_marker_for(107, week) == "🔥"
assert collect_issue_digest.attention_marker_for(140, week) == "🔥🔥"
def test_fetch_comments_uses_since_filter_and_page_cap(monkeypatch):
@@ -371,7 +300,7 @@ def test_attention_markers_count_human_user_interactions():
"user": {"login": f"user-{idx}"},
"body": "same here",
}
for idx in range(4)
for idx in range(9)
]
comments.append(
{
@@ -393,8 +322,8 @@ def test_attention_markers_count_human_user_interactions():
comment_chars=100,
)
assert summary["user_interactions"] == 5
assert summary["activity"]["new_human_comments"] == 4
assert summary["user_interactions"] == 10
assert summary["activity"]["new_human_comments"] == 9
assert summary["attention"] is True
assert summary["attention_level"] == 1
assert summary["attention_marker"] == "🔥"
@@ -408,7 +337,7 @@ def test_attention_markers_count_human_user_interactions():
"user": {"login": f"extra-user-{idx}"},
"body": "also seeing this",
}
for idx in range(100, 106)
for idx in range(11)
)
summary = collect_issue_digest.summarize_issue(
@@ -421,7 +350,7 @@ def test_attention_markers_count_human_user_interactions():
comment_chars=100,
)
assert summary["user_interactions"] == 10
assert summary["user_interactions"] == 20
assert summary["attention_level"] == 2
assert summary["attention_marker"] == "🔥🔥"

View File

@@ -1,6 +1,6 @@
# External (non-OpenAI) Pull Request Requirements
External code contributions are by invitation only. Please read the dedicated "Contributing" markdown file for details:
Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md
If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes.

View File

@@ -24,9 +24,7 @@ jobs:
build-windows-binaries:
name: Build Windows binaries - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ${{ matrix.runs_on }}
# Windows release builds can exceed an hour on fat-LTO mainline releases,
# so keep the timeout aligned with the top-level release build headroom.
timeout-minutes: 90
timeout-minutes: 60
permissions:
contents: read
defaults:
@@ -139,7 +137,7 @@ jobs:
- build-windows-binaries
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 90
timeout-minutes: 60
permissions:
contents: read
id-token: write

View File

@@ -49,9 +49,7 @@ jobs:
needs: tag-check
name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
# Release builds can take a long time, so leave some headroom to avoid
# having to restart the full workflow due to a timeout.
timeout-minutes: 90
timeout-minutes: 60
permissions:
contents: read
id-token: write

View File

@@ -19,12 +19,6 @@ In the codex-rs folder where the rust code lives:
- You can run `just argument-comment-lint` to run the lint check locally. This is powered by Bazel, so running it the first time can be slow if Bazel is not warmed up, though incremental invocations should take <15s. Most of the time, it is best to update the PR and let CI take responsibility for checking this (or run it asynchronously in the background after submitting the PR). Note CI checks all three platforms, which the local run does not.
- When possible, make `match` statements exhaustive and avoid wildcard arms.
- Newly added traits should include doc comments that explain their role and how implementations are expected to use them.
- Discourage both `#[async_trait]` and `#[allow(async_fn_in_trait)]` in Rust traits.
- Prefer native RPITIT trait methods with explicit `Send` bounds on the returned future, as in `3c7f013f9735` / `#16630`.
- Preferred trait shape:
`fn foo(&self, ...) -> impl std::future::Future<Output = T> + Send;`
- Implementations may still use `async fn foo(&self, ...) -> T` when they satisfy that contract.
- Do not use `#[allow(async_fn_in_trait)]` as a shortcut around spelling the future contract explicitly.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
- Prefer private modules and explicitly exported public crate API.

1
MODULE.bazel.lock generated
View File

@@ -1560,7 +1560,6 @@
"system-deps_7.0.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5\"},{\"features\":[\"targets\"],\"name\":\"cfg-expr\",\"req\":\">=0.17, <0.21\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"pkg-config\",\"req\":\"^0.3.25\"},{\"default_features\":false,\"features\":[\"parse\",\"std\"],\"name\":\"toml\",\"req\":\"^0.9\"},{\"name\":\"version-compare\",\"req\":\"^0.2\"}],\"features\":{}}",
"tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}",
"tar_0.4.44": "{\"dependencies\":[{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}",
"tar_0.4.45": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"astral-tokio-tar\",\"req\":\"^0.5\"},{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}",
"target-lexicon_0.13.3": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"arch_z80\":[],\"arch_zkasm\":[],\"default\":[],\"serde_support\":[\"serde\",\"std\"],\"std\":[]}}",
"tempfile_3.27.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\">=0.3.0, <0.5\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.4\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}",
"temporal_capi_0.1.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"diplomat\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"name\":\"diplomat-runtime\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"unstable\"],\"name\":\"icu_calendar\",\"req\":\"^2.1.0\"},{\"name\":\"icu_locale\",\"req\":\"^2.1.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.19\"},{\"default_features\":false,\"name\":\"temporal_rs\",\"req\":\"^0.1.2\"},{\"name\":\"timezone_provider\",\"req\":\"^0.1.2\"},{\"name\":\"writeable\",\"req\":\"^0.6.0\"},{\"name\":\"zoneinfo64\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"compiled_data\":[\"temporal_rs/compiled_data\"],\"zoneinfo64\":[\"dep:zoneinfo64\",\"timezone_provider/zoneinfo64\"]}}",

View File

@@ -92,4 +92,4 @@ quoted_args=""
for arg in "$@"; do
quoted_args+=" $(printf '%q' "$arg")"
done
docker exec -it "$CONTAINER_NAME" bash -c "cd \"/app$WORK_DIR\" && codex --sandbox workspace-write --ask-for-approval on-request ${quoted_args}"
docker exec -it "$CONTAINER_NAME" bash -c "cd \"/app$WORK_DIR\" && codex --full-auto ${quoted_args}"

141
codex-rs/Cargo.lock generated
View File

@@ -1748,21 +1748,6 @@ dependencies = [
"unicode-width 0.2.1",
]
[[package]]
name = "codex-agent-graph-store"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-protocol",
"codex-state",
"pretty_assertions",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "codex-agent-identity"
version = "0.0.0"
@@ -1868,16 +1853,12 @@ dependencies = [
"codex-core-plugins",
"codex-device-key",
"codex-exec-server",
"codex-external-agent-migration",
"codex-external-agent-sessions",
"codex-features",
"codex-feedback",
"codex-file-search",
"codex-git-utils",
"codex-hooks",
"codex-login",
"codex-mcp",
"codex-memories-write",
"codex-model-provider",
"codex-model-provider-info",
"codex-models-manager",
@@ -1899,7 +1880,6 @@ dependencies = [
"codex-utils-rustls-provider",
"constant_time_eq 0.3.1",
"core_test_support",
"flate2",
"futures",
"gethostname",
"hmac",
@@ -1915,7 +1895,6 @@ dependencies = [
"serial_test",
"sha2",
"shlex",
"tar",
"tempfile",
"thiserror 2.0.18",
"time",
@@ -2137,7 +2116,6 @@ dependencies = [
"codex-login",
"codex-mcp",
"codex-mcp-server",
"codex-memories-write",
"codex-models-manager",
"codex-protocol",
"codex-responses-api-proxy",
@@ -2311,8 +2289,8 @@ dependencies = [
"base64 0.22.1",
"codex-app-server-protocol",
"codex-execpolicy",
"codex-executor-fs",
"codex-features",
"codex-file-system",
"codex-git-utils",
"codex-model-provider-info",
"codex-network-proxy",
@@ -2393,6 +2371,7 @@ dependencies = [
"codex-login",
"codex-mcp",
"codex-memories-read",
"codex-memories-write",
"codex-model-provider",
"codex-model-provider-info",
"codex-models-manager",
@@ -2405,6 +2384,7 @@ dependencies = [
"codex-rollout",
"codex-rollout-trace",
"codex-sandboxing",
"codex-secrets",
"codex-shell-command",
"codex-shell-escalation",
"codex-state",
@@ -2480,23 +2460,6 @@ dependencies = [
"zstd 0.13.3",
]
[[package]]
name = "codex-core-api"
version = "0.0.0"
dependencies = [
"codex-analytics",
"codex-arg0",
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
"codex-login",
"codex-model-provider-info",
"codex-models-manager",
"codex-protocol",
"codex-utils-absolute-path",
]
[[package]]
name = "codex-core-plugins"
version = "0.0.0"
@@ -2516,13 +2479,11 @@ dependencies = [
"codex-utils-absolute-path",
"codex-utils-plugins",
"dirs",
"flate2",
"libc",
"pretty_assertions",
"reqwest",
"serde",
"serde_json",
"tar",
"tempfile",
"thiserror 2.0.18",
"tokio",
@@ -2648,7 +2609,7 @@ dependencies = [
"bytes",
"codex-app-server-protocol",
"codex-client",
"codex-file-system",
"codex-executor-fs",
"codex-protocol",
"codex-sandboxing",
"codex-test-binary-support",
@@ -2708,6 +2669,17 @@ dependencies = [
"tempfile",
]
[[package]]
name = "codex-executor-fs"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-protocol",
"codex-utils-absolute-path",
"serde",
"tokio",
]
[[package]]
name = "codex-experimental-api-macros"
version = "0.0.0"
@@ -2717,32 +2689,6 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "codex-external-agent-migration"
version = "0.0.0"
dependencies = [
"codex-hooks",
"pretty_assertions",
"serde_json",
"serde_yaml",
"tempfile",
"toml 0.9.11+spec-1.1.0",
]
[[package]]
name = "codex-external-agent-sessions"
version = "0.0.0"
dependencies = [
"chrono",
"codex-app-server-protocol",
"codex-protocol",
"codex-utils-output-truncation",
"serde",
"serde_json",
"sha2",
"tempfile",
]
[[package]]
name = "codex-features"
version = "0.0.0"
@@ -2785,23 +2731,14 @@ dependencies = [
"tokio",
]
[[package]]
name = "codex-file-system"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-protocol",
"codex-utils-absolute-path",
"serde",
]
[[package]]
name = "codex-git-utils"
version = "0.0.0"
dependencies = [
"anyhow",
"assert_matches",
"chrono",
"codex-file-system",
"codex-executor-fs",
"codex-protocol",
"codex-utils-absolute-path",
"futures",
@@ -2826,7 +2763,6 @@ dependencies = [
"anyhow",
"chrono",
"codex-config",
"codex-plugin",
"codex-protocol",
"codex-utils-absolute-path",
"futures",
@@ -2863,7 +2799,6 @@ dependencies = [
"cc",
"clap",
"codex-core",
"codex-process-hardening",
"codex-protocol",
"codex-sandboxing",
"codex-utils-absolute-path",
@@ -2913,7 +2848,6 @@ dependencies = [
"codex-terminal-detection",
"codex-utils-template",
"core_test_support",
"jsonwebtoken",
"keyring",
"once_cell",
"os_info",
@@ -2977,7 +2911,9 @@ dependencies = [
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
"codex-login",
"codex-models-manager",
"codex-protocol",
"codex-shell-command",
"codex-utils-absolute-path",
@@ -3019,33 +2955,18 @@ version = "0.0.0"
dependencies = [
"anyhow",
"chrono",
"codex-backend-client",
"codex-config",
"codex-core",
"codex-features",
"codex-git-utils",
"codex-login",
"codex-models-manager",
"codex-otel",
"codex-protocol",
"codex-rollout",
"codex-rollout-trace",
"codex-secrets",
"codex-state",
"codex-terminal-detection",
"codex-utils-absolute-path",
"codex-utils-output-truncation",
"codex-utils-template",
"core_test_support",
"futures",
"pretty_assertions",
"serde",
"serde_json",
"tempfile",
"tokio",
"tracing",
"uuid",
"wiremock",
]
[[package]]
@@ -3196,7 +3117,6 @@ dependencies = [
name = "codex-plugin"
version = "0.0.0"
dependencies = [
"codex-config",
"codex-utils-absolute-path",
"codex-utils-plugins",
"thiserror 2.0.18",
@@ -3508,16 +3428,6 @@ dependencies = [
"tempfile",
]
[[package]]
name = "codex-thread-manager-sample"
version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"codex-core-api",
"tracing",
]
[[package]]
name = "codex-thread-store"
version = "0.0.0"
@@ -3876,8 +3786,6 @@ version = "0.0.0"
dependencies = [
"pretty_assertions",
"regex-lite",
"serde",
"serde_json",
]
[[package]]
@@ -4153,7 +4061,6 @@ dependencies = [
"assert_cmd",
"base64 0.22.1",
"codex-arg0",
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
@@ -12373,16 +12280,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]]
name = "tar"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
dependencies = [
"filetime",
"libc",
]
[[package]]
name = "target-lexicon"
version = "0.13.3"

View File

@@ -2,7 +2,6 @@
members = [
"aws-auth",
"analytics",
"agent-graph-store",
"agent-identity",
"backend-client",
"ansi-escape",
@@ -32,18 +31,15 @@ members = [
"shell-escalation",
"skills",
"core",
"core-api",
"core-plugins",
"core-skills",
"hooks",
"secrets",
"exec",
"file-system",
"executor-fs",
"exec-server",
"execpolicy",
"execpolicy-legacy",
"external-agent-migration",
"external-agent-sessions",
"keyring-store",
"file-search",
"linux-sandbox",
@@ -99,7 +95,6 @@ members = [
"state",
"terminal-detection",
"test-binary-support",
"thread-manager-sample",
"thread-store",
"uds",
"codex-experimental-api-macros",
@@ -121,7 +116,6 @@ license = "Apache-2.0"
# Internal
app_test_support = { path = "app-server/tests/common" }
codex-analytics = { path = "analytics" }
codex-agent-graph-store = { path = "agent-graph-store" }
codex-agent-identity = { path = "agent-identity" }
codex-ansi-escape = { path = "ansi-escape" }
codex-api = { path = "codex-api" }
@@ -145,16 +139,13 @@ codex-code-mode = { path = "code-mode" }
codex-config = { path = "config" }
codex-connectors = { path = "connectors" }
codex-core = { path = "core" }
codex-core-api = { path = "core-api" }
codex-core-plugins = { path = "core-plugins" }
codex-core-skills = { path = "core-skills" }
codex-device-key = { path = "device-key" }
codex-exec = { path = "exec" }
codex-file-system = { path = "file-system" }
codex-executor-fs = { path = "executor-fs" }
codex-exec-server = { path = "exec-server" }
codex-execpolicy = { path = "execpolicy" }
codex-external-agent-migration = { path = "external-agent-migration" }
codex-external-agent-sessions = { path = "external-agent-sessions" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-features = { path = "features" }
codex-feedback = { path = "feedback" }
@@ -269,7 +260,6 @@ encoding_rs = "0.8.35"
env-flags = "0.1.1"
env_logger = "0.11.9"
eventsource-stream = "0.2.3"
flate2 = "1.1.8"
futures = { version = "0.3", default-features = false }
gethostname = "1.1.0"
gix = { version = "0.81.0", default-features = false, features = ["sha1"] }
@@ -362,7 +352,6 @@ strum_macros = "0.28.0"
supports-color = "3.0.2"
syntect = "5"
sys-locale = "0.3.2"
tar = { version = "=0.4.45", default-features = false }
tempfile = "3.23.0"
test-log = "0.2.19"
textwrap = "0.16.2"
@@ -454,7 +443,6 @@ unwrap_used = "deny"
# silence the false positive here instead of deleting a real dependency.
[workspace.metadata.cargo-shear]
ignored = [
"codex-agent-graph-store",
"icu_provider",
"openssl-sys",
"codex-utils-readiness",

View File

@@ -59,22 +59,19 @@ To test to see what happens when a command is run under the sandbox provided by
```
# macOS
codex sandbox macos [--log-denials] [COMMAND]...
codex sandbox macos [--full-auto] [--log-denials] [COMMAND]...
# Linux
codex sandbox linux [COMMAND]...
codex sandbox linux [--full-auto] [COMMAND]...
# Windows
codex sandbox windows [COMMAND]...
codex sandbox windows [--full-auto] [COMMAND]...
# Legacy aliases
codex debug seatbelt [--log-denials] [COMMAND]...
codex debug landlock [COMMAND]...
codex debug seatbelt [--full-auto] [--log-denials] [COMMAND]...
codex debug landlock [--full-auto] [COMMAND]...
```
To try a writable legacy sandbox mode with these commands, pass an explicit config override such
as `-c 'sandbox_mode="workspace-write"'`.
### Selecting a sandbox policy via `--sandbox`
The Rust CLI exposes a dedicated `--sandbox` (`-s`) flag that lets you pick the sandbox policy **without** having to reach for the generic `-c/--config` option:

View File

@@ -1,6 +0,0 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "agent-graph-store",
crate_name = "codex_agent_graph_store",
)

View File

@@ -1,25 +0,0 @@
[package]
edition.workspace = true
license.workspace = true
name = "codex-agent-graph-store"
version.workspace = true
[lib]
name = "codex_agent_graph_store"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
async-trait = { workspace = true }
codex-protocol = { workspace = true }
codex-state = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

View File

@@ -1,20 +0,0 @@
/// Result type returned by agent graph store operations.
pub type AgentGraphStoreResult<T> = Result<T, AgentGraphStoreError>;
/// Error type shared by agent graph store implementations.
#[derive(Debug, thiserror::Error)]
pub enum AgentGraphStoreError {
/// The caller supplied invalid request data.
#[error("invalid agent graph store request: {message}")]
InvalidRequest {
/// User-facing explanation of the invalid request.
message: String,
},
/// Catch-all for implementation failures that do not fit a more specific category.
#[error("agent graph store internal error: {message}")]
Internal {
/// User-facing explanation of the implementation failure.
message: String,
},
}

View File

@@ -1,12 +0,0 @@
//! Storage-neutral parent/child topology for thread-spawned agents.
mod error;
mod local;
mod store;
mod types;
pub use error::AgentGraphStoreError;
pub use error::AgentGraphStoreResult;
pub use local::LocalAgentGraphStore;
pub use store::AgentGraphStore;
pub use types::ThreadSpawnEdgeStatus;

View File

@@ -1,325 +0,0 @@
use async_trait::async_trait;
use codex_protocol::ThreadId;
use codex_state::StateRuntime;
use std::sync::Arc;
use crate::AgentGraphStore;
use crate::AgentGraphStoreError;
use crate::AgentGraphStoreResult;
use crate::ThreadSpawnEdgeStatus;
/// SQLite-backed implementation of [`AgentGraphStore`] using an existing state runtime.
#[derive(Clone)]
pub struct LocalAgentGraphStore {
state_db: Arc<StateRuntime>,
}
impl std::fmt::Debug for LocalAgentGraphStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LocalAgentGraphStore")
.field("codex_home", &self.state_db.codex_home())
.finish_non_exhaustive()
}
}
impl LocalAgentGraphStore {
/// Create a local graph store from an already-initialized state runtime.
pub fn new(state_db: Arc<StateRuntime>) -> Self {
Self { state_db }
}
}
#[async_trait]
impl AgentGraphStore for LocalAgentGraphStore {
async fn upsert_thread_spawn_edge(
&self,
parent_thread_id: ThreadId,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()> {
self.state_db
.upsert_thread_spawn_edge(parent_thread_id, child_thread_id, to_state_status(status))
.await
.map_err(internal_error)
}
async fn set_thread_spawn_edge_status(
&self,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()> {
self.state_db
.set_thread_spawn_edge_status(child_thread_id, to_state_status(status))
.await
.map_err(internal_error)
}
async fn list_thread_spawn_children(
&self,
parent_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>> {
if let Some(status) = status_filter {
return self
.state_db
.list_thread_spawn_children_with_status(parent_thread_id, to_state_status(status))
.await
.map_err(internal_error);
}
self.state_db
.list_thread_spawn_children(parent_thread_id)
.await
.map_err(internal_error)
}
async fn list_thread_spawn_descendants(
&self,
root_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>> {
match status_filter {
Some(status) => self
.state_db
.list_thread_spawn_descendants_with_status(root_thread_id, to_state_status(status))
.await
.map_err(internal_error),
None => self
.state_db
.list_thread_spawn_descendants(root_thread_id)
.await
.map_err(internal_error),
}
}
}
fn to_state_status(status: ThreadSpawnEdgeStatus) -> codex_state::DirectionalThreadSpawnEdgeStatus {
match status {
ThreadSpawnEdgeStatus::Open => codex_state::DirectionalThreadSpawnEdgeStatus::Open,
ThreadSpawnEdgeStatus::Closed => codex_state::DirectionalThreadSpawnEdgeStatus::Closed,
}
}
fn internal_error(err: impl std::fmt::Display) -> AgentGraphStoreError {
AgentGraphStoreError::Internal {
message: err.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_state::DirectionalThreadSpawnEdgeStatus;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
struct TestRuntime {
state_db: Arc<StateRuntime>,
_codex_home: TempDir,
}
fn thread_id(suffix: u128) -> ThreadId {
ThreadId::from_string(&format!("00000000-0000-0000-0000-{suffix:012}"))
.expect("valid thread id")
}
async fn state_runtime() -> TestRuntime {
let codex_home = TempDir::new().expect("tempdir should be created");
let state_db =
StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string())
.await
.expect("state db should initialize");
TestRuntime {
state_db,
_codex_home: codex_home,
}
}
#[tokio::test]
async fn local_store_upserts_and_lists_direct_children_with_status_filters() {
let fixture = state_runtime().await;
let state_db = fixture.state_db;
let store = LocalAgentGraphStore::new(state_db.clone());
let parent_thread_id = thread_id(/*suffix*/ 1);
let first_child_thread_id = thread_id(/*suffix*/ 2);
let second_child_thread_id = thread_id(/*suffix*/ 3);
store
.upsert_thread_spawn_edge(
parent_thread_id,
second_child_thread_id,
ThreadSpawnEdgeStatus::Closed,
)
.await
.expect("closed child edge should insert");
store
.upsert_thread_spawn_edge(
parent_thread_id,
first_child_thread_id,
ThreadSpawnEdgeStatus::Open,
)
.await
.expect("open child edge should insert");
let all_children = store
.list_thread_spawn_children(parent_thread_id, /*status_filter*/ None)
.await
.expect("all children should load");
assert_eq!(
all_children,
vec![first_child_thread_id, second_child_thread_id]
);
let open_children = store
.list_thread_spawn_children(parent_thread_id, Some(ThreadSpawnEdgeStatus::Open))
.await
.expect("open children should load");
let state_open_children = state_db
.list_thread_spawn_children_with_status(
parent_thread_id,
DirectionalThreadSpawnEdgeStatus::Open,
)
.await
.expect("state open children should load");
assert_eq!(open_children, state_open_children);
assert_eq!(open_children, vec![first_child_thread_id]);
let closed_children = store
.list_thread_spawn_children(parent_thread_id, Some(ThreadSpawnEdgeStatus::Closed))
.await
.expect("closed children should load");
assert_eq!(closed_children, vec![second_child_thread_id]);
}
#[tokio::test]
async fn local_store_updates_edge_status() {
let fixture = state_runtime().await;
let state_db = fixture.state_db;
let store = LocalAgentGraphStore::new(state_db);
let parent_thread_id = thread_id(/*suffix*/ 10);
let child_thread_id = thread_id(/*suffix*/ 11);
store
.upsert_thread_spawn_edge(
parent_thread_id,
child_thread_id,
ThreadSpawnEdgeStatus::Open,
)
.await
.expect("child edge should insert");
store
.set_thread_spawn_edge_status(child_thread_id, ThreadSpawnEdgeStatus::Closed)
.await
.expect("child edge should close");
let open_children = store
.list_thread_spawn_children(parent_thread_id, Some(ThreadSpawnEdgeStatus::Open))
.await
.expect("open children should load");
assert_eq!(open_children, Vec::<ThreadId>::new());
let closed_children = store
.list_thread_spawn_children(parent_thread_id, Some(ThreadSpawnEdgeStatus::Closed))
.await
.expect("closed children should load");
assert_eq!(closed_children, vec![child_thread_id]);
}
#[tokio::test]
async fn local_store_lists_descendants_breadth_first_with_status_filters() {
let fixture = state_runtime().await;
let state_db = fixture.state_db;
let store = LocalAgentGraphStore::new(state_db.clone());
let root_thread_id = thread_id(/*suffix*/ 20);
let later_child_thread_id = thread_id(/*suffix*/ 22);
let earlier_child_thread_id = thread_id(/*suffix*/ 21);
let closed_grandchild_thread_id = thread_id(/*suffix*/ 23);
let open_grandchild_thread_id = thread_id(/*suffix*/ 24);
let closed_child_thread_id = thread_id(/*suffix*/ 25);
let closed_great_grandchild_thread_id = thread_id(/*suffix*/ 26);
for (parent_thread_id, child_thread_id, status) in [
(
root_thread_id,
later_child_thread_id,
ThreadSpawnEdgeStatus::Open,
),
(
root_thread_id,
earlier_child_thread_id,
ThreadSpawnEdgeStatus::Open,
),
(
earlier_child_thread_id,
open_grandchild_thread_id,
ThreadSpawnEdgeStatus::Open,
),
(
later_child_thread_id,
closed_grandchild_thread_id,
ThreadSpawnEdgeStatus::Closed,
),
(
root_thread_id,
closed_child_thread_id,
ThreadSpawnEdgeStatus::Closed,
),
(
closed_child_thread_id,
closed_great_grandchild_thread_id,
ThreadSpawnEdgeStatus::Closed,
),
] {
store
.upsert_thread_spawn_edge(parent_thread_id, child_thread_id, status)
.await
.expect("edge should insert");
}
let all_descendants = store
.list_thread_spawn_descendants(root_thread_id, /*status_filter*/ None)
.await
.expect("all descendants should load");
assert_eq!(
all_descendants,
vec![
earlier_child_thread_id,
later_child_thread_id,
closed_child_thread_id,
closed_grandchild_thread_id,
open_grandchild_thread_id,
closed_great_grandchild_thread_id,
]
);
let open_descendants = store
.list_thread_spawn_descendants(root_thread_id, Some(ThreadSpawnEdgeStatus::Open))
.await
.expect("open descendants should load");
let state_open_descendants = state_db
.list_thread_spawn_descendants_with_status(
root_thread_id,
DirectionalThreadSpawnEdgeStatus::Open,
)
.await
.expect("state open descendants should load");
assert_eq!(open_descendants, state_open_descendants);
assert_eq!(
open_descendants,
vec![
earlier_child_thread_id,
later_child_thread_id,
open_grandchild_thread_id,
]
);
let closed_descendants = store
.list_thread_spawn_descendants(root_thread_id, Some(ThreadSpawnEdgeStatus::Closed))
.await
.expect("closed descendants should load");
assert_eq!(
closed_descendants,
vec![closed_child_thread_id, closed_great_grandchild_thread_id]
);
}
}

View File

@@ -1,55 +0,0 @@
use async_trait::async_trait;
use codex_protocol::ThreadId;
use crate::AgentGraphStoreResult;
use crate::ThreadSpawnEdgeStatus;
/// Storage-neutral boundary for persisted thread-spawn parent/child topology.
///
/// Implementations are expected to return stable ordering for list methods so callers can merge
/// persisted graph state with live in-memory state without introducing nondeterministic output.
#[async_trait]
pub trait AgentGraphStore: Send + Sync {
/// Insert or replace the directional parent/child edge for a spawned thread.
///
/// `child_thread_id` has at most one persisted parent. Re-inserting the same child should
/// update both the parent and status to match the supplied values.
async fn upsert_thread_spawn_edge(
&self,
parent_thread_id: ThreadId,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()>;
/// Update the persisted lifecycle status of a spawned thread's incoming edge.
///
/// Implementations should treat missing children as a successful no-op.
async fn set_thread_spawn_edge_status(
&self,
child_thread_id: ThreadId,
status: ThreadSpawnEdgeStatus,
) -> AgentGraphStoreResult<()>;
/// List direct spawned children of a parent thread.
///
/// When `status_filter` is `Some`, only child edges with that exact status are returned. When
/// it is `None`, all direct child edges are returned regardless of status, including statuses
/// that may be added by a future store implementation.
async fn list_thread_spawn_children(
&self,
parent_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>>;
/// List spawned descendants breadth-first by depth, then by thread id.
///
/// `status_filter` is applied to every traversed edge, not just to the returned descendants.
/// For example, `Some(Open)` walks only open edges, so descendants under a closed edge are not
/// included even if their own incoming edge is open. `None` walks and returns every persisted
/// edge regardless of status.
async fn list_thread_spawn_descendants(
&self,
root_thread_id: ThreadId,
status_filter: Option<ThreadSpawnEdgeStatus>,
) -> AgentGraphStoreResult<Vec<ThreadId>>;
}

View File

@@ -1,42 +0,0 @@
use serde::Deserialize;
use serde::Serialize;
/// Lifecycle status attached to a directional thread-spawn edge.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ThreadSpawnEdgeStatus {
/// The child thread is still live or resumable as an open spawned agent.
Open,
/// The child thread has been closed from the parent/child graph's perspective.
Closed,
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn thread_spawn_edge_status_serializes_as_snake_case() {
assert_eq!(
serde_json::to_string(&ThreadSpawnEdgeStatus::Open)
.expect("open status should serialize"),
"\"open\""
);
assert_eq!(
serde_json::to_string(&ThreadSpawnEdgeStatus::Closed)
.expect("closed status should serialize"),
"\"closed\""
);
assert_eq!(
serde_json::from_str::<ThreadSpawnEdgeStatus>("\"open\"")
.expect("open status should deserialize"),
ThreadSpawnEdgeStatus::Open
);
assert_eq!(
serde_json::from_str::<ThreadSpawnEdgeStatus>("\"closed\"")
.expect("closed status should deserialize"),
ThreadSpawnEdgeStatus::Closed
);
}
}

View File

@@ -8,7 +8,7 @@ use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::SecondsFormat;
use chrono::Utc;
use codex_protocol::auth::PlanType as AuthPlanType;
use codex_protocol::account::PlanType as AccountPlanType;
use codex_protocol::protocol::SessionSource;
use crypto_box::SecretKey as Curve25519SecretKey;
use ed25519_dalek::Signer as _;
@@ -19,9 +19,6 @@ use ed25519_dalek::pkcs8::EncodePrivateKey;
use jsonwebtoken::Algorithm;
use jsonwebtoken::DecodingKey;
use jsonwebtoken::Validation;
use jsonwebtoken::decode;
use jsonwebtoken::decode_header;
use jsonwebtoken::jwk::JwkSet;
use rand::TryRngCore;
use rand::rngs::OsRng;
use serde::Deserialize;
@@ -31,9 +28,6 @@ use sha2::Digest as _;
use sha2::Sha512;
const AGENT_TASK_REGISTRATION_TIMEOUT: Duration = Duration::from_secs(30);
const AGENT_IDENTITY_JWKS_TIMEOUT: Duration = Duration::from_secs(10);
const AGENT_IDENTITY_JWT_AUDIENCE: &str = "codex-app-server";
const AGENT_IDENTITY_JWT_ISSUER: &str = "https://chatgpt.com/codex-backend/agent-identity";
/// Stored key material for a registered agent identity.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -64,16 +58,12 @@ pub struct GeneratedAgentKeyMaterial {
/// Claims carried by an Agent Identity JWT.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct AgentIdentityJwtClaims {
pub iss: String,
pub aud: String,
pub iat: usize,
pub exp: usize,
pub agent_runtime_id: String,
pub agent_private_key: String,
pub account_id: String,
pub chatgpt_user_id: String,
pub email: String,
pub plan_type: AuthPlanType,
pub plan_type: AccountPlanType,
pub chatgpt_account_is_fedramp: bool,
}
@@ -125,49 +115,27 @@ pub fn authorization_header_for_agent_task(
Ok(format!("AgentAssertion {serialized_assertion}"))
}
pub async fn fetch_agent_identity_jwks(
client: &reqwest::Client,
chatgpt_base_url: &str,
) -> Result<JwkSet> {
let response = client
.get(agent_identity_jwks_url(chatgpt_base_url))
.timeout(AGENT_IDENTITY_JWKS_TIMEOUT)
.send()
.await
.context("failed to request agent identity JWKS")?
.error_for_status()
.context("agent identity JWKS endpoint returned an error")?;
response
.json()
.await
.context("failed to decode agent identity JWKS")
}
pub fn decode_agent_identity_jwt(
jwt: &str,
jwks: Option<&JwkSet>,
public_key_base64: Option<&str>,
) -> Result<AgentIdentityJwtClaims> {
let Some(jwks) = jwks else {
let Some(public_key_base64) = public_key_base64 else {
return decode_agent_identity_jwt_payload(jwt);
};
let header = decode_header(jwt).context("failed to decode agent identity JWT header")?;
let kid = header
.kid
.context("agent identity JWT header does not include a kid")?;
let jwk = jwks
.find(&kid)
.with_context(|| format!("agent identity JWT kid {kid} is not trusted"))?;
let decoding_key = DecodingKey::from_jwk(jwk).context("failed to build JWT decoding key")?;
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&[AGENT_IDENTITY_JWT_AUDIENCE]);
validation.set_issuer(&[AGENT_IDENTITY_JWT_ISSUER]);
validation.required_spec_claims.insert("iss".to_string());
validation.required_spec_claims.insert("aud".to_string());
decode::<AgentIdentityJwtClaims>(jwt, &decoding_key, &validation)
let mut validation = Validation::new(Algorithm::EdDSA);
validation.required_spec_claims.clear();
validation.validate_exp = false;
validation.validate_aud = false;
let public_key = BASE64_STANDARD
.decode(public_key_base64)
.context("agent identity JWT public key is not valid base64")?;
let decoding_key = DecodingKey::from_ed_der(&public_key);
jsonwebtoken::decode::<AgentIdentityJwtClaims>(jwt, &decoding_key, &validation)
.map(|data| data.claims)
.context("failed to verify agent identity JWT")
.context("failed to decode agent identity JWT")
}
fn decode_agent_identity_jwt_payload<T: DeserializeOwned>(jwt: &str) -> Result<T> {
@@ -311,15 +279,6 @@ pub fn agent_identity_biscuit_url(chatgpt_base_url: &str) -> String {
format!("{trimmed}/authenticate_app_v2")
}
pub fn agent_identity_jwks_url(chatgpt_base_url: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
if trimmed.contains("/backend-api") {
format!("{trimmed}/wham/agent-identities/jwks")
} else {
format!("{trimmed}/agent-identities/jwks")
}
}
pub fn agent_identity_request_id() -> Result<String> {
let mut request_id_bytes = [0u8; 16];
OsRng
@@ -331,6 +290,29 @@ pub fn agent_identity_request_id() -> Result<String> {
))
}
pub fn normalize_chatgpt_base_url(chatgpt_base_url: &str) -> String {
let mut base_url = chatgpt_base_url.trim_end_matches('/').to_string();
for suffix in [
"/wham/remote/control/server/enroll",
"/wham/remote/control/server",
] {
if let Some(stripped) = base_url.strip_suffix(suffix) {
base_url = stripped.to_string();
break;
}
}
if let Some(stripped) = base_url.strip_suffix("/codex") {
base_url = stripped.to_string();
}
if (base_url.starts_with("https://chatgpt.com")
|| base_url.starts_with("https://chat.openai.com"))
&& !base_url.contains("/backend-api")
{
base_url = format!("{base_url}/backend-api");
}
base_url
}
pub fn build_abom(session_source: SessionSource) -> AgentBillOfMaterials {
AgentBillOfMaterials {
agent_version: env!("CARGO_PKG_VERSION").to_string(),
@@ -340,7 +322,6 @@ pub fn build_abom(session_source: SessionSource) -> AgentBillOfMaterials {
| SessionSource::Exec
| SessionSource::Mcp
| SessionSource::Custom(_)
| SessionSource::Internal(_)
| SessionSource::SubAgent(_)
| SessionSource::Unknown => "codex-cli".to_string(),
},
@@ -408,8 +389,6 @@ mod tests {
use jsonwebtoken::Header;
use pretty_assertions::assert_eq;
use codex_protocol::auth::KnownPlan;
use super::*;
#[test]
@@ -492,10 +471,6 @@ mod tests {
#[test]
fn decode_agent_identity_jwt_reads_claims() {
let jwt = jwt_with_payload(serde_json::json!({
"iss": AGENT_IDENTITY_JWT_ISSUER,
"aud": AGENT_IDENTITY_JWT_AUDIENCE,
"iat": 1_700_000_000usize,
"exp": 4_000_000_000usize,
"agent_runtime_id": "agent-runtime-id",
"agent_private_key": "private-key",
"account_id": "account-id",
@@ -505,70 +480,44 @@ mod tests {
"chatgpt_account_is_fedramp": false,
}));
let claims = decode_agent_identity_jwt(&jwt, /*jwks*/ None).expect("JWT should decode");
let claims =
decode_agent_identity_jwt(&jwt, /*public_key_base64*/ None).expect("JWT should decode");
assert_eq!(
claims,
AgentIdentityJwtClaims {
iss: AGENT_IDENTITY_JWT_ISSUER.to_string(),
aud: AGENT_IDENTITY_JWT_AUDIENCE.to_string(),
iat: 1_700_000_000,
exp: 4_000_000_000,
agent_runtime_id: "agent-runtime-id".to_string(),
agent_private_key: "private-key".to_string(),
account_id: "account-id".to_string(),
chatgpt_user_id: "user-id".to_string(),
email: "user@example.com".to_string(),
plan_type: AuthPlanType::Known(KnownPlan::Pro),
plan_type: AccountPlanType::Pro,
chatgpt_account_is_fedramp: false,
}
);
}
#[test]
fn decode_agent_identity_jwt_maps_raw_plan_aliases() {
let jwt = jwt_with_payload(serde_json::json!({
"iss": AGENT_IDENTITY_JWT_ISSUER,
"aud": AGENT_IDENTITY_JWT_AUDIENCE,
"iat": 1_700_000_000usize,
"exp": 4_000_000_000usize,
"agent_runtime_id": "agent-runtime-id",
"agent_private_key": "private-key",
"account_id": "account-id",
"chatgpt_user_id": "user-id",
"email": "user@example.com",
"plan_type": "hc",
"chatgpt_account_is_fedramp": false,
}));
let claims = decode_agent_identity_jwt(&jwt, /*jwks*/ None).expect("JWT should decode");
assert_eq!(claims.plan_type, AuthPlanType::Known(KnownPlan::Enterprise));
}
#[test]
fn decode_agent_identity_jwt_verifies_when_jwks_is_present() {
let jwks = test_jwks("test-key");
fn decode_agent_identity_jwt_verifies_when_public_key_is_present() {
let mut secret_key_bytes = [0u8; 32];
secret_key_bytes[0] = 1;
let signing_key = SigningKey::from_bytes(&secret_key_bytes);
let private_key_pkcs8 = signing_key
.to_pkcs8_der()
.expect("private key should encode");
let public_key_base64 = BASE64_STANDARD.encode(signing_key.verifying_key().as_bytes());
let claims = AgentIdentityJwtClaims {
iss: AGENT_IDENTITY_JWT_ISSUER.to_string(),
aud: AGENT_IDENTITY_JWT_AUDIENCE.to_string(),
iat: 1_700_000_000,
exp: 4_000_000_000,
agent_runtime_id: "agent-runtime-id".to_string(),
agent_private_key: "private-key".to_string(),
account_id: "account-id".to_string(),
chatgpt_user_id: "user-id".to_string(),
email: "user@example.com".to_string(),
plan_type: AuthPlanType::Known(KnownPlan::Pro),
plan_type: AccountPlanType::Pro,
chatgpt_account_is_fedramp: false,
};
let jwt = jsonwebtoken::encode(
&test_jwt_header("test-key"),
&Header::new(Algorithm::EdDSA),
&serde_json::json!({
"iss": claims.iss,
"aud": claims.aud,
"iat": claims.iat,
"exp": claims.exp,
"agent_runtime_id": claims.agent_runtime_id,
"agent_private_key": claims.agent_private_key,
"account_id": claims.account_id,
@@ -577,40 +526,45 @@ mod tests {
"plan_type": "pro",
"chatgpt_account_is_fedramp": claims.chatgpt_account_is_fedramp,
}),
&test_rsa_encoding_key(),
&EncodingKey::from_ed_der(private_key_pkcs8.as_bytes()),
)
.expect("JWT should encode");
let expected_claims = AgentIdentityJwtClaims {
iss: AGENT_IDENTITY_JWT_ISSUER.to_string(),
aud: AGENT_IDENTITY_JWT_AUDIENCE.to_string(),
iat: 1_700_000_000,
exp: 4_000_000_000,
agent_runtime_id: "agent-runtime-id".to_string(),
agent_private_key: "private-key".to_string(),
account_id: "account-id".to_string(),
chatgpt_user_id: "user-id".to_string(),
email: "user@example.com".to_string(),
plan_type: AuthPlanType::Known(KnownPlan::Pro),
plan_type: AccountPlanType::Pro,
chatgpt_account_is_fedramp: false,
};
assert_eq!(
decode_agent_identity_jwt(&jwt, Some(&jwks)).expect("JWT should verify"),
decode_agent_identity_jwt(&jwt, Some(&public_key_base64)).expect("JWT should verify"),
expected_claims
);
}
#[test]
fn decode_agent_identity_jwt_rejects_untrusted_kid() {
let jwks = test_jwks("other-key");
fn decode_agent_identity_jwt_rejects_wrong_public_key() {
let mut signing_secret_key_bytes = [0u8; 32];
signing_secret_key_bytes[0] = 1;
let signing_key = SigningKey::from_bytes(&signing_secret_key_bytes);
let private_key_pkcs8 = signing_key
.to_pkcs8_der()
.expect("private key should encode");
let mut other_secret_key_bytes = [0u8; 32];
other_secret_key_bytes[0] = 2;
let other_public_key_base64 = BASE64_STANDARD.encode(
SigningKey::from_bytes(&other_secret_key_bytes)
.verifying_key()
.as_bytes(),
);
let jwt = jsonwebtoken::encode(
&test_jwt_header("test-key"),
&Header::new(Algorithm::EdDSA),
&serde_json::json!({
"iss": AGENT_IDENTITY_JWT_ISSUER,
"aud": AGENT_IDENTITY_JWT_AUDIENCE,
"iat": 1_700_000_000,
"exp": 4_000_000_000usize,
"agent_runtime_id": "agent-runtime-id",
"agent_private_key": "private-key",
"account_id": "account-id",
@@ -619,111 +573,19 @@ mod tests {
"plan_type": "pro",
"chatgpt_account_is_fedramp": false,
}),
&test_rsa_encoding_key(),
&EncodingKey::from_ed_der(private_key_pkcs8.as_bytes()),
)
.expect("JWT should encode");
decode_agent_identity_jwt(&jwt, Some(&jwks)).expect_err("JWT should not verify");
decode_agent_identity_jwt(&jwt, Some(&other_public_key_base64))
.expect_err("JWT should not verify");
}
#[test]
fn decode_agent_identity_jwt_requires_issuer_and_audience() {
let jwks = test_jwks("test-key");
let jwt = jsonwebtoken::encode(
&test_jwt_header("test-key"),
&serde_json::json!({
"iat": 1_700_000_000,
"exp": 4_000_000_000usize,
"agent_runtime_id": "agent-runtime-id",
"agent_private_key": "private-key",
"account_id": "account-id",
"chatgpt_user_id": "user-id",
"email": "user@example.com",
"plan_type": "pro",
"chatgpt_account_is_fedramp": false,
}),
&test_rsa_encoding_key(),
)
.expect("JWT should encode");
decode_agent_identity_jwt(&jwt, Some(&jwks)).expect_err("JWT should not verify");
}
fn test_jwt_header(kid: &str) -> Header {
let mut header = Header::new(Algorithm::RS256);
header.kid = Some(kid.to_string());
header
}
fn test_rsa_encoding_key() -> EncodingKey {
EncodingKey::from_rsa_pem(
br#"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWpAXYypOsYAwO
bvBduMk/mxaoYDze0AZSzaSzLuIlcsl2EKDgC3AabhIWXh/qTGEJLOU3VB1e5mO9
FPbBlmIZSL3FQTbyt/hYutPFKfCou5PLmScw/TzILS3/RhT8UY9kxxZvXiEbTki9
mvxRuZFpVqDFJHwfitIjKZGhXDCYVKurPTrxetYZJg0h8sQBLKjkZ0BqqaTUkAsg
0eBgZAlXEzG3By8PGhUqYLt6W1Q3KYw0FmGy/gTyzH1g0ukGgSJvOd8SkNT8MbOs
zl5kKxDNqpuEE6UZ3jbuJ+5382d31w+rOAJRzbf7QVdI9+luCSwJcDACYPQ4WNBa
uCpV0ovpAgMBAAECggEAVu84LwZdqYN9XpswX8VoPYrjMm9IODapWQBRpQFoNyK2
1ksF3bjEPvA2Azk8U/l7k+vLKw22l6lY3EyRZPcz5GnB8xLm3ogE3mtNOp4yCyVu
RxhQ91aaN7mU17/a4BdorLi2LYVCg3zBmYociD1Q2AluNGsCmwPu+K7tfR2J0Sg8
NjqiTbDG1XDpR/icwgC9t6vh8lZpCHDhF4tbQfLLVLeA/OdcuzXDyMCXbmdVIdBQ
rm4aIFmr2e1/2ctTbCg85S6AGFTH+pSLjrwTzyvf+F6NW5uNjLQAQLFj+EznBDxj
Xdx90cySrjsKK6PVWQF4RiTvkSW8eWL7R6B2FZbGwQKBgQDuVQRj72hWloR7mbEL
aUEEv3pIXTMXWEsoMBNczos/1L1RnAN1AI44TurznasPZAWvQj+kVbLDR+TAeZrL
iA8HIWswQUI18hFmgKzSkwIXGtubcKVrgsKeS4lMDKCM/Ef6WAYdeq6ronoY5lCN
YrJFmGp81W5zcV7lyiycgbSiGwKBgQDmjWYf6pZjrK7Z+OJ3X1AZfi2vss15SCvL
3fPgzIDbViztpGyQhc3DQZIsBNIu0xZp/veGce9TEeTds2ro9NfdJFeou8+fC7Pq
sOsM3amGFFi+ZW/9BWyjZEM88bgWWAjqLHbpfHDxjAf5CSxddqxgHlbP0Ytyb1Vg
gmPDn9YKSwKBgQDbTi3hC35WFuDHn0/zcSHcDZmnFuOZeqyFyV83yfMGhGrEuqvP
sPgtRikajJ3IZsB4WZyYSidZXEFY/0z6NjOl2xF38MTNQPbT/FmK1q1Yt2UWrlv5
BvSwlk87RG9D7C0LZo4R+D7cPoDdgqjiwMvMEIkEX5zn641oI1ZTmWKuuwKBgQCD
KF+3unnRvHRAVoFnTZbA2fJdqMeRvogD04GhGlYX8V9f1hFY6nXTJaNlXVzA/J8c
r8ra9kgjJuPfZ+ljG58OFFW2DRohLcQtuHYPfK6rMzoFHqnl9EcIcMp7ijuionR3
29HOJFgQYgxLFXfit9d6WugiE+BTupiEbckZif13HwKBgE/lAlkVHP6YahOO2Ljc
J1bwkqKZTB5dHolX9A58e/xXnfZ5P8f3Z83+Izap3FwqQulk7b1WO1MQcHuVg2NN
5da0D4h2rYOXnbYIg0BVu4spQbaM6ewsp66b8+MzLOBvj8SzWdt1Oyw0q/MRyQAR
8U4M2TSWCKUY/A6sT4W8+mT9
-----END PRIVATE KEY-----"#,
)
.expect("test RSA key should parse")
}
fn test_jwks(kid: &str) -> jsonwebtoken::jwk::JwkSet {
serde_json::from_value(serde_json::json!({
"keys": [{
"kty": "RSA",
"kid": kid,
"use": "sig",
"alg": "RS256",
"n": "1qQF2MqTrGAMDm7wXbjJP5sWqGA83tAGUs2ksy7iJXLJdhCg4AtwGm4SFl4f6kxhCSzlN1QdXuZjvRT2wZZiGUi9xUE28rf4WLrTxSnwqLuTy5knMP08yC0t_0YU_FGPZMcWb14hG05IvZr8UbmRaVagxSR8H4rSIymRoVwwmFSrqz068XrWGSYNIfLEASyo5GdAaqmk1JALINHgYGQJVxMxtwcvDxoVKmC7eltUNymMNBZhsv4E8sx9YNLpBoEibznfEpDU_DGzrM5eZCsQzaqbhBOlGd427ifud_Nnd9cPqzgCUc23-0FXSPfpbgksCXAwAmD0OFjQWrgqVdKL6Q",
"e": "AQAB",
}]
}))
.expect("test JWKS should parse")
}
#[test]
fn agent_identity_jwks_url_uses_backend_api_base_url() {
fn normalize_chatgpt_base_url_strips_codex_before_backend_api() {
assert_eq!(
agent_identity_jwks_url("https://chatgpt.com/backend-api"),
"https://chatgpt.com/backend-api/wham/agent-identities/jwks"
);
assert_eq!(
agent_identity_jwks_url("https://chatgpt.com/backend-api/"),
"https://chatgpt.com/backend-api/wham/agent-identities/jwks"
);
}
#[test]
fn agent_identity_jwks_url_uses_codex_api_base_url() {
assert_eq!(
agent_identity_jwks_url("http://localhost:8080/api/codex"),
"http://localhost:8080/api/codex/agent-identities/jwks"
);
assert_eq!(
agent_identity_jwks_url("http://localhost:8080/api/codex/"),
"http://localhost:8080/api/codex/agent-identities/jwks"
normalize_chatgpt_base_url("https://chatgpt.com/codex"),
"https://chatgpt.com/backend-api"
);
}

View File

@@ -59,19 +59,18 @@ use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::ClientResponse;
use codex_app_server_protocol::CodexErrorInfo;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::NonSteerableTurnKind;
use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionSource as AppServerSessionSource;
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadArchiveResponse;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus;
@@ -142,25 +141,27 @@ fn sample_thread_with_source(
}
}
fn sample_thread_start_response(
thread_id: &str,
ephemeral: bool,
model: &str,
) -> ClientResponsePayload {
ClientResponsePayload::ThreadStart(ThreadStartResponse {
thread: sample_thread(thread_id, ephemeral),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: None,
active_permission_profile: None,
reasoning_effort: None,
})
fn sample_thread_start_response(thread_id: &str, ephemeral: bool, model: &str) -> ClientResponse {
ClientResponse::ThreadStart {
request_id: RequestId::Integer(1),
response: ThreadStartResponse {
thread: sample_thread(thread_id, ephemeral),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
reasoning_effort: None,
},
}
}
fn sample_permission_profile() -> AppServerPermissionProfile {
CorePermissionProfile::Disabled.into()
}
fn sample_app_server_client_metadata() -> CodexAppServerClientMetadata {
@@ -182,11 +183,7 @@ fn sample_runtime_metadata() -> CodexRuntimeMetadata {
}
}
fn sample_thread_resume_response(
thread_id: &str,
ephemeral: bool,
model: &str,
) -> ClientResponsePayload {
fn sample_thread_resume_response(thread_id: &str, ephemeral: bool, model: &str) -> ClientResponse {
sample_thread_resume_response_with_source(
thread_id,
ephemeral,
@@ -200,21 +197,23 @@ fn sample_thread_resume_response_with_source(
ephemeral: bool,
model: &str,
source: AppServerSessionSource,
) -> ClientResponsePayload {
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
thread: sample_thread_with_source(thread_id, ephemeral, source),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: None,
active_permission_profile: None,
reasoning_effort: None,
})
) -> ClientResponse {
ClientResponse::ThreadResume {
request_id: RequestId::Integer(2),
response: ThreadResumeResponse {
thread: sample_thread_with_source(thread_id, ephemeral, source),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
reasoning_effort: None,
},
}
}
fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest {
@@ -236,18 +235,21 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest
}
}
fn sample_turn_start_response(turn_id: &str) -> ClientResponsePayload {
ClientResponsePayload::TurnStart(codex_app_server_protocol::TurnStartResponse {
turn: Turn {
id: turn_id.to_string(),
items: vec![],
status: AppServerTurnStatus::InProgress,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
fn sample_turn_start_response(turn_id: &str, request_id: i64) -> ClientResponse {
ClientResponse::TurnStart {
request_id: RequestId::Integer(request_id),
response: codex_app_server_protocol::TurnStartResponse {
turn: Turn {
id: turn_id.to_string(),
items: vec![],
status: AppServerTurnStatus::InProgress,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
},
},
})
}
}
fn sample_turn_started_notification(thread_id: &str, turn_id: &str) -> ServerNotification {
@@ -353,10 +355,13 @@ fn sample_turn_steer_request(
}
}
fn sample_turn_steer_response(turn_id: &str) -> ClientResponsePayload {
ClientResponsePayload::TurnSteer(TurnSteerResponse {
turn_id: turn_id.to_string(),
})
fn sample_turn_steer_response(turn_id: &str, request_id: i64) -> ClientResponse {
ClientResponse::TurnSteer {
request_id: RequestId::Integer(request_id),
response: TurnSteerResponse {
turn_id: turn_id.to_string(),
},
}
}
fn no_active_turn_steer_error() -> JSONRPCErrorError {
@@ -421,7 +426,7 @@ async fn ingest_rejected_turn_steer(
.await;
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(4),
request: Box::new(sample_turn_steer_request(
@@ -481,9 +486,8 @@ async fn ingest_turn_prerequisites(
ingest_initialize(reducer, out).await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(1),
response: Box::new(sample_thread_start_response(
"thread-2", /*ephemeral*/ false, "gpt-5",
)),
@@ -496,7 +500,7 @@ async fn ingest_turn_prerequisites(
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(3),
request: Box::new(sample_turn_start_request("thread-2", /*request_id*/ 3)),
@@ -506,10 +510,9 @@ async fn ingest_turn_prerequisites(
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(3),
response: Box::new(sample_turn_start_response("turn-2")),
response: Box::new(sample_turn_start_response("turn-2", /*request_id*/ 3)),
},
out,
)
@@ -859,9 +862,8 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(1),
response: Box::new(sample_thread_start_response(
"thread-no-client",
/*ephemeral*/ false,
@@ -904,9 +906,8 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(2),
response: Box::new(sample_thread_resume_response(
"thread-1", /*ephemeral*/ true, "gpt-5",
)),
@@ -953,65 +954,6 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
);
}
#[tokio::test]
async fn unrelated_client_requests_are_ignored_by_reducer() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::ClientRequest {
connection_id: 7,
request_id: RequestId::Integer(3),
request: Box::new(ClientRequest::ThreadArchive {
request_id: RequestId::Integer(3),
params: ThreadArchiveParams {
thread_id: "thread-2".to_string(),
},
}),
},
&mut events,
)
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
connection_id: 7,
request_id: RequestId::Integer(3),
response: Box::new(sample_turn_start_response("turn-2")),
},
&mut events,
)
.await;
assert!(
events.is_empty(),
"unrelated requests must not create pending turn state"
);
}
#[tokio::test]
async fn unrelated_client_responses_are_ignored_by_reducer() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
ingest_initialize(&mut reducer, &mut events).await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
connection_id: 7,
request_id: RequestId::Integer(9),
response: Box::new(ClientResponsePayload::ThreadArchive(
ThreadArchiveResponse {},
)),
},
&mut events,
)
.await;
assert!(events.is_empty());
}
#[tokio::test]
async fn compaction_event_ingests_custom_fact() {
let mut reducer = AnalyticsReducer::default();
@@ -1044,9 +986,8 @@ async fn compaction_event_ingests_custom_fact() {
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(2),
response: Box::new(sample_thread_resume_response_with_source(
"thread-1",
/*ephemeral*/ false,
@@ -1156,9 +1097,8 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(1),
response: Box::new(sample_thread_start_response(
"thread-guardian",
/*ephemeral*/ false,
@@ -1559,15 +1499,6 @@ fn hook_run_metadata_maps_sources_and_statuses() {
},
))
.expect("serialize project hook");
let cloud_requirements = serde_json::to_value(codex_hook_run_metadata(
&tracking,
HookRunFact {
event_name: HookEventName::Stop,
hook_source: HookSource::CloudRequirements,
status: HookRunStatus::Blocked,
},
))
.expect("serialize cloud requirements hook");
let unknown = serde_json::to_value(codex_hook_run_metadata(
&tracking,
HookRunFact {
@@ -1582,8 +1513,6 @@ fn hook_run_metadata_maps_sources_and_statuses() {
assert_eq!(system["status"], "completed");
assert_eq!(project["hook_source"], "project");
assert_eq!(project["status"], "blocked");
assert_eq!(cloud_requirements["hook_source"], "cloud_requirements");
assert_eq!(cloud_requirements["status"], "blocked");
assert_eq!(unknown["hook_source"], "unknown");
assert_eq!(unknown["status"], "failed");
}
@@ -1938,7 +1867,7 @@ async fn accepted_turn_steer_emits_expected_event() {
.await;
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(4),
request: Box::new(sample_turn_steer_request(
@@ -1950,10 +1879,9 @@ async fn accepted_turn_steer_emits_expected_event() {
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(4),
response: Box::new(sample_turn_steer_response("turn-2")),
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 4)),
},
&mut out,
)
@@ -2093,7 +2021,7 @@ async fn turn_start_error_response_discards_pending_start_request() {
ingest_initialize(&mut reducer, &mut out).await;
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(3),
request: Box::new(sample_turn_start_request("thread-2", /*request_id*/ 3)),
@@ -2117,10 +2045,9 @@ async fn turn_start_error_response_discards_pending_start_request() {
// failed turn/start request and attach request-scoped connection metadata.
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(3),
response: Box::new(sample_turn_start_response("turn-2")),
response: Box::new(sample_turn_start_response("turn-2", /*request_id*/ 3)),
},
&mut out,
)
@@ -2235,7 +2162,7 @@ async fn accepted_steers_increment_turn_steer_count() {
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(4),
request: Box::new(sample_turn_steer_request(
@@ -2247,10 +2174,9 @@ async fn accepted_steers_increment_turn_steer_count() {
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(4),
response: Box::new(sample_turn_steer_response("turn-2")),
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 4)),
},
&mut out,
)
@@ -2258,7 +2184,7 @@ async fn accepted_steers_increment_turn_steer_count() {
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(5),
request: Box::new(sample_turn_steer_request(
@@ -2282,7 +2208,7 @@ async fn accepted_steers_increment_turn_steer_count() {
reducer
.ingest(
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id: 7,
request_id: RequestId::Integer(6),
request: Box::new(sample_turn_steer_request(
@@ -2294,10 +2220,9 @@ async fn accepted_steers_increment_turn_steer_count() {
.await;
reducer
.ingest(
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id: 7,
request_id: RequestId::Integer(6),
response: Box::new(sample_turn_steer_response("turn-2")),
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 6)),
},
&mut out,
)

View File

@@ -22,13 +22,11 @@ use crate::facts::TurnResolvedConfigFact;
use crate::facts::TurnTokenUsageFact;
use crate::reducer::AnalyticsReducer;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::ClientResponse;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ServerResponse;
use codex_login::AuthManager;
use codex_login::default_client::create_client;
use codex_plugin::PluginTelemetryMetadata;
@@ -51,7 +49,8 @@ pub(crate) struct AnalyticsEventsQueue {
#[derive(Clone)]
pub struct AnalyticsEventsClient {
queue: Option<AnalyticsEventsQueue>,
queue: AnalyticsEventsQueue,
analytics_enabled: Option<bool>,
}
impl AnalyticsEventsQueue {
@@ -120,15 +119,11 @@ impl AnalyticsEventsClient {
analytics_enabled: Option<bool>,
) -> Self {
Self {
queue: (analytics_enabled != Some(false))
.then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url)),
queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url),
analytics_enabled,
}
}
pub fn disabled() -> Self {
Self { queue: None }
}
pub fn track_skill_invocations(
&self,
tracking: TrackEventsContext,
@@ -186,30 +181,16 @@ impl AnalyticsEventsClient {
)));
}
pub fn track_request(
&self,
connection_id: u64,
request_id: RequestId,
request: &ClientRequest,
) {
if !matches!(
request,
ClientRequest::TurnStart { .. } | ClientRequest::TurnSteer { .. }
) {
return;
}
self.record_fact(AnalyticsFact::ClientRequest {
pub fn track_request(&self, connection_id: u64, request_id: RequestId, request: ClientRequest) {
self.record_fact(AnalyticsFact::Request {
connection_id,
request_id,
request: Box::new(request.clone()),
request: Box::new(request),
});
}
pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) {
let Some(queue) = self.queue.as_ref() else {
return;
};
if !queue.should_enqueue_app_used(&tracking, &app) {
if !self.queue.should_enqueue_app_used(&tracking, &app) {
return;
}
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(
@@ -224,10 +205,7 @@ impl AnalyticsEventsClient {
}
pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) {
let Some(queue) = self.queue.as_ref() else {
return;
};
if !queue.should_enqueue_plugin_used(&tracking, &plugin) {
if !self.queue.should_enqueue_plugin_used(&tracking, &plugin) {
return;
}
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed(
@@ -290,30 +268,15 @@ impl AnalyticsEventsClient {
}
pub(crate) fn record_fact(&self, input: AnalyticsFact) {
if let Some(queue) = self.queue.as_ref() {
queue.try_send(input);
}
}
pub fn track_response(
&self,
connection_id: u64,
request_id: RequestId,
response: ClientResponsePayload,
) {
if !matches!(
response,
ClientResponsePayload::ThreadStart(_)
| ClientResponsePayload::ThreadResume(_)
| ClientResponsePayload::ThreadFork(_)
| ClientResponsePayload::TurnStart(_)
| ClientResponsePayload::TurnSteer(_)
) {
if self.analytics_enabled == Some(false) {
return;
}
self.record_fact(AnalyticsFact::ClientResponse {
self.queue.try_send(input);
}
pub fn track_response(&self, connection_id: u64, response: ClientResponse) {
self.record_fact(AnalyticsFact::Response {
connection_id,
request_id,
response: Box::new(response),
});
}
@@ -336,19 +299,6 @@ impl AnalyticsEventsClient {
pub fn track_notification(&self, notification: ServerNotification) {
self.record_fact(AnalyticsFact::Notification(Box::new(notification)));
}
pub fn track_server_request(&self, connection_id: u64, request: ServerRequest) {
self.record_fact(AnalyticsFact::ServerRequest {
connection_id,
request: Box::new(request),
});
}
pub fn track_server_response(&self, response: ServerResponse) {
self.record_fact(AnalyticsFact::ServerResponse {
response: Box::new(response),
});
}
}
async fn send_track_events(
@@ -391,7 +341,3 @@ async fn send_track_events(
}
}
}
#[cfg(test)]
#[path = "client_tests.rs"]
mod tests;

View File

@@ -1,221 +0,0 @@
use super::AnalyticsEventsClient;
use super::AnalyticsEventsQueue;
use crate::facts::AnalyticsFact;
use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy;
use codex_app_server_protocol::SessionSource as AppServerSessionSource;
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadArchiveResponse;
use codex_app_server_protocol::ThreadForkResponse;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus;
use codex_app_server_protocol::Turn;
use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
use codex_app_server_protocol::TurnSteerParams;
use codex_app_server_protocol::TurnSteerResponse;
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use std::collections::HashSet;
use std::sync::Arc;
use std::sync::Mutex;
use tokio::sync::mpsc;
use tokio::sync::mpsc::error::TryRecvError;
fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFact>) {
let (sender, receiver) = mpsc::channel(8);
let queue = AnalyticsEventsQueue {
sender,
app_used_emitted_keys: Arc::new(Mutex::new(HashSet::new())),
plugin_used_emitted_keys: Arc::new(Mutex::new(HashSet::new())),
};
(AnalyticsEventsClient { queue: Some(queue) }, receiver)
}
fn sample_turn_start_request() -> ClientRequest {
ClientRequest::TurnStart {
request_id: RequestId::Integer(1),
params: TurnStartParams {
thread_id: "thread-1".to_string(),
input: Vec::new(),
..Default::default()
},
}
}
fn sample_turn_steer_request() -> ClientRequest {
ClientRequest::TurnSteer {
request_id: RequestId::Integer(2),
params: TurnSteerParams {
thread_id: "thread-1".to_string(),
expected_turn_id: "turn-1".to_string(),
input: Vec::new(),
responsesapi_client_metadata: None,
},
}
}
fn sample_thread_archive_request() -> ClientRequest {
ClientRequest::ThreadArchive {
request_id: RequestId::Integer(3),
params: ThreadArchiveParams {
thread_id: "thread-1".to_string(),
},
}
}
fn sample_thread(thread_id: &str) -> Thread {
Thread {
id: thread_id.to_string(),
forked_from_id: None,
preview: "first prompt".to_string(),
ephemeral: false,
model_provider: "openai".to_string(),
created_at: 1,
updated_at: 2,
status: AppServerThreadStatus::Idle,
path: None,
cwd: test_path_buf("/tmp").abs(),
cli_version: "0.0.0".to_string(),
source: AppServerSessionSource::Exec,
agent_nickname: None,
agent_role: None,
git_info: None,
name: None,
turns: Vec::new(),
}
}
fn sample_permission_profile() -> AppServerPermissionProfile {
CorePermissionProfile::Disabled.into()
}
fn sample_thread_start_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadStart(ThreadStartResponse {
thread: sample_thread("thread-1"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
}
fn sample_thread_resume_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
thread: sample_thread("thread-2"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
}
fn sample_thread_fork_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadFork(ThreadForkResponse {
thread: sample_thread("thread-3"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
}
fn sample_turn_start_response() -> ClientResponsePayload {
ClientResponsePayload::TurnStart(TurnStartResponse {
turn: Turn {
id: "turn-1".to_string(),
items: Vec::new(),
status: AppServerTurnStatus::InProgress,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
},
})
}
fn sample_turn_steer_response() -> ClientResponsePayload {
ClientResponsePayload::TurnSteer(TurnSteerResponse {
turn_id: "turn-2".to_string(),
})
}
#[test]
fn track_request_only_enqueues_analytics_relevant_requests() {
let (client, mut receiver) = client_with_receiver();
for (request_id, request) in [
(RequestId::Integer(1), sample_turn_start_request()),
(RequestId::Integer(2), sample_turn_steer_request()),
] {
client.track_request(/*connection_id*/ 7, request_id, &request);
assert!(matches!(
receiver.try_recv(),
Ok(AnalyticsFact::ClientRequest { .. })
));
}
let ignored_request = sample_thread_archive_request();
client.track_request(
/*connection_id*/ 7,
RequestId::Integer(3),
&ignored_request,
);
assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn track_response_only_enqueues_analytics_relevant_responses() {
let (client, mut receiver) = client_with_receiver();
for (request_id, response) in [
(RequestId::Integer(1), sample_thread_start_response()),
(RequestId::Integer(2), sample_thread_resume_response()),
(RequestId::Integer(3), sample_thread_fork_response()),
(RequestId::Integer(4), sample_turn_start_response()),
(RequestId::Integer(5), sample_turn_steer_response()),
] {
client.track_response(/*connection_id*/ 7, request_id, response);
assert!(matches!(
receiver.try_recv(),
Ok(AnalyticsFact::ClientResponse { .. })
));
}
client.track_response(
/*connection_id*/ 7,
RequestId::Integer(6),
ClientResponsePayload::ThreadArchive(ThreadArchiveResponse {}),
);
assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
}

View File

@@ -684,8 +684,6 @@ fn analytics_hook_source(source: HookSource) -> &'static str {
HookSource::Project => "project",
HookSource::Mdm => "mdm",
HookSource::SessionFlags => "session_flags",
HookSource::Plugin => "plugin",
HookSource::CloudRequirements => "cloud_requirements",
HookSource::LegacyManagedConfigFile => "legacy_managed_config_file",
HookSource::LegacyManagedConfigMdm => "legacy_managed_config_mdm",
HookSource::Unknown => "unknown",

View File

@@ -2,13 +2,11 @@ use crate::events::AppServerRpcTransport;
use crate::events::CodexRuntimeMetadata;
use crate::events::GuardianReviewEventParams;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::ClientResponse;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ServerResponse;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::ModeKind;
@@ -274,15 +272,14 @@ pub(crate) enum AnalyticsFact {
runtime: CodexRuntimeMetadata,
rpc_transport: AppServerRpcTransport,
},
ClientRequest {
Request {
connection_id: u64,
request_id: RequestId,
request: Box<ClientRequest>,
},
ClientResponse {
Response {
connection_id: u64,
request_id: RequestId,
response: Box<ClientResponsePayload>,
response: Box<ClientResponse>,
},
ErrorResponse {
connection_id: u64,
@@ -290,13 +287,6 @@ pub(crate) enum AnalyticsFact {
error: JSONRPCErrorError,
error_type: Option<AnalyticsJsonRpcError>,
},
ServerRequest {
connection_id: u64,
request: Box<ServerRequest>,
},
ServerResponse {
response: Box<ServerResponse>,
},
Notification(Box<ServerNotification>),
// Facts that do not naturally exist on the app-server protocol surface, or
// would require non-trivial protocol reshaping on this branch.

View File

@@ -106,7 +106,6 @@ impl ThreadMetadataState {
| SessionSource::Exec
| SessionSource::Mcp
| SessionSource::Custom(_)
| SessionSource::Internal(_)
| SessionSource::Unknown => (None, None),
};
Self {
@@ -172,21 +171,18 @@ impl AnalyticsReducer {
rpc_transport,
);
}
AnalyticsFact::ClientRequest {
AnalyticsFact::Request {
connection_id,
request_id,
request,
} => {
self.ingest_request(connection_id, request_id, *request);
}
AnalyticsFact::ClientResponse {
AnalyticsFact::Response {
connection_id,
request_id,
response,
} => {
if let Some(response) = response.into_client_response(request_id) {
self.ingest_response(connection_id, response, out);
}
self.ingest_response(connection_id, *response, out);
}
AnalyticsFact::ErrorResponse {
connection_id,
@@ -199,13 +195,6 @@ impl AnalyticsReducer {
AnalyticsFact::Notification(notification) => {
self.ingest_notification(*notification, out);
}
AnalyticsFact::ServerRequest {
connection_id: _connection_id,
request: _request,
} => {}
AnalyticsFact::ServerResponse {
response: _response,
} => {}
AnalyticsFact::Custom(input) => match input {
CustomAnalyticsFact::SubAgentThreadStarted(input) => {
self.ingest_subagent_thread_started(input, out);

View File

@@ -1396,55 +1396,6 @@ mod tests {
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_typed_request_accepts_large_single_frame_response() {
let padding = "x".repeat((17 << 20) + 1024);
let websocket_url = start_test_remote_server(move |mut websocket| async move {
expect_remote_initialize(&mut websocket).await;
let JSONRPCMessage::Request(request) = read_websocket_message(&mut websocket).await
else {
panic!("expected account/read request");
};
assert_eq!(request.method, "account/read");
write_websocket_message(
&mut websocket,
JSONRPCMessage::Response(JSONRPCResponse {
id: request.id,
result: serde_json::json!({
"account": null,
"requiresOpenaiAuth": false,
"padding": padding,
}),
}),
)
.await;
websocket.close(None).await.expect("close should succeed");
})
.await;
let client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
.await
.expect("remote client should connect");
let response: GetAccountResponse = client
.request_typed(ClientRequest::GetAccount {
request_id: RequestId::Integer(1),
params: codex_app_server_protocol::GetAccountParams {
refresh_token: false,
},
})
.await
.expect("large typed request should succeed");
assert_eq!(
response,
GetAccountResponse {
account: None,
requires_openai_auth: false,
}
);
client.shutdown().await.expect("shutdown should complete");
}
#[tokio::test]
async fn remote_connect_includes_auth_header_when_configured() {
let auth_token = "remote-bearer-token".to_string();
@@ -2029,17 +1980,14 @@ mod tests {
#[tokio::test]
async fn runtime_start_args_forward_environment_manager() {
let config = Arc::new(build_test_config().await);
let environment_manager = Arc::new(
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths"),
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("ws://127.0.0.1:8765".to_string()),
local_runtime_paths: ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.await,
);
.expect("runtime paths"),
}));
let runtime_args = InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),

View File

@@ -45,18 +45,16 @@ use tokio::sync::oneshot;
use tokio::time::timeout;
use tokio_tungstenite::MaybeTlsStream;
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::connect_async_with_config;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use tokio_tungstenite::tungstenite::http::HeaderValue;
use tokio_tungstenite::tungstenite::http::header::AUTHORIZATION;
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
use tracing::warn;
use url::Url;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
const INITIALIZE_TIMEOUT: Duration = Duration::from_secs(10);
const REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE: usize = 128 << 20;
#[derive(Debug, Clone)]
pub struct RemoteAppServerConnectArgs {
@@ -172,32 +170,20 @@ impl RemoteAppServerClient {
request.headers_mut().insert(AUTHORIZATION, header_value);
}
ensure_rustls_crypto_provider();
// Remote resume responses can legitimately carry large thread histories.
// Keep a bounded cap, but raise it above tungstenite's 16 MiB frame default.
let websocket_config = WebSocketConfig::default()
.max_frame_size(Some(REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE))
.max_message_size(Some(REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE));
let stream = timeout(
CONNECT_TIMEOUT,
connect_async_with_config(
request,
Some(websocket_config),
/*disable_nagle*/ false,
),
)
.await
.map_err(|_| {
IoError::new(
ErrorKind::TimedOut,
format!("timed out connecting to remote app server at `{websocket_url}`"),
)
})?
.map(|(stream, _response)| stream)
.map_err(|err| {
IoError::other(format!(
"failed to connect to remote app server at `{websocket_url}`: {err}"
))
})?;
let stream = timeout(CONNECT_TIMEOUT, connect_async(request))
.await
.map_err(|_| {
IoError::new(
ErrorKind::TimedOut,
format!("timed out connecting to remote app server at `{websocket_url}`"),
)
})?
.map(|(stream, _response)| stream)
.map_err(|err| {
IoError::other(format!(
"failed to connect to remote app server at `{websocket_url}`: {err}"
))
})?;
let mut stream = stream;
let pending_events = initialize_remote_connection(
&mut stream,
@@ -212,7 +198,6 @@ impl RemoteAppServerClient {
let worker_handle = tokio::spawn(async move {
let mut pending_requests =
HashMap::<RequestId, oneshot::Sender<IoResult<RequestResult>>>::new();
let mut worker_exit_error: Option<(ErrorKind, String)> = None;
loop {
tokio::select! {
command = command_rx.recv() => {
@@ -239,19 +224,17 @@ impl RemoteAppServerClient {
.await
{
let err_message = err.to_string();
let message = format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
);
if let Some(response_tx) = pending_requests.remove(&request_id) {
let _ = response_tx.send(Err(err));
}
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
),
},
);
worker_exit_error = Some((ErrorKind::BrokenPipe, message));
break;
}
}
@@ -368,34 +351,28 @@ impl RemoteAppServerClient {
.await
{
let err_message = reject_err.to_string();
let message = format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
);
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` write failed: {err_message}"
),
},
);
worker_exit_error =
Some((ErrorKind::BrokenPipe, message));
break;
}
}
}
}
Err(err) => {
let message = format!(
"remote app server at `{websocket_url}` sent invalid JSON-RPC: {err}"
);
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` sent invalid JSON-RPC: {err}"
),
},
);
worker_exit_error =
Some((ErrorKind::InvalidData, message));
break;
}
}
@@ -406,19 +383,14 @@ impl RemoteAppServerClient {
.map(|frame| frame.reason.to_string())
.filter(|reason| !reason.is_empty())
.unwrap_or_else(|| "connection closed".to_string());
let message = format!(
"remote app server at `{websocket_url}` disconnected: {reason}"
);
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` disconnected: {reason}"
),
},
);
worker_exit_error = Some((
ErrorKind::ConnectionAborted,
message,
));
break;
}
Some(Ok(Message::Binary(_)))
@@ -426,29 +398,25 @@ impl RemoteAppServerClient {
| Some(Ok(Message::Pong(_)))
| Some(Ok(Message::Frame(_))) => {}
Some(Err(err)) => {
let message = format!(
"remote app server at `{websocket_url}` transport failed: {err}"
);
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` transport failed: {err}"
),
},
);
worker_exit_error = Some((ErrorKind::InvalidData, message));
break;
}
None => {
let message = format!(
"remote app server at `{websocket_url}` closed the connection"
);
let _ = deliver_event(
&event_tx,
AppServerEvent::Disconnected {
message: message.clone(),
message: format!(
"remote app server at `{websocket_url}` closed the connection"
),
},
);
worker_exit_error = Some((ErrorKind::UnexpectedEof, message));
break;
}
}
@@ -456,14 +424,12 @@ impl RemoteAppServerClient {
}
}
let (err_kind, err_message) = worker_exit_error.unwrap_or_else(|| {
(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed".to_string(),
)
});
let err = IoError::new(
ErrorKind::BrokenPipe,
"remote app-server worker channel is closed",
);
for (_, response_tx) in pending_requests {
let _ = response_tx.send(Err(IoError::new(err_kind, err_message.clone())));
let _ = response_tx.send(Err(IoError::new(err.kind(), err.to_string())));
}
});

View File

@@ -218,6 +218,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [
@@ -354,17 +365,6 @@
],
"type": "object"
},
"CommandMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"ConfigBatchWriteParams": {
"properties": {
"edits": {
@@ -860,11 +860,7 @@
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
"MCP_SERVER_CONFIG"
],
"type": "string"
},
@@ -1404,27 +1400,36 @@
},
"type": "object"
},
"HookMigration": {
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
"name": {
"id": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"HooksListParams": {
"properties": {
"cwds": {
"description": "When empty, defaults to the current session working directory.",
},
"parent": {
"type": [
"string",
"null"
]
},
"preexisting_untracked_dirs": {
"items": {
"type": "string"
},
"type": "array"
},
"preexisting_untracked_files": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"preexisting_untracked_dirs",
"preexisting_untracked_files"
],
"type": "object"
},
"ImageDetail": {
@@ -1598,9 +1603,6 @@
},
{
"properties": {
"codexStreamlinedLogin": {
"type": "boolean"
},
"type": {
"enum": [
"chatgpt"
@@ -1736,17 +1738,6 @@
],
"type": "object"
},
"McpServerMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"McpServerOauthLoginParams": {
"properties": {
"name": {
@@ -1830,49 +1821,16 @@
},
"MigrationDetails": {
"properties": {
"commands": {
"default": [],
"items": {
"$ref": "#/definitions/CommandMigration"
},
"type": "array"
},
"hooks": {
"default": [],
"items": {
"$ref": "#/definitions/HookMigration"
},
"type": "array"
},
"mcpServers": {
"default": [],
"items": {
"$ref": "#/definitions/McpServerMigration"
},
"type": "array"
},
"plugins": {
"default": [],
"items": {
"$ref": "#/definitions/PluginsMigration"
},
"type": "array"
},
"sessions": {
"default": [],
"items": {
"$ref": "#/definitions/SessionMigration"
},
"type": "array"
},
"subagents": {
"default": [],
"items": {
"$ref": "#/definitions/SubagentMigration"
},
"type": "array"
}
},
"required": [
"plugins"
],
"type": "object"
},
"ModeKind": {
@@ -1911,9 +1869,6 @@
},
"type": "object"
},
"ModelProviderCapabilitiesReadParams": {
"type": "object"
},
"NetworkAccess": {
"enum": [
"restricted",
@@ -2039,31 +1994,6 @@
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
@@ -2075,40 +2005,6 @@
],
"type": "object"
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -2186,37 +2082,6 @@
],
"type": "object"
},
"PluginShareDeleteParams": {
"properties": {
"remotePluginId": {
"type": "string"
}
},
"required": [
"remotePluginId"
],
"type": "object"
},
"PluginShareListParams": {
"type": "object"
},
"PluginShareSaveParams": {
"properties": {
"pluginPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"remotePluginId": {
"type": [
"string",
"null"
]
}
},
"required": [
"pluginPath"
],
"type": "object"
},
"PluginUninstallParams": {
"properties": {
"pluginId": {
@@ -2811,6 +2676,26 @@
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
"$ref": "#/definitions/GhostCommit"
},
"type": {
"enum": [
"ghost_snapshot"
],
"title": "GhostSnapshotResponseItemType",
"type": "string"
}
},
"required": [
"ghost_commit",
"type"
],
"title": "GhostSnapshotResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -3200,27 +3085,6 @@
],
"type": "string"
},
"SessionMigration": {
"properties": {
"cwd": {
"type": "string"
},
"path": {
"type": "string"
},
"title": {
"type": [
"string",
"null"
]
}
},
"required": [
"cwd",
"path"
],
"type": "object"
},
"Settings": {
"description": "Settings for a collaboration mode.",
"properties": {
@@ -3330,17 +3194,6 @@
],
"type": "string"
},
"SubagentMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"TextElement": {
"properties": {
"byteRange": {
@@ -3470,6 +3323,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{
@@ -3875,6 +3739,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -4058,6 +3933,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -4269,6 +4155,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{
@@ -4962,30 +4859,6 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"hooks/list"
],
"title": "Hooks/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/HooksListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Hooks/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -5106,78 +4979,6 @@
"title": "Plugin/readRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/share/save"
],
"title": "Plugin/share/saveRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginShareSaveParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/saveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/share/list"
],
"title": "Plugin/share/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginShareListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/share/delete"
],
"title": "Plugin/share/deleteRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginShareDeleteParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/deleteRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -5682,30 +5483,6 @@
"title": "Model/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"modelProvider/capabilities/read"
],
"title": "ModelProvider/capabilities/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ModelProviderCapabilitiesReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "ModelProvider/capabilities/readRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -1900,8 +1900,6 @@
"project",
"mdm",
"sessionFlags",
"plugin",
"cloudRequirements",
"legacyManagedConfigFile",
"legacyManagedConfigMdm",
"unknown"
@@ -2604,33 +2602,6 @@
],
"type": "object"
},
"RemoteControlConnectionStatus": {
"enum": [
"disabled",
"connecting",
"connected",
"errored"
],
"type": "string"
},
"RemoteControlStatusChangedNotification": {
"description": "Current remote-control connection status and environment id exposed to clients.",
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"status": {
"$ref": "#/definitions/RemoteControlConnectionStatus"
}
},
"required": [
"status"
],
"type": "object"
},
"RequestId": {
"anyOf": [
{
@@ -5370,26 +5341,6 @@
"title": "App/list/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"remoteControl/status/changed"
],
"title": "RemoteControl/status/changedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/RemoteControlStatusChangedNotification"
}
},
"required": [
"method",
"params"
],
"title": "RemoteControl/status/changedNotification",
"type": "object"
},
{
"properties": {
"method": {

View File

@@ -505,6 +505,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [

View File

@@ -1,17 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"CommandMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"ExternalAgentConfigMigrationItem": {
"properties": {
"cwd": {
@@ -50,81 +39,22 @@
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
"MCP_SERVER_CONFIG"
],
"type": "string"
},
"HookMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"McpServerMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"MigrationDetails": {
"properties": {
"commands": {
"default": [],
"items": {
"$ref": "#/definitions/CommandMigration"
},
"type": "array"
},
"hooks": {
"default": [],
"items": {
"$ref": "#/definitions/HookMigration"
},
"type": "array"
},
"mcpServers": {
"default": [],
"items": {
"$ref": "#/definitions/McpServerMigration"
},
"type": "array"
},
"plugins": {
"default": [],
"items": {
"$ref": "#/definitions/PluginsMigration"
},
"type": "array"
},
"sessions": {
"default": [],
"items": {
"$ref": "#/definitions/SessionMigration"
},
"type": "array"
},
"subagents": {
"default": [],
"items": {
"$ref": "#/definitions/SubagentMigration"
},
"type": "array"
}
},
"required": [
"plugins"
],
"type": "object"
},
"PluginsMigration": {
@@ -144,38 +74,6 @@
"pluginNames"
],
"type": "object"
},
"SessionMigration": {
"properties": {
"cwd": {
"type": "string"
},
"path": {
"type": "string"
},
"title": {
"type": [
"string",
"null"
]
}
},
"required": [
"cwd",
"path"
],
"type": "object"
},
"SubagentMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
},
"properties": {

View File

@@ -1,17 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"CommandMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"ExternalAgentConfigMigrationItem": {
"properties": {
"cwd": {
@@ -50,81 +39,22 @@
"CONFIG",
"SKILLS",
"PLUGINS",
"MCP_SERVER_CONFIG",
"SUBAGENTS",
"HOOKS",
"COMMANDS",
"SESSIONS"
"MCP_SERVER_CONFIG"
],
"type": "string"
},
"HookMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"McpServerMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"MigrationDetails": {
"properties": {
"commands": {
"default": [],
"items": {
"$ref": "#/definitions/CommandMigration"
},
"type": "array"
},
"hooks": {
"default": [],
"items": {
"$ref": "#/definitions/HookMigration"
},
"type": "array"
},
"mcpServers": {
"default": [],
"items": {
"$ref": "#/definitions/McpServerMigration"
},
"type": "array"
},
"plugins": {
"default": [],
"items": {
"$ref": "#/definitions/PluginsMigration"
},
"type": "array"
},
"sessions": {
"default": [],
"items": {
"$ref": "#/definitions/SessionMigration"
},
"type": "array"
},
"subagents": {
"default": [],
"items": {
"$ref": "#/definitions/SubagentMigration"
},
"type": "array"
}
},
"required": [
"plugins"
],
"type": "object"
},
"PluginsMigration": {
@@ -144,38 +74,6 @@
"pluginNames"
],
"type": "object"
},
"SessionMigration": {
"properties": {
"cwd": {
"type": "string"
},
"path": {
"type": "string"
},
"title": {
"type": [
"string",
"null"
]
}
},
"required": [
"cwd",
"path"
],
"type": "object"
},
"SubagentMigration": {
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
},
"properties": {

View File

@@ -160,8 +160,6 @@
"project",
"mdm",
"sessionFlags",
"plugin",
"cloudRequirements",
"legacyManagedConfigFile",
"legacyManagedConfigMdm",
"unknown"

View File

@@ -160,8 +160,6 @@
"project",
"mdm",
"sessionFlags",
"plugin",
"cloudRequirements",
"legacyManagedConfigFile",
"legacyManagedConfigMdm",
"unknown"

View File

@@ -1,14 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "When empty, defaults to the current session working directory.",
"items": {
"type": "string"
},
"type": "array"
}
},
"title": "HooksListParams",
"type": "object"
}

View File

@@ -1,173 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"HookErrorInfo": {
"properties": {
"message": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"message",
"path"
],
"type": "object"
},
"HookEventName": {
"enum": [
"preToolUse",
"permissionRequest",
"postToolUse",
"sessionStart",
"userPromptSubmit",
"stop"
],
"type": "string"
},
"HookHandlerType": {
"enum": [
"command",
"prompt",
"agent"
],
"type": "string"
},
"HookMetadata": {
"properties": {
"command": {
"type": [
"string",
"null"
]
},
"displayOrder": {
"format": "int64",
"type": "integer"
},
"enabled": {
"type": "boolean"
},
"eventName": {
"$ref": "#/definitions/HookEventName"
},
"handlerType": {
"$ref": "#/definitions/HookHandlerType"
},
"isManaged": {
"type": "boolean"
},
"key": {
"type": "string"
},
"matcher": {
"type": [
"string",
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"source": {
"$ref": "#/definitions/HookSource"
},
"sourcePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"statusMessage": {
"type": [
"string",
"null"
]
},
"timeoutSec": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"displayOrder",
"enabled",
"eventName",
"handlerType",
"isManaged",
"key",
"source",
"sourcePath",
"timeoutSec"
],
"type": "object"
},
"HookSource": {
"enum": [
"system",
"user",
"project",
"mdm",
"sessionFlags",
"plugin",
"cloudRequirements",
"legacyManagedConfigFile",
"legacyManagedConfigMdm",
"unknown"
],
"type": "string"
},
"HooksListEntry": {
"properties": {
"cwd": {
"type": "string"
},
"errors": {
"items": {
"$ref": "#/definitions/HookErrorInfo"
},
"type": "array"
},
"hooks": {
"items": {
"$ref": "#/definitions/HookMetadata"
},
"type": "array"
},
"warnings": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"cwd",
"errors",
"hooks",
"warnings"
],
"type": "object"
}
},
"properties": {
"data": {
"items": {
"$ref": "#/definitions/HooksListEntry"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "HooksListResponse",
"type": "object"
}

View File

@@ -23,9 +23,6 @@
},
{
"properties": {
"codexStreamlinedLogin": {
"type": "boolean"
},
"type": {
"enum": [
"chatgpt"

View File

@@ -1,5 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ModelProviderCapabilitiesReadParams",
"type": "object"
}

View File

@@ -1,21 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"imageGeneration": {
"type": "boolean"
},
"namespaceTools": {
"type": "boolean"
},
"webSearch": {
"type": "boolean"
}
},
"required": [
"imageGeneration",
"namespaceTools",
"webSearch"
],
"title": "ModelProviderCapabilitiesReadResponse",
"type": "object"
}

View File

@@ -1,13 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"remotePluginId": {
"type": "string"
}
},
"required": [
"remotePluginId"
],
"title": "PluginShareDeleteParams",
"type": "object"
}

View File

@@ -1,5 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PluginShareDeleteResponse",
"type": "object"
}

View File

@@ -1,5 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PluginShareListParams",
"type": "object"
}

View File

@@ -1,291 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"PluginAuthPolicy": {
"enum": [
"ON_INSTALL",
"ON_USE"
],
"type": "string"
},
"PluginInstallPolicy": {
"enum": [
"NOT_AVAILABLE",
"AVAILABLE",
"INSTALLED_BY_DEFAULT"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
"type": [
"string",
"null"
]
},
"capabilities": {
"items": {
"type": "string"
},
"type": "array"
},
"category": {
"type": [
"string",
"null"
]
},
"composerIcon": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
],
"description": "Local composer icon path, resolved from the installed plugin package."
},
"composerIconUrl": {
"description": "Remote composer icon URL from the plugin catalog.",
"type": [
"string",
"null"
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developerName": {
"type": [
"string",
"null"
]
},
"displayName": {
"type": [
"string",
"null"
]
},
"logo": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
],
"description": "Local logo path, resolved from the installed plugin package."
},
"logoUrl": {
"description": "Remote logo URL from the plugin catalog.",
"type": [
"string",
"null"
]
},
"longDescription": {
"type": [
"string",
"null"
]
},
"privacyPolicyUrl": {
"type": [
"string",
"null"
]
},
"screenshotUrls": {
"description": "Remote screenshot URLs from the plugin catalog.",
"items": {
"type": "string"
},
"type": "array"
},
"screenshots": {
"description": "Local screenshot paths, resolved from the installed plugin package.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"shortDescription": {
"type": [
"string",
"null"
]
},
"termsOfServiceUrl": {
"type": [
"string",
"null"
]
},
"websiteUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
"capabilities",
"screenshotUrls",
"screenshots"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
},
{
"properties": {
"path": {
"type": [
"string",
"null"
]
},
"refName": {
"type": [
"string",
"null"
]
},
"sha": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"git"
],
"title": "GitPluginSourceType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "GitPluginSource",
"type": "object"
},
{
"description": "The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.",
"properties": {
"type": {
"enum": [
"remote"
],
"title": "RemotePluginSourceType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RemotePluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"authPolicy": {
"$ref": "#/definitions/PluginAuthPolicy"
},
"enabled": {
"type": "boolean"
},
"id": {
"type": "string"
},
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installed": {
"type": "boolean"
},
"interface": {
"anyOf": [
{
"$ref": "#/definitions/PluginInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"authPolicy",
"enabled",
"id",
"installPolicy",
"installed",
"name",
"source"
],
"type": "object"
}
},
"properties": {
"data": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"data"
],
"title": "PluginShareListResponse",
"type": "object"
}

View File

@@ -1,25 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
}
},
"properties": {
"pluginPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"remotePluginId": {
"type": [
"string",
"null"
]
}
},
"required": [
"pluginPath"
],
"title": "PluginShareSaveParams",
"type": "object"
}

View File

@@ -1,17 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"remotePluginId": {
"type": "string"
},
"shareUrl": {
"type": "string"
}
},
"required": [
"remotePluginId",
"shareUrl"
],
"title": "PluginShareSaveResponse",
"type": "object"
}

View File

@@ -143,6 +143,38 @@
}
]
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
"id": {
"type": "string"
},
"parent": {
"type": [
"string",
"null"
]
},
"preexisting_untracked_dirs": {
"items": {
"type": "string"
},
"type": "array"
},
"preexisting_untracked_files": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"preexisting_untracked_dirs",
"preexisting_untracked_files"
],
"type": "object"
},
"ImageDetail": {
"enum": [
"auto",
@@ -712,6 +744,26 @@
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
"$ref": "#/definitions/GhostCommit"
},
"type": {
"enum": [
"ghost_snapshot"
],
"title": "GhostSnapshotResponseItemType",
"type": "string"
}
},
"required": [
"ghost_commit",
"type"
],
"title": "GhostSnapshotResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {

View File

@@ -1,31 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RemoteControlConnectionStatus": {
"enum": [
"disabled",
"connecting",
"connected",
"errored"
],
"type": "string"
}
},
"description": "Current remote-control connection status and environment id exposed to clients.",
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"status": {
"$ref": "#/definitions/RemoteControlConnectionStatus"
}
},
"required": [
"status"
],
"title": "RemoteControlStatusChangedNotification",
"type": "object"
}

View File

@@ -64,19 +64,26 @@
}
]
},
"PermissionProfileModificationParams": {
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
"path"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"title": "PathFileSystemPathType",
"type": "string"
}
},
@@ -84,45 +91,304 @@
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"subpath": {
"type": [
"array",
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"profile"
"restricted"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"id",
"entries",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"SandboxMode": {
"enum": [
"read-only",
@@ -207,6 +473,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{

View File

@@ -5,59 +5,6 @@
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"ActivePermissionProfile": {
"properties": {
"extends": {
"default": null,
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
"type": [
"string",
"null"
]
},
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
"id"
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -2538,6 +2485,18 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical active permissions view for this thread."
},
"reasoningEffort": {
"anyOf": [
{
@@ -2554,7 +2513,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
},
"serviceTier": {
"anyOf": [

View File

@@ -138,6 +138,202 @@
}
]
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -206,6 +402,38 @@
}
]
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
"id": {
"type": "string"
},
"parent": {
"type": [
"string",
"null"
]
},
"preexisting_untracked_dirs": {
"items": {
"type": "string"
},
"type": "array"
},
"preexisting_untracked_files": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"preexisting_untracked_dirs",
"preexisting_untracked_files"
],
"type": "object"
},
"ImageDetail": {
"enum": [
"auto",
@@ -298,65 +526,135 @@
}
]
},
"PermissionProfileModificationParams": {
"PermissionProfile": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"additionalWritableRoot"
"managed"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"path",
"fileSystem",
"network",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"entries": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"array",
"integer",
"null"
]
},
"type": {
"enum": [
"profile"
"restricted"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"id",
"entries",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"Personality": {
"enum": [
"none",
@@ -842,6 +1140,26 @@
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
"$ref": "#/definitions/GhostCommit"
},
"type": {
"enum": [
"ghost_snapshot"
],
"title": "GhostSnapshotResponseItemType",
"type": "string"
}
},
"required": [
"ghost_commit",
"type"
],
"title": "GhostSnapshotResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -1062,6 +1380,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{

View File

@@ -5,59 +5,6 @@
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"ActivePermissionProfile": {
"properties": {
"extends": {
"default": null,
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
"type": [
"string",
"null"
]
},
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
"id"
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -2538,6 +2485,18 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical active permissions view for this thread."
},
"reasoningEffort": {
"anyOf": [
{
@@ -2554,7 +2513,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
},
"serviceTier": {
"anyOf": [

View File

@@ -90,19 +90,26 @@
],
"type": "object"
},
"PermissionProfileModificationParams": {
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
"path"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"title": "PathFileSystemPathType",
"type": "string"
}
},
@@ -110,45 +117,304 @@
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"subpath": {
"type": [
"array",
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"profile"
"restricted"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"id",
"entries",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"Personality": {
"enum": [
"none",
@@ -260,6 +526,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{

View File

@@ -5,59 +5,6 @@
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"ActivePermissionProfile": {
"properties": {
"extends": {
"default": null,
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
"type": [
"string",
"null"
]
},
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
"id"
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -2538,6 +2485,18 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"default": null,
"description": "Canonical active permissions view for this thread."
},
"reasoningEffort": {
"anyOf": [
{
@@ -2554,7 +2513,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
},
"serviceTier": {
"anyOf": [

View File

@@ -99,6 +99,202 @@
],
"type": "object"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"ModeKind": {
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
@@ -114,65 +310,135 @@
],
"type": "string"
},
"PermissionProfileModificationParams": {
"PermissionProfile": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"additionalWritableRoot"
"managed"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"path",
"fileSystem",
"network",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"entries": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"array",
"integer",
"null"
]
},
"type": {
"enum": [
"profile"
"restricted"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"id",
"entries",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"Personality": {
"enum": [
"none",
@@ -563,6 +829,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Details of a ghost commit created from a repository state.
*/
export type GhostCommit = { id: string, parent: string | null, preexisting_untracked_files: Array<string>, preexisting_untracked_dirs: Array<string>, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InternalSessionSource = "memory_consolidation";

View File

@@ -3,6 +3,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ContentItem } from "./ContentItem";
import type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
import type { GhostCommit } from "./GhostCommit";
import type { LocalShellAction } from "./LocalShellAction";
import type { LocalShellStatus } from "./LocalShellStatus";
import type { MessagePhase } from "./MessagePhase";
@@ -14,4 +15,4 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
/**
* Set when using the Responses API.
*/
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InternalSessionSource } from "./InternalSessionSource";
import type { SubAgentSource } from "./SubAgentSource";
export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "custom": string } | { "internal": InternalSessionSource } | { "subagent": SubAgentSource } | "unknown";
export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "custom": string } | { "subagent": SubAgentSource } | "unknown";

View File

@@ -29,6 +29,7 @@ export type { GetAuthStatusParams } from "./GetAuthStatusParams";
export type { GetAuthStatusResponse } from "./GetAuthStatusResponse";
export type { GetConversationSummaryParams } from "./GetConversationSummaryParams";
export type { GetConversationSummaryResponse } from "./GetConversationSummaryResponse";
export type { GhostCommit } from "./GhostCommit";
export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams";
export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse";
export type { GitSha } from "./GitSha";
@@ -37,7 +38,6 @@ export type { InitializeCapabilities } from "./InitializeCapabilities";
export type { InitializeParams } from "./InitializeParams";
export type { InitializeResponse } from "./InitializeResponse";
export type { InputModality } from "./InputModality";
export type { InternalSessionSource } from "./InternalSessionSource";
export type { LocalShellAction } from "./LocalShellAction";
export type { LocalShellExecAction } from "./LocalShellExecAction";
export type { LocalShellStatus } from "./LocalShellStatus";

View File

@@ -1,21 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActivePermissionProfileModification } from "./ActivePermissionProfileModification";
export type ActivePermissionProfile = {
/**
* Identifier from `default_permissions` or the implicit built-in default,
* such as `:workspace` or a user-defined `[permissions.<id>]` profile.
*/
id: string,
/**
* Parent profile identifier once permissions profiles support
* inheritance. This is currently always `null`.
*/
extends: string | null,
/**
* Bounded user-requested modifications applied on top of the named
* profile, if any.
*/
modifications: Array<ActivePermissionProfileModification>, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type ActivePermissionProfileModification = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };

View File

@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CommandExecTerminalSize } from "./CommandExecTerminalSize";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
/**
@@ -12,10 +13,12 @@ import type { SandboxPolicy } from "./SandboxPolicy";
* sent only after all `command/exec/outputDelta` notifications for that
* connection have been emitted.
*/
export type CommandExecParams = {/**
export type CommandExecParams = {
/**
* Command argv vector. Empty arrays are rejected.
*/
command: Array<string>, /**
command: Array<string>,
/**
* Optional client-supplied, connection-scoped process id.
*
* Required for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up
@@ -23,63 +26,81 @@ command: Array<string>, /**
* `command/exec/terminate` calls. When omitted, buffered execution gets an
* internal id that is not exposed to the client.
*/
processId?: string | null, /**
processId?: string | null,
/**
* Enable PTY mode.
*
* This implies `streamStdin` and `streamStdoutStderr`.
*/
tty?: boolean, /**
tty?: boolean,
/**
* Allow follow-up `command/exec/write` requests to write stdin bytes.
*
* Requires a client-supplied `processId`.
*/
streamStdin?: boolean, /**
streamStdin?: boolean,
/**
* Stream stdout/stderr via `command/exec/outputDelta` notifications.
*
* Streamed bytes are not duplicated into the final response and require a
* client-supplied `processId`.
*/
streamStdoutStderr?: boolean, /**
streamStdoutStderr?: boolean,
/**
* Optional per-stream stdout/stderr capture cap in bytes.
*
* When omitted, the server default applies. Cannot be combined with
* `disableOutputCap`.
*/
outputBytesCap?: number | null, /**
outputBytesCap?: number | null,
/**
* Disable stdout/stderr capture truncation for this request.
*
* Cannot be combined with `outputBytesCap`.
*/
disableOutputCap?: boolean, /**
disableOutputCap?: boolean,
/**
* Disable the timeout entirely for this request.
*
* Cannot be combined with `timeoutMs`.
*/
disableTimeout?: boolean, /**
disableTimeout?: boolean,
/**
* Optional timeout in milliseconds.
*
* When omitted, the server default applies. Cannot be combined with
* `disableTimeout`.
*/
timeoutMs?: number | null, /**
timeoutMs?: number | null,
/**
* Optional working directory. Defaults to the server cwd.
*/
cwd?: string | null, /**
cwd?: string | null,
/**
* Optional environment overrides merged into the server-computed
* environment.
*
* Matching names override inherited values. Set a key to `null` to unset
* an inherited variable.
*/
env?: { [key in string]?: string | null } | null, /**
env?: { [key in string]?: string | null } | null,
/**
* Optional initial PTY size in character cells. Only valid when `tty` is
* true.
*/
size?: CommandExecTerminalSize | null, /**
size?: CommandExecTerminalSize | null,
/**
* Optional sandbox policy for this command.
*
* Uses the same shape as thread/turn execution sandbox configuration and
* defaults to the user's configured policy when omitted. Cannot be
* combined with `permissionProfile`.
*/
sandboxPolicy?: SandboxPolicy | null};
sandboxPolicy?: SandboxPolicy | null,
/**
* Optional full permissions profile for this command.
*
* Defaults to the user's configured permissions when omitted. Cannot be
* combined with `sandboxPolicy`.
*/
permissionProfile?: PermissionProfile | null, };

View File

@@ -2,12 +2,15 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile";
import type { CommandAction } from "./CommandAction";
import type { CommandExecutionApprovalDecision } from "./CommandExecutionApprovalDecision";
import type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
import type { NetworkApprovalContext } from "./NetworkApprovalContext";
import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
export type CommandExecutionRequestApprovalParams = {threadId: string, turnId: string, itemId: string, /**
export type CommandExecutionRequestApprovalParams = { threadId: string, turnId: string, itemId: string,
/**
* Unique identifier for this specific approval callback.
*
* For regular shell/unified_exec approvals, this is null.
@@ -16,25 +19,40 @@ export type CommandExecutionRequestApprovalParams = {threadId: string, turnId: s
* one parent `itemId`, so `approvalId` is a distinct opaque callback id
* (a UUID) used to disambiguate routing.
*/
approvalId?: string | null, /**
approvalId?: string | null,
/**
* Optional explanatory reason (e.g. request for network access).
*/
reason?: string | null, /**
reason?: string | null,
/**
* Optional context for a managed-network approval prompt.
*/
networkApprovalContext?: NetworkApprovalContext | null, /**
networkApprovalContext?: NetworkApprovalContext | null,
/**
* The command to be executed.
*/
command?: string | null, /**
command?: string | null,
/**
* The command's working directory.
*/
cwd?: AbsolutePathBuf | null, /**
cwd?: AbsolutePathBuf | null,
/**
* Best-effort parsed command actions for friendly display.
*/
commandActions?: Array<CommandAction> | null, /**
commandActions?: Array<CommandAction> | null,
/**
* Optional additional permissions requested for this command.
*/
additionalPermissions?: AdditionalPermissionProfile | null,
/**
* Optional proposed execpolicy amendment to allow similar commands without prompting.
*/
proposedExecpolicyAmendment?: ExecPolicyAmendment | null, /**
proposedExecpolicyAmendment?: ExecPolicyAmendment | null,
/**
* Optional proposed network policy amendments (allow/deny host) for future requests.
*/
proposedNetworkPolicyAmendments?: Array<NetworkPolicyAmendment> | null};
proposedNetworkPolicyAmendments?: Array<NetworkPolicyAmendment> | null,
/**
* Ordered list of decisions the client may present for this prompt.
*/
availableDecisions?: Array<CommandExecutionApprovalDecision> | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CommandMigration = { name: string, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ExternalAgentConfigMigrationItemType = "AGENTS_MD" | "CONFIG" | "SKILLS" | "PLUGINS" | "MCP_SERVER_CONFIG" | "SUBAGENTS" | "HOOKS" | "COMMANDS" | "SESSIONS";
export type ExternalAgentConfigMigrationItemType = "AGENTS_MD" | "CONFIG" | "SKILLS" | "PLUGINS" | "MCP_SERVER_CONFIG";

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HookErrorInfo = { path: string, message: string, };

View File

@@ -1,9 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { HookEventName } from "./HookEventName";
import type { HookHandlerType } from "./HookHandlerType";
import type { HookSource } from "./HookSource";
export type HookMetadata = { key: string, eventName: HookEventName, handlerType: HookHandlerType, matcher: string | null, command: string | null, timeoutSec: bigint, statusMessage: string | null, sourcePath: AbsolutePathBuf, source: HookSource, pluginId: string | null, displayOrder: bigint, enabled: boolean, isManaged: boolean, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HookMigration = { name: string, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HookSource = "system" | "user" | "project" | "mdm" | "sessionFlags" | "plugin" | "cloudRequirements" | "legacyManagedConfigFile" | "legacyManagedConfigMdm" | "unknown";
export type HookSource = "system" | "user" | "project" | "mdm" | "sessionFlags" | "legacyManagedConfigFile" | "legacyManagedConfigMdm" | "unknown";

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HookErrorInfo } from "./HookErrorInfo";
import type { HookMetadata } from "./HookMetadata";
export type HooksListEntry = { cwd: string, hooks: Array<HookMetadata>, warnings: Array<string>, errors: Array<HookErrorInfo>, };

View File

@@ -1,9 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HooksListParams = {
/**
* When empty, defaults to the current session working directory.
*/
cwds?: Array<string>, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HooksListEntry } from "./HooksListEntry";
export type HooksListResponse = { data: Array<HooksListEntry>, };

View File

@@ -2,7 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt", codexStreamlinedLogin?: boolean, } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt" } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
/**
* Access token (JWT) supplied by the client.
* This token is used for backend API requests and email extraction.

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type McpServerMigration = { name: string, };

View File

@@ -1,11 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CommandMigration } from "./CommandMigration";
import type { HookMigration } from "./HookMigration";
import type { McpServerMigration } from "./McpServerMigration";
import type { PluginsMigration } from "./PluginsMigration";
import type { SessionMigration } from "./SessionMigration";
import type { SubagentMigration } from "./SubagentMigration";
export type MigrationDetails = { plugins: Array<PluginsMigration>, sessions: Array<SessionMigration>, mcpServers: Array<McpServerMigration>, hooks: Array<HookMigration>, subagents: Array<SubagentMigration>, commands: Array<CommandMigration>, };
export type MigrationDetails = { plugins: Array<PluginsMigration>, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ModelProviderCapabilitiesReadParams = Record<string, never>;

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ModelProviderCapabilitiesReadResponse = { namespaceTools: boolean, imageGeneration: boolean, webSearch: boolean, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PermissionProfileModificationParams = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PermissionProfileModificationParams } from "./PermissionProfileModificationParams";
export type PermissionProfileSelectionParams = { "type": "profile", id: string, modifications?: Array<PermissionProfileModificationParams> | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginShareDeleteParams = { remotePluginId: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginShareDeleteResponse = Record<string, never>;

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginShareListParams = Record<string, never>;

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PluginSummary } from "./PluginSummary";
export type PluginShareListResponse = { data: Array<PluginSummary>, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginShareSaveResponse = { remotePluginId: string, shareUrl: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RemoteControlConnectionStatus = "disabled" | "connecting" | "connected" | "errored";

View File

@@ -1,9 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RemoteControlConnectionStatus } from "./RemoteControlConnectionStatus";
/**
* Current remote-control connection status and environment id exposed to clients.
*/
export type RemoteControlStatusChangedNotification = { status: RemoteControlConnectionStatus, environmentId: string | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SessionMigration = { path: string, cwd: string, title: string | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SubagentMigration = { name: string, };

View File

@@ -5,6 +5,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -17,15 +18,27 @@ import type { SandboxMode } from "./SandboxMode";
* Prefer using thread_id whenever possible.
*/
export type ThreadForkParams = {threadId: string, /**
* [UNSTABLE] Specify the rollout path to fork from.
* If specified, the thread_id param will be ignored.
*/
path?: string | null, /**
* Configuration overrides for the forked thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for the forked thread. Cannot be combined
* with `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
* When true, return only thread metadata and live fork state without
* populating `thread.turns`. This is useful when the client plans to call
* `thread/turns/list` immediately after forking.
*/
excludeTurns?: boolean};
excludeTurns?: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
persistExtendedHistory: boolean};

View File

@@ -6,18 +6,26 @@ import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf,
/**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, /**
* Legacy sandbox policy retained for compatibility. Experimental clients
* should prefer `permissionProfile` when they need exact runtime
* permissions.
approvalsReviewer: ApprovalsReviewer,
/**
* Legacy sandbox policy retained for compatibility. New clients should use
* `permissionProfile` when present as the canonical active permissions
* view.
*/
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
sandbox: SandboxPolicy,
/**
* Canonical active permissions view for this thread.
*/
permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, };

View File

@@ -2,10 +2,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Personality } from "../Personality";
import type { ResponseItem } from "../ResponseItem";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -20,15 +22,32 @@ import type { SandboxMode } from "./SandboxMode";
* Prefer using thread_id whenever possible.
*/
export type ThreadResumeParams = {threadId: string, /**
* [UNSTABLE] FOR CODEX CLOUD - DO NOT USE.
* If specified, the thread will be resumed with the provided history
* instead of loaded from disk.
*/
history?: Array<ResponseItem> | null, /**
* [UNSTABLE] Specify the rollout path to resume from.
* If specified, the thread_id param will be ignored.
*/
path?: string | null, /**
* Configuration overrides for the resumed thread, if any.
*/
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for the resumed thread. Cannot be combined
* with `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
* When true, return only thread metadata and live-resume state without
* populating `thread.turns`. This is useful when the client plans to call
* `thread/turns/list` immediately after resuming.
*/
excludeTurns?: boolean};
excludeTurns?: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
persistExtendedHistory: boolean};

View File

@@ -6,18 +6,26 @@ import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf,
/**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, /**
* Legacy sandbox policy retained for compatibility. Experimental clients
* should prefer `permissionProfile` when they need exact runtime
* permissions.
approvalsReviewer: ApprovalsReviewer,
/**
* Legacy sandbox policy retained for compatibility. New clients should use
* `permissionProfile` when present as the canonical active permissions
* view.
*/
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
sandbox: SandboxPolicy,
/**
* Canonical active permissions view for this thread.
*/
permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, };

View File

@@ -6,6 +6,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
import type { ThreadStartSource } from "./ThreadStartSource";
@@ -13,4 +14,16 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for this thread. Cannot be combined with
* `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
* If true, opt into emitting raw Responses API items on the event stream.
* This is for internal use only (e.g. Codex Cloud).
*/
experimentalRawEvents: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on resume/fork/read.
*/
persistExtendedHistory: boolean};

View File

@@ -6,18 +6,26 @@ import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf,
/**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, /**
* Legacy sandbox policy retained for compatibility. Experimental clients
* should prefer `permissionProfile` when they need exact runtime
* permissions.
approvalsReviewer: ApprovalsReviewer,
/**
* Legacy sandbox policy retained for compatibility. New clients should use
* `permissionProfile` when present as the canonical active permissions
* view.
*/
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
sandbox: SandboxPolicy,
/**
* Canonical active permissions view for this thread.
*/
permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, };

Some files were not shown because too many files have changed in this diff Show More