mirror of
https://github.com/openai/codex.git
synced 2026-05-09 13:52:41 +00:00
Compare commits
147 Commits
rust-v0.12
...
dev/cc/bro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09637409b0 | ||
|
|
79908b64a1 | ||
|
|
6014b6679f | ||
|
|
8426edf71e | ||
|
|
7b3de63041 | ||
|
|
127be0612c | ||
|
|
9121132c8f | ||
|
|
70090c9ff7 | ||
|
|
8121710ffe | ||
|
|
7dd08e304c | ||
|
|
06f3b4836a | ||
|
|
31f8813e3e | ||
|
|
93d53f655b | ||
|
|
719431da6e | ||
|
|
b52083146c | ||
|
|
f2bc2f26a9 | ||
|
|
5cc5f12efc | ||
|
|
c70cdc108f | ||
|
|
487716ae74 | ||
|
|
a85d265097 | ||
|
|
c02814c106 | ||
|
|
3516cb9751 | ||
|
|
8a97f3cf03 | ||
|
|
c37f7434ba | ||
|
|
a73403a890 | ||
|
|
87d0cf1a62 | ||
|
|
ae863e72a2 | ||
|
|
8f3c06cc97 | ||
|
|
ac4332c05b | ||
|
|
ebe602d005 | ||
|
|
4e677d62da | ||
|
|
bb536d65bd | ||
|
|
8b07132e09 | ||
|
|
515aa9a4fb | ||
|
|
fedcefe9da | ||
|
|
c8abcbf925 | ||
|
|
7bcd4626c4 | ||
|
|
8774229a89 | ||
|
|
6eab7519b4 | ||
|
|
98f67b15d3 | ||
|
|
74f06dcdfb | ||
|
|
13dbcda28f | ||
|
|
8de2a7a16d | ||
|
|
782191547c | ||
|
|
e20391e567 | ||
|
|
4241df4d79 | ||
|
|
b1546008fc | ||
|
|
f63b19bedd | ||
|
|
8d5da3ffe5 | ||
|
|
72a39e3a96 | ||
|
|
afbddabc8b | ||
|
|
973c5c823e | ||
|
|
b15074d0a4 | ||
|
|
8ce48f9968 | ||
|
|
0690ab0842 | ||
|
|
9d1e5df4b2 | ||
|
|
07c8b8c77c | ||
|
|
73cd831952 | ||
|
|
5cf0adba93 | ||
|
|
05fd904572 | ||
|
|
8356806fc9 | ||
|
|
47fba5df4a | ||
|
|
d0204c3dcc | ||
|
|
445629815c | ||
|
|
df966996a7 | ||
|
|
cecca5ae06 | ||
|
|
1c420a90cd | ||
|
|
91ca551df8 | ||
|
|
70ac0f123c | ||
|
|
c41b74c453 | ||
|
|
5cac3f896d | ||
|
|
d92c909ee4 | ||
|
|
5597925155 | ||
|
|
857146b328 | ||
|
|
6ed0440611 | ||
|
|
3d10ba9f36 | ||
|
|
e1ec9e63a0 | ||
|
|
6f328d5e02 | ||
|
|
e6db1a9442 | ||
|
|
80fb0704ee | ||
|
|
8c47e36504 | ||
|
|
4c39ad33cb | ||
|
|
24be9ac0a4 | ||
|
|
c9f7c88f3d | ||
|
|
f8fe96d548 | ||
|
|
cb8b1bbcd6 | ||
|
|
ebdf3a878c | ||
|
|
1211a90a35 | ||
|
|
1fed948c66 | ||
|
|
1dae5788e1 | ||
|
|
6662c0f312 | ||
|
|
026df712cc | ||
|
|
1ea90410e1 | ||
|
|
af39e488bc | ||
|
|
5d08315c00 | ||
|
|
b599849d86 | ||
|
|
3ef09c71d3 | ||
|
|
8d3992d830 | ||
|
|
162f4e3183 | ||
|
|
2a8ce9b319 | ||
|
|
d77d23da2e | ||
|
|
5b0d9df1d0 | ||
|
|
d6d79ffcc7 | ||
|
|
158b2a4201 | ||
|
|
52e79ee49a | ||
|
|
7d15936e69 | ||
|
|
2223b31c06 | ||
|
|
c6465c1ec2 | ||
|
|
5e6cbbadf7 | ||
|
|
891722849d | ||
|
|
2dbde94aa9 | ||
|
|
3291463ff1 | ||
|
|
2e598df6fc | ||
|
|
66b0781502 | ||
|
|
c6e7d564c3 | ||
|
|
89698ad1c3 | ||
|
|
10e2a73b3c | ||
|
|
3b74a4d3b1 | ||
|
|
34d71d43eb | ||
|
|
1de7a9bf69 | ||
|
|
e1ba87ccb2 | ||
|
|
25ac0e4527 | ||
|
|
0700f979ba | ||
|
|
7f7c7c2c07 | ||
|
|
3377afd84a | ||
|
|
de2ccf9473 | ||
|
|
640a1b23ea | ||
|
|
9e26613657 | ||
|
|
3afb185a4f | ||
|
|
4c68bd728f | ||
|
|
a036584104 | ||
|
|
bc5a1b961e | ||
|
|
c6bcd27832 | ||
|
|
273c2e21a9 | ||
|
|
01de13b7e6 | ||
|
|
0670d8971a | ||
|
|
f6797c3ac6 | ||
|
|
6138063656 | ||
|
|
ccec84b148 | ||
|
|
4e0cf945b7 | ||
|
|
087c9c1f1f | ||
|
|
5b7d6f5c4f | ||
|
|
0156b1e61f | ||
|
|
5e737372ee | ||
|
|
a61c785040 | ||
|
|
598bbcdb58 | ||
|
|
21e19912e0 |
@@ -7,7 +7,7 @@ description: Run a GitHub issue digest for openai/codex by feature-area labels,
|
||||
|
||||
## Objective
|
||||
|
||||
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".
|
||||
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.
|
||||
|
||||
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,21 +29,46 @@ 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. 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.
|
||||
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.
|
||||
- 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`.
|
||||
5. In `## Details`, include a compact table only when useful:
|
||||
- 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:
|
||||
- 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.
|
||||
@@ -52,7 +77,7 @@ Use `--window "past week"` or `--window-hours 168` when the user asks for a non-
|
||||
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 the digest footer or final line.
|
||||
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.
|
||||
|
||||
## Reaction Handling
|
||||
|
||||
@@ -64,7 +89,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 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.
|
||||
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.
|
||||
|
||||
## Freshness
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
SCRIPT_VERSION = 2
|
||||
SCRIPT_VERSION = 4
|
||||
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 = 10
|
||||
TWO_ATTENTION_INTERACTION_THRESHOLD = 20
|
||||
ONE_ATTENTION_INTERACTION_THRESHOLD = 5
|
||||
TWO_ATTENTION_INTERACTION_THRESHOLD = 10
|
||||
ALL_LABEL_PHRASES = {"all", "all areas", "all labels", "all-areas", "all-labels", "*"}
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ def search_issue_numbers(queries, limit):
|
||||
numbers = {}
|
||||
for query in queries:
|
||||
page = 1
|
||||
seen_for_query = 0
|
||||
while True:
|
||||
payload = gh_json(
|
||||
[
|
||||
@@ -315,6 +316,10 @@ def search_issue_numbers(queries, limit):
|
||||
"-f",
|
||||
f"q={query}",
|
||||
"-f",
|
||||
"sort=updated",
|
||||
"-f",
|
||||
"order=desc",
|
||||
"-f",
|
||||
"per_page=100",
|
||||
"-f",
|
||||
f"page={page}",
|
||||
@@ -331,7 +336,8 @@ def search_issue_numbers(queries, limit):
|
||||
number = item.get("number")
|
||||
if isinstance(number, int):
|
||||
numbers[number] = str(item.get("updated_at") or "")
|
||||
if len(items) < 100 or len(numbers) >= limit:
|
||||
seen_for_query += 1
|
||||
if len(items) < 100 or seen_for_query >= limit:
|
||||
break
|
||||
page += 1
|
||||
ordered = sorted(
|
||||
|
||||
@@ -51,6 +51,77 @@ 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")
|
||||
@@ -227,19 +298,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"] == 10
|
||||
assert one_day["very_high"] == 20
|
||||
assert one_day["elevated"] == 5
|
||||
assert one_day["very_high"] == 10
|
||||
|
||||
half_day = collect_issue_digest.attention_thresholds_for_window(12)
|
||||
assert half_day["elevated"] == 5
|
||||
assert half_day["very_high"] == 10
|
||||
assert half_day["elevated"] == 3
|
||||
assert half_day["very_high"] == 5
|
||||
|
||||
week = collect_issue_digest.attention_thresholds_for_window(168)
|
||||
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) == "🔥🔥"
|
||||
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) == "🔥🔥"
|
||||
|
||||
|
||||
def test_fetch_comments_uses_since_filter_and_page_cap(monkeypatch):
|
||||
@@ -300,7 +371,7 @@ def test_attention_markers_count_human_user_interactions():
|
||||
"user": {"login": f"user-{idx}"},
|
||||
"body": "same here",
|
||||
}
|
||||
for idx in range(9)
|
||||
for idx in range(4)
|
||||
]
|
||||
comments.append(
|
||||
{
|
||||
@@ -322,8 +393,8 @@ def test_attention_markers_count_human_user_interactions():
|
||||
comment_chars=100,
|
||||
)
|
||||
|
||||
assert summary["user_interactions"] == 10
|
||||
assert summary["activity"]["new_human_comments"] == 9
|
||||
assert summary["user_interactions"] == 5
|
||||
assert summary["activity"]["new_human_comments"] == 4
|
||||
assert summary["attention"] is True
|
||||
assert summary["attention_level"] == 1
|
||||
assert summary["attention_marker"] == "🔥"
|
||||
@@ -337,7 +408,7 @@ def test_attention_markers_count_human_user_interactions():
|
||||
"user": {"login": f"extra-user-{idx}"},
|
||||
"body": "also seeing this",
|
||||
}
|
||||
for idx in range(11)
|
||||
for idx in range(100, 106)
|
||||
)
|
||||
|
||||
summary = collect_issue_digest.summarize_issue(
|
||||
@@ -350,7 +421,7 @@ def test_attention_markers_count_human_user_interactions():
|
||||
comment_chars=100,
|
||||
)
|
||||
|
||||
assert summary["user_interactions"] == 20
|
||||
assert summary["user_interactions"] == 10
|
||||
assert summary["attention_level"] == 2
|
||||
assert summary["attention_marker"] == "🔥🔥"
|
||||
|
||||
|
||||
6
.github/workflows/rust-release-windows.yml
vendored
6
.github/workflows/rust-release-windows.yml
vendored
@@ -24,7 +24,9 @@ jobs:
|
||||
build-windows-binaries:
|
||||
name: Build Windows binaries - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
timeout-minutes: 60
|
||||
# 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
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
@@ -137,7 +139,7 @@ jobs:
|
||||
- build-windows-binaries
|
||||
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 90
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
4
.github/workflows/rust-release.yml
vendored
4
.github/workflows/rust-release.yml
vendored
@@ -49,7 +49,9 @@ jobs:
|
||||
needs: tag-check
|
||||
name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
# 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
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
@@ -19,6 +19,12 @@ 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.
|
||||
|
||||
@@ -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 --full-auto ${quoted_args}"
|
||||
docker exec -it "$CONTAINER_NAME" bash -c "cd \"/app$WORK_DIR\" && codex --sandbox workspace-write --ask-for-approval on-request ${quoted_args}"
|
||||
|
||||
88
codex-rs/Cargo.lock
generated
88
codex-rs/Cargo.lock
generated
@@ -1748,6 +1748,21 @@ 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"
|
||||
@@ -1853,10 +1868,13 @@ 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",
|
||||
@@ -1864,6 +1882,7 @@ dependencies = [
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-otel",
|
||||
"codex-plugin",
|
||||
"codex-protocol",
|
||||
"codex-rmcp-client",
|
||||
"codex-rollout",
|
||||
@@ -2082,9 +2101,11 @@ dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"codex-connectors",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-git-utils",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-plugin",
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-cli",
|
||||
"pretty_assertions",
|
||||
@@ -2462,12 +2483,31 @@ dependencies = [
|
||||
"zstd 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-core-api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-analytics",
|
||||
"codex-app-server-protocol",
|
||||
"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"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"codex-analytics",
|
||||
"codex-app-server-protocol",
|
||||
"codex-config",
|
||||
"codex-core-skills",
|
||||
@@ -2682,6 +2722,32 @@ 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"
|
||||
@@ -2765,6 +2831,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"codex-config",
|
||||
"codex-plugin",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"futures",
|
||||
@@ -2801,6 +2868,7 @@ dependencies = [
|
||||
"cc",
|
||||
"clap",
|
||||
"codex-core",
|
||||
"codex-process-hardening",
|
||||
"codex-protocol",
|
||||
"codex-sandboxing",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -2850,6 +2918,7 @@ dependencies = [
|
||||
"codex-terminal-detection",
|
||||
"codex-utils-template",
|
||||
"core_test_support",
|
||||
"jsonwebtoken",
|
||||
"keyring",
|
||||
"once_cell",
|
||||
"os_info",
|
||||
@@ -2913,9 +2982,7 @@ dependencies = [
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-models-manager",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -3134,6 +3201,7 @@ dependencies = [
|
||||
name = "codex-plugin"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-config",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-plugins",
|
||||
"thiserror 2.0.18",
|
||||
@@ -3445,6 +3513,17 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-thread-manager-sample"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"codex-core-api",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-thread-store"
|
||||
version = "0.0.0"
|
||||
@@ -3516,6 +3595,7 @@ dependencies = [
|
||||
"codex-install-context",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-model-provider",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-otel",
|
||||
@@ -3803,6 +3883,8 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3827,6 +3909,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-pty",
|
||||
@@ -4078,6 +4161,7 @@ dependencies = [
|
||||
"assert_cmd",
|
||||
"base64 0.22.1",
|
||||
"codex-arg0",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-exec-server",
|
||||
"codex-features",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"aws-auth",
|
||||
"analytics",
|
||||
"agent-graph-store",
|
||||
"agent-identity",
|
||||
"backend-client",
|
||||
"ansi-escape",
|
||||
@@ -31,6 +32,7 @@ members = [
|
||||
"shell-escalation",
|
||||
"skills",
|
||||
"core",
|
||||
"core-api",
|
||||
"core-plugins",
|
||||
"core-skills",
|
||||
"hooks",
|
||||
@@ -40,6 +42,8 @@ members = [
|
||||
"exec-server",
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"external-agent-migration",
|
||||
"external-agent-sessions",
|
||||
"keyring-store",
|
||||
"file-search",
|
||||
"linux-sandbox",
|
||||
@@ -95,6 +99,7 @@ members = [
|
||||
"state",
|
||||
"terminal-detection",
|
||||
"test-binary-support",
|
||||
"thread-manager-sample",
|
||||
"thread-store",
|
||||
"uds",
|
||||
"codex-experimental-api-macros",
|
||||
@@ -116,6 +121,7 @@ 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" }
|
||||
@@ -139,6 +145,7 @@ 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" }
|
||||
@@ -146,6 +153,8 @@ codex-exec = { path = "exec" }
|
||||
codex-file-system = { path = "file-system" }
|
||||
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" }
|
||||
@@ -445,6 +454,7 @@ 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",
|
||||
|
||||
@@ -59,19 +59,22 @@ To test to see what happens when a command is run under the sandbox provided by
|
||||
|
||||
```
|
||||
# macOS
|
||||
codex sandbox macos [--full-auto] [--log-denials] [COMMAND]...
|
||||
codex sandbox macos [--log-denials] [COMMAND]...
|
||||
|
||||
# Linux
|
||||
codex sandbox linux [--full-auto] [COMMAND]...
|
||||
codex sandbox linux [COMMAND]...
|
||||
|
||||
# Windows
|
||||
codex sandbox windows [--full-auto] [COMMAND]...
|
||||
codex sandbox windows [COMMAND]...
|
||||
|
||||
# Legacy aliases
|
||||
codex debug seatbelt [--full-auto] [--log-denials] [COMMAND]...
|
||||
codex debug landlock [--full-auto] [COMMAND]...
|
||||
codex debug seatbelt [--log-denials] [COMMAND]...
|
||||
codex debug landlock [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:
|
||||
|
||||
6
codex-rs/agent-graph-store/BUILD.bazel
Normal file
6
codex-rs/agent-graph-store/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "agent-graph-store",
|
||||
crate_name = "codex_agent_graph_store",
|
||||
)
|
||||
25
codex-rs/agent-graph-store/Cargo.toml
Normal file
25
codex-rs/agent-graph-store/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[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"] }
|
||||
20
codex-rs/agent-graph-store/src/error.rs
Normal file
20
codex-rs/agent-graph-store/src/error.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
12
codex-rs/agent-graph-store/src/lib.rs
Normal file
12
codex-rs/agent-graph-store/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! 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;
|
||||
325
codex-rs/agent-graph-store/src/local.rs
Normal file
325
codex-rs/agent-graph-store/src/local.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
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]
|
||||
);
|
||||
}
|
||||
}
|
||||
55
codex-rs/agent-graph-store/src/store.rs
Normal file
55
codex-rs/agent-graph-store/src/store.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
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>>;
|
||||
}
|
||||
42
codex-rs/agent-graph-store/src/types.rs
Normal file
42
codex-rs/agent-graph-store/src/types.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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::account::PlanType as AccountPlanType;
|
||||
use codex_protocol::auth::PlanType as AuthPlanType;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use crypto_box::SecretKey as Curve25519SecretKey;
|
||||
use ed25519_dalek::Signer as _;
|
||||
@@ -19,6 +19,9 @@ 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;
|
||||
@@ -28,6 +31,9 @@ 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)]
|
||||
@@ -58,12 +64,16 @@ 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: AccountPlanType,
|
||||
pub plan_type: AuthPlanType,
|
||||
pub chatgpt_account_is_fedramp: bool,
|
||||
}
|
||||
|
||||
@@ -115,27 +125,49 @@ 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,
|
||||
public_key_base64: Option<&str>,
|
||||
jwks: Option<&JwkSet>,
|
||||
) -> Result<AgentIdentityJwtClaims> {
|
||||
let Some(public_key_base64) = public_key_base64 else {
|
||||
let Some(jwks) = jwks else {
|
||||
return decode_agent_identity_jwt_payload(jwt);
|
||||
};
|
||||
|
||||
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)
|
||||
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)
|
||||
.map(|data| data.claims)
|
||||
.context("failed to decode agent identity JWT")
|
||||
.context("failed to verify agent identity JWT")
|
||||
}
|
||||
|
||||
fn decode_agent_identity_jwt_payload<T: DeserializeOwned>(jwt: &str) -> Result<T> {
|
||||
@@ -279,6 +311,15 @@ 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
|
||||
@@ -290,29 +331,6 @@ 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(),
|
||||
@@ -390,6 +408,8 @@ mod tests {
|
||||
use jsonwebtoken::Header;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use codex_protocol::auth::KnownPlan;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -472,6 +492,10 @@ 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",
|
||||
@@ -481,44 +505,70 @@ mod tests {
|
||||
"chatgpt_account_is_fedramp": false,
|
||||
}));
|
||||
|
||||
let claims =
|
||||
decode_agent_identity_jwt(&jwt, /*public_key_base64*/ None).expect("JWT should decode");
|
||||
let claims = decode_agent_identity_jwt(&jwt, /*jwks*/ 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: AccountPlanType::Pro,
|
||||
plan_type: AuthPlanType::Known(KnownPlan::Pro),
|
||||
chatgpt_account_is_fedramp: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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());
|
||||
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");
|
||||
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: AccountPlanType::Pro,
|
||||
plan_type: AuthPlanType::Known(KnownPlan::Pro),
|
||||
chatgpt_account_is_fedramp: false,
|
||||
};
|
||||
let jwt = jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::EdDSA),
|
||||
&test_jwt_header("test-key"),
|
||||
&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,
|
||||
@@ -527,45 +577,40 @@ mod tests {
|
||||
"plan_type": "pro",
|
||||
"chatgpt_account_is_fedramp": claims.chatgpt_account_is_fedramp,
|
||||
}),
|
||||
&EncodingKey::from_ed_der(private_key_pkcs8.as_bytes()),
|
||||
&test_rsa_encoding_key(),
|
||||
)
|
||||
.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: AccountPlanType::Pro,
|
||||
plan_type: AuthPlanType::Known(KnownPlan::Pro),
|
||||
chatgpt_account_is_fedramp: false,
|
||||
};
|
||||
assert_eq!(
|
||||
decode_agent_identity_jwt(&jwt, Some(&public_key_base64)).expect("JWT should verify"),
|
||||
decode_agent_identity_jwt(&jwt, Some(&jwks)).expect("JWT should verify"),
|
||||
expected_claims
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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(),
|
||||
);
|
||||
fn decode_agent_identity_jwt_rejects_untrusted_kid() {
|
||||
let jwks = test_jwks("other-key");
|
||||
|
||||
let jwt = jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::EdDSA),
|
||||
&test_jwt_header("test-key"),
|
||||
&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",
|
||||
@@ -574,19 +619,111 @@ mod tests {
|
||||
"plan_type": "pro",
|
||||
"chatgpt_account_is_fedramp": false,
|
||||
}),
|
||||
&EncodingKey::from_ed_der(private_key_pkcs8.as_bytes()),
|
||||
&test_rsa_encoding_key(),
|
||||
)
|
||||
.expect("JWT should encode");
|
||||
|
||||
decode_agent_identity_jwt(&jwt, Some(&other_public_key_base64))
|
||||
.expect_err("JWT should not verify");
|
||||
decode_agent_identity_jwt(&jwt, Some(&jwks)).expect_err("JWT should not verify");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_chatgpt_base_url_strips_codex_before_backend_api() {
|
||||
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() {
|
||||
assert_eq!(
|
||||
normalize_chatgpt_base_url("https://chatgpt.com/codex"),
|
||||
"https://chatgpt.com/backend-api"
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,18 +59,19 @@ 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::ClientResponse;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
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;
|
||||
@@ -141,27 +142,25 @@ fn sample_thread_with_source(
|
||||
}
|
||||
}
|
||||
|
||||
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_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_app_server_client_metadata() -> CodexAppServerClientMetadata {
|
||||
@@ -183,7 +182,11 @@ fn sample_runtime_metadata() -> CodexRuntimeMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_thread_resume_response(thread_id: &str, ephemeral: bool, model: &str) -> ClientResponse {
|
||||
fn sample_thread_resume_response(
|
||||
thread_id: &str,
|
||||
ephemeral: bool,
|
||||
model: &str,
|
||||
) -> ClientResponsePayload {
|
||||
sample_thread_resume_response_with_source(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
@@ -197,23 +200,21 @@ fn sample_thread_resume_response_with_source(
|
||||
ephemeral: bool,
|
||||
model: &str,
|
||||
source: AppServerSessionSource,
|
||||
) -> 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,
|
||||
},
|
||||
}
|
||||
) -> 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,
|
||||
})
|
||||
}
|
||||
|
||||
fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest {
|
||||
@@ -235,21 +236,18 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest
|
||||
}
|
||||
}
|
||||
|
||||
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_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_started_notification(thread_id: &str, turn_id: &str) -> ServerNotification {
|
||||
@@ -355,13 +353,10 @@ fn sample_turn_steer_request(
|
||||
}
|
||||
}
|
||||
|
||||
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 sample_turn_steer_response(turn_id: &str) -> ClientResponsePayload {
|
||||
ClientResponsePayload::TurnSteer(TurnSteerResponse {
|
||||
turn_id: turn_id.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn no_active_turn_steer_error() -> JSONRPCErrorError {
|
||||
@@ -426,7 +421,7 @@ async fn ingest_rejected_turn_steer(
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(4),
|
||||
request: Box::new(sample_turn_steer_request(
|
||||
@@ -486,8 +481,9 @@ async fn ingest_turn_prerequisites(
|
||||
ingest_initialize(reducer, out).await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-2", /*ephemeral*/ false, "gpt-5",
|
||||
)),
|
||||
@@ -500,7 +496,7 @@ async fn ingest_turn_prerequisites(
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(3),
|
||||
request: Box::new(sample_turn_start_request("thread-2", /*request_id*/ 3)),
|
||||
@@ -510,9 +506,10 @@ async fn ingest_turn_prerequisites(
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_turn_start_response("turn-2", /*request_id*/ 3)),
|
||||
request_id: RequestId::Integer(3),
|
||||
response: Box::new(sample_turn_start_response("turn-2")),
|
||||
},
|
||||
out,
|
||||
)
|
||||
@@ -862,8 +859,9 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-no-client",
|
||||
/*ephemeral*/ false,
|
||||
@@ -906,8 +904,9 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(2),
|
||||
response: Box::new(sample_thread_resume_response(
|
||||
"thread-1", /*ephemeral*/ true, "gpt-5",
|
||||
)),
|
||||
@@ -954,6 +953,65 @@ 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();
|
||||
@@ -986,8 +1044,9 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(2),
|
||||
response: Box::new(sample_thread_resume_response_with_source(
|
||||
"thread-1",
|
||||
/*ephemeral*/ false,
|
||||
@@ -1097,8 +1156,9 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(1),
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-guardian",
|
||||
/*ephemeral*/ false,
|
||||
@@ -1499,6 +1559,15 @@ 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 {
|
||||
@@ -1513,6 +1582,8 @@ 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");
|
||||
}
|
||||
@@ -1867,7 +1938,7 @@ async fn accepted_turn_steer_emits_expected_event() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(4),
|
||||
request: Box::new(sample_turn_steer_request(
|
||||
@@ -1879,9 +1950,10 @@ async fn accepted_turn_steer_emits_expected_event() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 4)),
|
||||
request_id: RequestId::Integer(4),
|
||||
response: Box::new(sample_turn_steer_response("turn-2")),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
@@ -2021,7 +2093,7 @@ async fn turn_start_error_response_discards_pending_start_request() {
|
||||
ingest_initialize(&mut reducer, &mut out).await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(3),
|
||||
request: Box::new(sample_turn_start_request("thread-2", /*request_id*/ 3)),
|
||||
@@ -2045,9 +2117,10 @@ async fn turn_start_error_response_discards_pending_start_request() {
|
||||
// failed turn/start request and attach request-scoped connection metadata.
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_turn_start_response("turn-2", /*request_id*/ 3)),
|
||||
request_id: RequestId::Integer(3),
|
||||
response: Box::new(sample_turn_start_response("turn-2")),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
@@ -2162,7 +2235,7 @@ async fn accepted_steers_increment_turn_steer_count() {
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(4),
|
||||
request: Box::new(sample_turn_steer_request(
|
||||
@@ -2174,9 +2247,10 @@ async fn accepted_steers_increment_turn_steer_count() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 4)),
|
||||
request_id: RequestId::Integer(4),
|
||||
response: Box::new(sample_turn_steer_response("turn-2")),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
@@ -2184,7 +2258,7 @@ async fn accepted_steers_increment_turn_steer_count() {
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(5),
|
||||
request: Box::new(sample_turn_steer_request(
|
||||
@@ -2208,7 +2282,7 @@ async fn accepted_steers_increment_turn_steer_count() {
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id: 7,
|
||||
request_id: RequestId::Integer(6),
|
||||
request: Box::new(sample_turn_steer_request(
|
||||
@@ -2220,9 +2294,10 @@ async fn accepted_steers_increment_turn_steer_count() {
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_turn_steer_response("turn-2", /*request_id*/ 6)),
|
||||
request_id: RequestId::Integer(6),
|
||||
response: Box::new(sample_turn_steer_response("turn-2")),
|
||||
},
|
||||
&mut out,
|
||||
)
|
||||
|
||||
@@ -22,11 +22,13 @@ use crate::facts::TurnResolvedConfigFact;
|
||||
use crate::facts::TurnTokenUsageFact;
|
||||
use crate::reducer::AnalyticsReducer;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ClientResponse;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
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;
|
||||
@@ -49,8 +51,7 @@ pub(crate) struct AnalyticsEventsQueue {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnalyticsEventsClient {
|
||||
queue: AnalyticsEventsQueue,
|
||||
analytics_enabled: Option<bool>,
|
||||
queue: Option<AnalyticsEventsQueue>,
|
||||
}
|
||||
|
||||
impl AnalyticsEventsQueue {
|
||||
@@ -119,11 +120,15 @@ impl AnalyticsEventsClient {
|
||||
analytics_enabled: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url),
|
||||
analytics_enabled,
|
||||
queue: (analytics_enabled != Some(false))
|
||||
.then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disabled() -> Self {
|
||||
Self { queue: None }
|
||||
}
|
||||
|
||||
pub fn track_skill_invocations(
|
||||
&self,
|
||||
tracking: TrackEventsContext,
|
||||
@@ -181,16 +186,30 @@ impl AnalyticsEventsClient {
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_request(&self, connection_id: u64, request_id: RequestId, request: ClientRequest) {
|
||||
self.record_fact(AnalyticsFact::Request {
|
||||
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 {
|
||||
connection_id,
|
||||
request_id,
|
||||
request: Box::new(request),
|
||||
request: Box::new(request.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) {
|
||||
if !self.queue.should_enqueue_app_used(&tracking, &app) {
|
||||
let Some(queue) = self.queue.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if !queue.should_enqueue_app_used(&tracking, &app) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(
|
||||
@@ -205,7 +224,10 @@ impl AnalyticsEventsClient {
|
||||
}
|
||||
|
||||
pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) {
|
||||
if !self.queue.should_enqueue_plugin_used(&tracking, &plugin) {
|
||||
let Some(queue) = self.queue.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if !queue.should_enqueue_plugin_used(&tracking, &plugin) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed(
|
||||
@@ -268,15 +290,30 @@ impl AnalyticsEventsClient {
|
||||
}
|
||||
|
||||
pub(crate) fn record_fact(&self, input: AnalyticsFact) {
|
||||
if self.analytics_enabled == Some(false) {
|
||||
return;
|
||||
if let Some(queue) = self.queue.as_ref() {
|
||||
queue.try_send(input);
|
||||
}
|
||||
self.queue.try_send(input);
|
||||
}
|
||||
|
||||
pub fn track_response(&self, connection_id: u64, response: ClientResponse) {
|
||||
self.record_fact(AnalyticsFact::Response {
|
||||
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(_)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::ClientResponse {
|
||||
connection_id,
|
||||
request_id,
|
||||
response: Box::new(response),
|
||||
});
|
||||
}
|
||||
@@ -299,6 +336,19 @@ 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(
|
||||
@@ -341,3 +391,7 @@ async fn send_track_events(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "client_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
221
codex-rs/analytics/src/client_tests.rs
Normal file
221
codex-rs/analytics/src/client_tests.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
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)));
|
||||
}
|
||||
@@ -684,6 +684,8 @@ 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",
|
||||
|
||||
@@ -2,11 +2,13 @@ use crate::events::AppServerRpcTransport;
|
||||
use crate::events::CodexRuntimeMetadata;
|
||||
use crate::events::GuardianReviewEventParams;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ClientResponse;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
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;
|
||||
@@ -272,14 +274,15 @@ pub(crate) enum AnalyticsFact {
|
||||
runtime: CodexRuntimeMetadata,
|
||||
rpc_transport: AppServerRpcTransport,
|
||||
},
|
||||
Request {
|
||||
ClientRequest {
|
||||
connection_id: u64,
|
||||
request_id: RequestId,
|
||||
request: Box<ClientRequest>,
|
||||
},
|
||||
Response {
|
||||
ClientResponse {
|
||||
connection_id: u64,
|
||||
response: Box<ClientResponse>,
|
||||
request_id: RequestId,
|
||||
response: Box<ClientResponsePayload>,
|
||||
},
|
||||
ErrorResponse {
|
||||
connection_id: u64,
|
||||
@@ -287,6 +290,13 @@ 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.
|
||||
|
||||
@@ -172,18 +172,21 @@ impl AnalyticsReducer {
|
||||
rpc_transport,
|
||||
);
|
||||
}
|
||||
AnalyticsFact::Request {
|
||||
AnalyticsFact::ClientRequest {
|
||||
connection_id,
|
||||
request_id,
|
||||
request,
|
||||
} => {
|
||||
self.ingest_request(connection_id, request_id, *request);
|
||||
}
|
||||
AnalyticsFact::Response {
|
||||
AnalyticsFact::ClientResponse {
|
||||
connection_id,
|
||||
request_id,
|
||||
response,
|
||||
} => {
|
||||
self.ingest_response(connection_id, *response, out);
|
||||
if let Some(response) = response.into_client_response(request_id) {
|
||||
self.ingest_response(connection_id, response, out);
|
||||
}
|
||||
}
|
||||
AnalyticsFact::ErrorResponse {
|
||||
connection_id,
|
||||
@@ -196,6 +199,13 @@ 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);
|
||||
|
||||
@@ -99,10 +99,6 @@ pub mod legacy_core {
|
||||
pub use codex_core::personality_migration::*;
|
||||
}
|
||||
|
||||
pub mod plugins {
|
||||
pub use codex_core::plugins::PluginsManager;
|
||||
}
|
||||
|
||||
pub mod review_format {
|
||||
pub use codex_core::review_format::*;
|
||||
}
|
||||
@@ -2029,14 +2025,17 @@ 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::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,
|
||||
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"),
|
||||
)
|
||||
.expect("runtime paths"),
|
||||
}));
|
||||
.await,
|
||||
);
|
||||
|
||||
let runtime_args = InProcessClientStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
|
||||
@@ -354,6 +354,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigBatchWriteParams": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
@@ -849,7 +860,11 @@
|
||||
"CONFIG",
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG"
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1389,6 +1404,29 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HooksListParams": {
|
||||
"properties": {
|
||||
"cwds": {
|
||||
"description": "When empty, defaults to the current session working directory.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -1560,6 +1598,9 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"codexStreamlinedLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgpt"
|
||||
@@ -1695,6 +1736,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginParams": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -1778,16 +1830,49 @@
|
||||
},
|
||||
"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": {
|
||||
@@ -1826,6 +1911,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkAccess": {
|
||||
"enum": [
|
||||
"restricted",
|
||||
@@ -1951,6 +2039,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -1962,6 +2075,40 @@
|
||||
],
|
||||
"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",
|
||||
@@ -2039,6 +2186,37 @@
|
||||
],
|
||||
"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": {
|
||||
@@ -3022,6 +3200,27 @@
|
||||
],
|
||||
"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": {
|
||||
@@ -3131,6 +3330,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TextElement": {
|
||||
"properties": {
|
||||
"byteRange": {
|
||||
@@ -4752,6 +4962,30 @@
|
||||
"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": {
|
||||
@@ -4872,6 +5106,78 @@
|
||||
"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": {
|
||||
@@ -5376,6 +5682,30 @@
|
||||
"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": {
|
||||
|
||||
@@ -1032,6 +1032,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"FileChangeOutputDeltaNotification": {
|
||||
"description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.",
|
||||
"properties": {
|
||||
"delta": {
|
||||
"type": "string"
|
||||
@@ -1900,6 +1901,8 @@
|
||||
"project",
|
||||
"mdm",
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
@@ -2602,6 +2605,33 @@
|
||||
],
|
||||
"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": [
|
||||
{
|
||||
@@ -3901,7 +3931,7 @@
|
||||
"ThreadRealtimeStartedNotification": {
|
||||
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"realtimeSessionId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -5162,6 +5192,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Deprecated legacy apply_patch output stream notification.",
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
@@ -5341,6 +5372,26 @@
|
||||
"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": {
|
||||
|
||||
@@ -642,6 +642,30 @@
|
||||
"title": "Skills/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"hooks/list"
|
||||
],
|
||||
"title": "Hooks/listRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/HooksListParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Hooks/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -762,6 +786,78 @@
|
||||
"title": "Plugin/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/save"
|
||||
],
|
||||
"title": "Plugin/share/saveRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/PluginShareSaveParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/saveRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/list"
|
||||
],
|
||||
"title": "Plugin/share/listRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/PluginShareListParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/delete"
|
||||
],
|
||||
"title": "Plugin/share/deleteRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/PluginShareDeleteParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/deleteRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1266,6 +1362,30 @@
|
||||
"title": "Model/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"modelProvider/capabilities/read"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ModelProviderCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ModelProvider/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -4169,6 +4289,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Deprecated legacy apply_patch output stream notification.",
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
@@ -4348,6 +4469,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"remoteControl/status/changed"
|
||||
],
|
||||
"title": "RemoteControl/status/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/RemoteControlStatusChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "RemoteControl/status/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -5345,6 +5486,59 @@
|
||||
"title": "AccountUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"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/v2/ActivePermissionProfileModification"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfileModification": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModification",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AddCreditsNudgeCreditType": {
|
||||
"enum": [
|
||||
"credits",
|
||||
@@ -6766,6 +6960,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -8326,7 +8531,11 @@
|
||||
"CONFIG",
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG"
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8392,6 +8601,7 @@
|
||||
},
|
||||
"FileChangeOutputDeltaNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.",
|
||||
"properties": {
|
||||
"delta": {
|
||||
"type": "string"
|
||||
@@ -9509,6 +9719,21 @@
|
||||
"title": "HookCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"HookErrorInfo": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
@@ -9535,6 +9760,87 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookMetadata": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"displayOrder": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"eventName": {
|
||||
"$ref": "#/definitions/v2/HookEventName"
|
||||
},
|
||||
"handlerType": {
|
||||
"$ref": "#/definitions/v2/HookHandlerType"
|
||||
},
|
||||
"isManaged": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"matcher": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"pluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/v2/HookSource"
|
||||
},
|
||||
"sourcePath": {
|
||||
"$ref": "#/definitions/v2/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"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookOutputEntry": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
@@ -9679,6 +9985,8 @@
|
||||
"project",
|
||||
"mdm",
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
@@ -9708,6 +10016,68 @@
|
||||
"title": "HookStartedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"HooksListEntry": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"errors": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/HookErrorInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/HookMetadata"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"warnings": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"errors",
|
||||
"hooks",
|
||||
"warnings"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HooksListParams": {
|
||||
"$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"
|
||||
},
|
||||
"HooksListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/HooksListEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"title": "HooksListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -10004,6 +10374,9 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"codexStreamlinedLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgpt"
|
||||
@@ -10447,6 +10820,17 @@
|
||||
"title": "McpResourceReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -10771,16 +11155,49 @@
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginsMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"sessions": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"plugins"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ModeKind": {
|
||||
@@ -10944,6 +11361,32 @@
|
||||
"title": "ModelListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadResponse": {
|
||||
"$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"
|
||||
},
|
||||
"ModelRerouteReason": {
|
||||
"enum": [
|
||||
"highRiskCyberActivity"
|
||||
@@ -11385,6 +11828,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
@@ -11396,6 +11864,40 @@
|
||||
],
|
||||
"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/v2/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"profile"
|
||||
],
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
@@ -11821,6 +12323,81 @@
|
||||
"title": "PluginReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDeleteParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"title": "PluginShareDeleteParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDeleteResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareDeleteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareListParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareListParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"title": "PluginShareListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pluginPath"
|
||||
],
|
||||
"title": "PluginShareSaveParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareSaveResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareUrl"
|
||||
],
|
||||
"title": "PluginShareSaveResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -12492,6 +13069,35 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlConnectionStatus": {
|
||||
"enum": [
|
||||
"disabled",
|
||||
"connecting",
|
||||
"connected",
|
||||
"errored"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/v2/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
],
|
||||
"title": "RemoteControlStatusChangedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"RequestId": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -13545,6 +14151,27 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SessionMigration": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SessionSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -14059,6 +14686,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TerminalInteractionNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -14516,7 +15154,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -15800,7 +16438,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"realtimeSessionId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -16027,7 +16665,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -16331,7 +16969,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -130,6 +130,59 @@
|
||||
"title": "AccountUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AddCreditsNudgeCreditType": {
|
||||
"enum": [
|
||||
"credits",
|
||||
@@ -1348,6 +1401,30 @@
|
||||
"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": {
|
||||
@@ -1468,6 +1545,78 @@
|
||||
"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": {
|
||||
@@ -1972,6 +2121,30 @@
|
||||
"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": {
|
||||
@@ -3285,6 +3458,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -4845,7 +5029,11 @@
|
||||
"CONFIG",
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG"
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4911,6 +5099,7 @@
|
||||
},
|
||||
"FileChangeOutputDeltaNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.",
|
||||
"properties": {
|
||||
"delta": {
|
||||
"type": "string"
|
||||
@@ -6139,6 +6328,21 @@
|
||||
"title": "HookCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"HookErrorInfo": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
@@ -6165,6 +6369,87 @@
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookOutputEntry": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
@@ -6309,6 +6594,8 @@
|
||||
"project",
|
||||
"mdm",
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
@@ -6338,6 +6625,68 @@
|
||||
"title": "HookStartedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"HooksListParams": {
|
||||
"$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"
|
||||
},
|
||||
"HooksListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HooksListEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"title": "HooksListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -6678,6 +7027,9 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"codexStreamlinedLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgpt"
|
||||
@@ -7121,6 +7473,17 @@
|
||||
"title": "McpResourceReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -7445,16 +7808,49 @@
|
||||
},
|
||||
"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": {
|
||||
@@ -7618,6 +8014,32 @@
|
||||
"title": "ModelListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ModelProviderCapabilitiesReadResponse": {
|
||||
"$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"
|
||||
},
|
||||
"ModelRerouteReason": {
|
||||
"enum": [
|
||||
"highRiskCyberActivity"
|
||||
@@ -8059,6 +8481,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -8070,6 +8517,40 @@
|
||||
],
|
||||
"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",
|
||||
@@ -8495,6 +8976,81 @@
|
||||
"title": "PluginReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDeleteParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"title": "PluginShareDeleteParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDeleteResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareDeleteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareListParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareListParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"title": "PluginShareListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pluginPath"
|
||||
],
|
||||
"title": "PluginShareSaveParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareSaveResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareUrl"
|
||||
],
|
||||
"title": "PluginShareSaveResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -9166,6 +9722,35 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlConnectionStatus": {
|
||||
"enum": [
|
||||
"disabled",
|
||||
"connecting",
|
||||
"connected",
|
||||
"errored"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"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"
|
||||
},
|
||||
"RequestId": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -10723,6 +11308,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Deprecated legacy apply_patch output stream notification.",
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
@@ -10902,6 +11488,26 @@
|
||||
"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": {
|
||||
@@ -11431,6 +12037,27 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SessionMigration": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SessionSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -11945,6 +12572,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TerminalInteractionNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -12402,7 +13040,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -13686,7 +14324,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"realtimeSessionId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
@@ -13913,7 +14551,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -14217,7 +14855,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExternalAgentConfigMigrationItem": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -39,22 +50,81 @@
|
||||
"CONFIG",
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG"
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"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": {
|
||||
@@ -74,6 +144,38 @@
|
||||
"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": {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExternalAgentConfigMigrationItem": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -39,22 +50,81 @@
|
||||
"CONFIG",
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG"
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"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": {
|
||||
@@ -74,6 +144,38 @@
|
||||
"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": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.",
|
||||
"properties": {
|
||||
"delta": {
|
||||
"type": "string"
|
||||
|
||||
@@ -160,6 +160,8 @@
|
||||
"project",
|
||||
"mdm",
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
|
||||
@@ -160,6 +160,8 @@
|
||||
"project",
|
||||
"mdm",
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
|
||||
14
codex-rs/app-server-protocol/schema/json/v2/HooksListParams.json
generated
Normal file
14
codex-rs/app-server-protocol/schema/json/v2/HooksListParams.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
173
codex-rs/app-server-protocol/schema/json/v2/HooksListResponse.json
generated
Normal file
173
codex-rs/app-server-protocol/schema/json/v2/HooksListResponse.json
generated
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
@@ -23,6 +23,9 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"codexStreamlinedLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgpt"
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json
generated
Normal file
5
codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadParams.json
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ModelProviderCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
}
|
||||
21
codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json
generated
Normal file
21
codex-rs/app-server-protocol/schema/json/v2/ModelProviderCapabilitiesReadResponse.json
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
13
codex-rs/app-server-protocol/schema/json/v2/PluginShareDeleteParams.json
generated
Normal file
13
codex-rs/app-server-protocol/schema/json/v2/PluginShareDeleteParams.json
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId"
|
||||
],
|
||||
"title": "PluginShareDeleteParams",
|
||||
"type": "object"
|
||||
}
|
||||
5
codex-rs/app-server-protocol/schema/json/v2/PluginShareDeleteResponse.json
generated
Normal file
5
codex-rs/app-server-protocol/schema/json/v2/PluginShareDeleteResponse.json
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareDeleteResponse",
|
||||
"type": "object"
|
||||
}
|
||||
5
codex-rs/app-server-protocol/schema/json/v2/PluginShareListParams.json
generated
Normal file
5
codex-rs/app-server-protocol/schema/json/v2/PluginShareListParams.json
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginShareListParams",
|
||||
"type": "object"
|
||||
}
|
||||
291
codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json
generated
Normal file
291
codex-rs/app-server-protocol/schema/json/v2/PluginShareListResponse.json
generated
Normal file
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
25
codex-rs/app-server-protocol/schema/json/v2/PluginShareSaveParams.json
generated
Normal file
25
codex-rs/app-server-protocol/schema/json/v2/PluginShareSaveParams.json
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
17
codex-rs/app-server-protocol/schema/json/v2/PluginShareSaveResponse.json
generated
Normal file
17
codex-rs/app-server-protocol/schema/json/v2/PluginShareSaveResponse.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareUrl"
|
||||
],
|
||||
"title": "PluginShareSaveResponse",
|
||||
"type": "object"
|
||||
}
|
||||
31
codex-rs/app-server-protocol/schema/json/v2/RemoteControlStatusChangedNotification.json
generated
Normal file
31
codex-rs/app-server-protocol/schema/json/v2/RemoteControlStatusChangedNotification.json
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
@@ -64,26 +64,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -91,304 +84,45 @@
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"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"
|
||||
],
|
||||
"id": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SandboxMode": {
|
||||
"enum": [
|
||||
"read-only",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"realtimeSessionId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
|
||||
@@ -138,202 +138,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
@@ -494,135 +298,65 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"path",
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -90,26 +90,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -117,304 +110,45 @@
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"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"
|
||||
],
|
||||
"id": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
@@ -99,202 +99,6 @@
|
||||
],
|
||||
"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": [
|
||||
@@ -310,135 +114,65 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"path",
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts
generated
Normal file
21
codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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>, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts
generated
Normal file
@@ -0,0 +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 { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type ActivePermissionProfileModification = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/CommandMigration.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/CommandMigration.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
@@ -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";
|
||||
export type ExternalAgentConfigMigrationItemType = "AGENTS_MD" | "CONFIG" | "SKILLS" | "PLUGINS" | "MCP_SERVER_CONFIG" | "SUBAGENTS" | "HOOKS" | "COMMANDS" | "SESSIONS";
|
||||
|
||||
@@ -2,4 +2,9 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Deprecated legacy notification for `apply_patch` textual output.
|
||||
*
|
||||
* The server no longer emits this notification.
|
||||
*/
|
||||
export type FileChangeOutputDeltaNotification = { threadId: string, turnId: string, itemId: string, delta: string, };
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/HookErrorInfo.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/HookErrorInfo.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
9
codex-rs/app-server-protocol/schema/typescript/v2/HookMetadata.ts
generated
Normal file
9
codex-rs/app-server-protocol/schema/typescript/v2/HookMetadata.ts
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
// 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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/HookMigration.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/HookMigration.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
@@ -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" | "legacyManagedConfigFile" | "legacyManagedConfigMdm" | "unknown";
|
||||
export type HookSource = "system" | "user" | "project" | "mdm" | "sessionFlags" | "plugin" | "cloudRequirements" | "legacyManagedConfigFile" | "legacyManagedConfigMdm" | "unknown";
|
||||
|
||||
7
codex-rs/app-server-protocol/schema/typescript/v2/HooksListEntry.ts
generated
Normal file
7
codex-rs/app-server-protocol/schema/typescript/v2/HooksListEntry.ts
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
// 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>, };
|
||||
9
codex-rs/app-server-protocol/schema/typescript/v2/HooksListParams.ts
generated
Normal file
9
codex-rs/app-server-protocol/schema/typescript/v2/HooksListParams.ts
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
// 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>, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/HooksListResponse.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/HooksListResponse.ts
generated
Normal file
@@ -0,0 +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 { HooksListEntry } from "./HooksListEntry";
|
||||
|
||||
export type HooksListResponse = { data: Array<HooksListEntry>, };
|
||||
@@ -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" } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
|
||||
export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt", codexStreamlinedLogin?: boolean, } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
|
||||
/**
|
||||
* Access token (JWT) supplied by the client.
|
||||
* This token is used for backend API requests and email extraction.
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/McpServerMigration.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/McpServerMigration.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
@@ -1,6 +1,11 @@
|
||||
// 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>, };
|
||||
export type MigrationDetails = { plugins: Array<PluginsMigration>, sessions: Array<SessionMigration>, mcpServers: Array<McpServerMigration>, hooks: Array<HookMigration>, subagents: Array<SubagentMigration>, commands: Array<CommandMigration>, };
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadParams.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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>;
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/ModelProviderCapabilitiesReadResponse.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts
generated
Normal file
@@ -0,0 +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 { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type PermissionProfileModificationParams = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts
generated
Normal file
@@ -0,0 +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 { PermissionProfileModificationParams } from "./PermissionProfileModificationParams";
|
||||
|
||||
export type PermissionProfileSelectionParams = { "type": "profile", id: string, modifications?: Array<PermissionProfileModificationParams> | null, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDeleteParams.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDeleteParams.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDeleteResponse.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDeleteResponse.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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>;
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareListParams.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareListParams.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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>;
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareListResponse.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareListResponse.ts
generated
Normal file
@@ -0,0 +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 { PluginSummary } from "./PluginSummary";
|
||||
|
||||
export type PluginShareListResponse = { data: Array<PluginSummary>, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareSaveParams.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareSaveParams.ts
generated
Normal file
@@ -0,0 +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 { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareSaveResponse.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareSaveResponse.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlConnectionStatus.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlConnectionStatus.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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";
|
||||
9
codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlStatusChangedNotification.ts
generated
Normal file
9
codex-rs/app-server-protocol/schema/typescript/v2/RemoteControlStatusChangedNotification.ts
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
// 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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/SessionMigration.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/SessionMigration.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/SubagentMigration.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/SubagentMigration.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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, };
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -6,4 +6,4 @@ import type { RealtimeConversationVersion } from "../RealtimeConversationVersion
|
||||
/**
|
||||
* EXPERIMENTAL - emitted when thread realtime startup is accepted.
|
||||
*/
|
||||
export type ThreadRealtimeStartedNotification = { threadId: string, sessionId: string | null, version: RealtimeConversationVersion, };
|
||||
export type ThreadRealtimeStartedNotification = { threadId: string, realtimeSessionId: string | null, version: RealtimeConversationVersion, };
|
||||
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -4,6 +4,8 @@ export type { Account } from "./Account";
|
||||
export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedNotification";
|
||||
export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUpdatedNotification";
|
||||
export type { AccountUpdatedNotification } from "./AccountUpdatedNotification";
|
||||
export type { ActivePermissionProfile } from "./ActivePermissionProfile";
|
||||
export type { ActivePermissionProfileModification } from "./ActivePermissionProfileModification";
|
||||
export type { AddCreditsNudgeCreditType } from "./AddCreditsNudgeCreditType";
|
||||
export type { AddCreditsNudgeEmailStatus } from "./AddCreditsNudgeEmailStatus";
|
||||
export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions";
|
||||
@@ -58,6 +60,7 @@ export type { CommandExecutionRequestApprovalParams } from "./CommandExecutionRe
|
||||
export type { CommandExecutionRequestApprovalResponse } from "./CommandExecutionRequestApprovalResponse";
|
||||
export type { CommandExecutionSource } from "./CommandExecutionSource";
|
||||
export type { CommandExecutionStatus } from "./CommandExecutionStatus";
|
||||
export type { CommandMigration } from "./CommandMigration";
|
||||
export type { Config } from "./Config";
|
||||
export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams";
|
||||
export type { ConfigEdit } from "./ConfigEdit";
|
||||
@@ -151,9 +154,12 @@ export type { GuardianRiskLevel } from "./GuardianRiskLevel";
|
||||
export type { GuardianUserAuthorization } from "./GuardianUserAuthorization";
|
||||
export type { GuardianWarningNotification } from "./GuardianWarningNotification";
|
||||
export type { HookCompletedNotification } from "./HookCompletedNotification";
|
||||
export type { HookErrorInfo } from "./HookErrorInfo";
|
||||
export type { HookEventName } from "./HookEventName";
|
||||
export type { HookExecutionMode } from "./HookExecutionMode";
|
||||
export type { HookHandlerType } from "./HookHandlerType";
|
||||
export type { HookMetadata } from "./HookMetadata";
|
||||
export type { HookMigration } from "./HookMigration";
|
||||
export type { HookOutputEntry } from "./HookOutputEntry";
|
||||
export type { HookOutputEntryKind } from "./HookOutputEntryKind";
|
||||
export type { HookPromptFragment } from "./HookPromptFragment";
|
||||
@@ -162,6 +168,9 @@ export type { HookRunSummary } from "./HookRunSummary";
|
||||
export type { HookScope } from "./HookScope";
|
||||
export type { HookSource } from "./HookSource";
|
||||
export type { HookStartedNotification } from "./HookStartedNotification";
|
||||
export type { HooksListEntry } from "./HooksListEntry";
|
||||
export type { HooksListParams } from "./HooksListParams";
|
||||
export type { HooksListResponse } from "./HooksListResponse";
|
||||
export type { ItemCompletedNotification } from "./ItemCompletedNotification";
|
||||
export type { ItemGuardianApprovalReviewCompletedNotification } from "./ItemGuardianApprovalReviewCompletedNotification";
|
||||
export type { ItemGuardianApprovalReviewStartedNotification } from "./ItemGuardianApprovalReviewStartedNotification";
|
||||
@@ -209,6 +218,7 @@ export type { McpResourceReadResponse } from "./McpResourceReadResponse";
|
||||
export type { McpServerElicitationAction } from "./McpServerElicitationAction";
|
||||
export type { McpServerElicitationRequestParams } from "./McpServerElicitationRequestParams";
|
||||
export type { McpServerElicitationRequestResponse } from "./McpServerElicitationRequestResponse";
|
||||
export type { McpServerMigration } from "./McpServerMigration";
|
||||
export type { McpServerOauthLoginCompletedNotification } from "./McpServerOauthLoginCompletedNotification";
|
||||
export type { McpServerOauthLoginParams } from "./McpServerOauthLoginParams";
|
||||
export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse";
|
||||
@@ -231,6 +241,8 @@ export type { Model } from "./Model";
|
||||
export type { ModelAvailabilityNux } from "./ModelAvailabilityNux";
|
||||
export type { ModelListParams } from "./ModelListParams";
|
||||
export type { ModelListResponse } from "./ModelListResponse";
|
||||
export type { ModelProviderCapabilitiesReadParams } from "./ModelProviderCapabilitiesReadParams";
|
||||
export type { ModelProviderCapabilitiesReadResponse } from "./ModelProviderCapabilitiesReadResponse";
|
||||
export type { ModelRerouteReason } from "./ModelRerouteReason";
|
||||
export type { ModelReroutedNotification } from "./ModelReroutedNotification";
|
||||
export type { ModelUpgradeInfo } from "./ModelUpgradeInfo";
|
||||
@@ -251,7 +263,9 @@ export type { PatchChangeKind } from "./PatchChangeKind";
|
||||
export type { PermissionGrantScope } from "./PermissionGrantScope";
|
||||
export type { PermissionProfile } from "./PermissionProfile";
|
||||
export type { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions";
|
||||
export type { PermissionProfileModificationParams } from "./PermissionProfileModificationParams";
|
||||
export type { PermissionProfileNetworkPermissions } from "./PermissionProfileNetworkPermissions";
|
||||
export type { PermissionProfileSelectionParams } from "./PermissionProfileSelectionParams";
|
||||
export type { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams";
|
||||
export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse";
|
||||
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
|
||||
@@ -266,6 +280,12 @@ export type { PluginListResponse } from "./PluginListResponse";
|
||||
export type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
|
||||
export type { PluginReadParams } from "./PluginReadParams";
|
||||
export type { PluginReadResponse } from "./PluginReadResponse";
|
||||
export type { PluginShareDeleteParams } from "./PluginShareDeleteParams";
|
||||
export type { PluginShareDeleteResponse } from "./PluginShareDeleteResponse";
|
||||
export type { PluginShareListParams } from "./PluginShareListParams";
|
||||
export type { PluginShareListResponse } from "./PluginShareListResponse";
|
||||
export type { PluginShareSaveParams } from "./PluginShareSaveParams";
|
||||
export type { PluginShareSaveResponse } from "./PluginShareSaveResponse";
|
||||
export type { PluginSource } from "./PluginSource";
|
||||
export type { PluginSummary } from "./PluginSummary";
|
||||
export type { PluginUninstallParams } from "./PluginUninstallParams";
|
||||
@@ -282,6 +302,8 @@ export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTe
|
||||
export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification";
|
||||
export type { RemoteControlClientConnectionAudience } from "./RemoteControlClientConnectionAudience";
|
||||
export type { RemoteControlClientEnrollmentAudience } from "./RemoteControlClientEnrollmentAudience";
|
||||
export type { RemoteControlConnectionStatus } from "./RemoteControlConnectionStatus";
|
||||
export type { RemoteControlStatusChangedNotification } from "./RemoteControlStatusChangedNotification";
|
||||
export type { RequestPermissionProfile } from "./RequestPermissionProfile";
|
||||
export type { ResidencyRequirement } from "./ResidencyRequirement";
|
||||
export type { ReviewDelivery } from "./ReviewDelivery";
|
||||
@@ -294,6 +316,7 @@ export type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
|
||||
export type { SendAddCreditsNudgeEmailParams } from "./SendAddCreditsNudgeEmailParams";
|
||||
export type { SendAddCreditsNudgeEmailResponse } from "./SendAddCreditsNudgeEmailResponse";
|
||||
export type { ServerRequestResolvedNotification } from "./ServerRequestResolvedNotification";
|
||||
export type { SessionMigration } from "./SessionMigration";
|
||||
export type { SessionSource } from "./SessionSource";
|
||||
export type { SkillDependencies } from "./SkillDependencies";
|
||||
export type { SkillErrorInfo } from "./SkillErrorInfo";
|
||||
@@ -310,6 +333,7 @@ export type { SkillsListExtraRootsForCwd } from "./SkillsListExtraRootsForCwd";
|
||||
export type { SkillsListParams } from "./SkillsListParams";
|
||||
export type { SkillsListResponse } from "./SkillsListResponse";
|
||||
export type { SortDirection } from "./SortDirection";
|
||||
export type { SubagentMigration } from "./SubagentMigration";
|
||||
export type { TerminalInteractionNotification } from "./TerminalInteractionNotification";
|
||||
export type { TextElement } from "./TextElement";
|
||||
export type { TextPosition } from "./TextPosition";
|
||||
|
||||
@@ -14,6 +14,7 @@ pub use export::generate_ts_with_options;
|
||||
pub use export::generate_types;
|
||||
pub use jsonrpc_lite::*;
|
||||
pub use protocol::common::*;
|
||||
pub use protocol::event_mapping::*;
|
||||
pub use protocol::item_builders::*;
|
||||
pub use protocol::thread_history::*;
|
||||
pub use protocol::v1::ApplyPatchApprovalParams;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
44
codex-rs/app-server-protocol/src/protocol/common_tests.rs
Normal file
44
codex-rs/app-server-protocol/src/protocol/common_tests.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn client_response_payload_returns_jsonrpc_parts_and_client_response() -> Result<()> {
|
||||
let (request_id, result, payload) =
|
||||
ClientResponsePayload::ThreadArchive(v2::ThreadArchiveResponse {})
|
||||
.into_jsonrpc_parts_and_payload(RequestId::Integer(7))?;
|
||||
|
||||
assert_eq!(request_id, RequestId::Integer(7));
|
||||
assert_eq!(result, json!({}));
|
||||
|
||||
let Some(ClientResponse::ThreadArchive {
|
||||
request_id,
|
||||
response: _,
|
||||
}) = payload.and_then(|payload| payload.into_client_response(RequestId::Integer(7)))
|
||||
else {
|
||||
panic!("expected thread/archive client response");
|
||||
};
|
||||
assert_eq!(request_id, RequestId::Integer(7));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_conversation_payload_stays_jsonrpc_only() -> Result<()> {
|
||||
let (request_id, result, payload) =
|
||||
ClientResponsePayload::InterruptConversation(v1::InterruptConversationResponse {
|
||||
abort_reason: TurnAbortReason::Interrupted,
|
||||
})
|
||||
.into_jsonrpc_parts_and_payload(RequestId::Integer(8))?;
|
||||
|
||||
assert_eq!(request_id, RequestId::Integer(8));
|
||||
assert_eq!(
|
||||
result,
|
||||
json!({
|
||||
"abortReason": "interrupted",
|
||||
})
|
||||
);
|
||||
assert!(payload.is_none());
|
||||
Ok(())
|
||||
}
|
||||
827
codex-rs/app-server-protocol/src/protocol/event_mapping.rs
Normal file
827
codex-rs/app-server-protocol/src/protocol/event_mapping.rs
Normal file
@@ -0,0 +1,827 @@
|
||||
use crate::protocol::common::ServerNotification;
|
||||
use crate::protocol::item_builders::build_command_execution_begin_item;
|
||||
use crate::protocol::item_builders::build_command_execution_end_item;
|
||||
use crate::protocol::item_builders::build_file_change_begin_item;
|
||||
use crate::protocol::item_builders::convert_patch_changes;
|
||||
use crate::protocol::v2::AgentMessageDeltaNotification;
|
||||
use crate::protocol::v2::CollabAgentState;
|
||||
use crate::protocol::v2::CollabAgentTool;
|
||||
use crate::protocol::v2::CollabAgentToolCallStatus;
|
||||
use crate::protocol::v2::CommandExecutionOutputDeltaNotification;
|
||||
use crate::protocol::v2::DynamicToolCallOutputContentItem;
|
||||
use crate::protocol::v2::DynamicToolCallStatus;
|
||||
use crate::protocol::v2::FileChangePatchUpdatedNotification;
|
||||
use crate::protocol::v2::ItemCompletedNotification;
|
||||
use crate::protocol::v2::ItemStartedNotification;
|
||||
use crate::protocol::v2::McpToolCallError;
|
||||
use crate::protocol::v2::McpToolCallResult;
|
||||
use crate::protocol::v2::McpToolCallStatus;
|
||||
use crate::protocol::v2::PlanDeltaNotification;
|
||||
use crate::protocol::v2::ReasoningSummaryPartAddedNotification;
|
||||
use crate::protocol::v2::ReasoningSummaryTextDeltaNotification;
|
||||
use crate::protocol::v2::ReasoningTextDeltaNotification;
|
||||
use crate::protocol::v2::TerminalInteractionNotification;
|
||||
use crate::protocol::v2::ThreadItem;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Build the v2 app-server notification that directly corresponds to a single core event.
|
||||
///
|
||||
/// This only covers the stateless event-to-notification projections that have a one-to-one
|
||||
/// mapping. Callers remain responsible for any surrounding state checks or side effects before
|
||||
/// invoking this helper.
|
||||
pub fn item_event_to_server_notification(
|
||||
msg: EventMsg,
|
||||
thread_id: &str,
|
||||
turn_id: &str,
|
||||
) -> ServerNotification {
|
||||
let thread_id = thread_id.to_string();
|
||||
let turn_id = turn_id.to_string();
|
||||
match msg {
|
||||
EventMsg::DynamicToolCallResponse(response) => {
|
||||
let status = if response.success {
|
||||
DynamicToolCallStatus::Completed
|
||||
} else {
|
||||
DynamicToolCallStatus::Failed
|
||||
};
|
||||
let duration_ms = i64::try_from(response.duration.as_millis()).ok();
|
||||
let item = ThreadItem::DynamicToolCall {
|
||||
id: response.call_id,
|
||||
namespace: response.namespace,
|
||||
tool: response.tool,
|
||||
arguments: response.arguments,
|
||||
status,
|
||||
content_items: Some(
|
||||
response
|
||||
.content_items
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
CoreDynamicToolCallOutputContentItem::InputText { text } => {
|
||||
DynamicToolCallOutputContentItem::InputText { text }
|
||||
}
|
||||
CoreDynamicToolCallOutputContentItem::InputImage { image_url } => {
|
||||
DynamicToolCallOutputContentItem::InputImage { image_url }
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
success: Some(response.success),
|
||||
duration_ms,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id: response.turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::McpToolCallBegin(begin_event) => {
|
||||
let item = ThreadItem::McpToolCall {
|
||||
id: begin_event.call_id,
|
||||
server: begin_event.invocation.server,
|
||||
tool: begin_event.invocation.tool,
|
||||
status: McpToolCallStatus::InProgress,
|
||||
arguments: begin_event.invocation.arguments.unwrap_or(JsonValue::Null),
|
||||
mcp_app_resource_uri: begin_event.mcp_app_resource_uri,
|
||||
result: None,
|
||||
error: None,
|
||||
duration_ms: None,
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::McpToolCallEnd(end_event) => {
|
||||
let status = if end_event.is_success() {
|
||||
McpToolCallStatus::Completed
|
||||
} else {
|
||||
McpToolCallStatus::Failed
|
||||
};
|
||||
let duration_ms = i64::try_from(end_event.duration.as_millis()).ok();
|
||||
let (result, error) = match &end_event.result {
|
||||
Ok(value) => (
|
||||
Some(Box::new(McpToolCallResult {
|
||||
content: value.content.clone(),
|
||||
structured_content: value.structured_content.clone(),
|
||||
meta: value.meta.clone(),
|
||||
})),
|
||||
None,
|
||||
),
|
||||
Err(message) => (
|
||||
None,
|
||||
Some(McpToolCallError {
|
||||
message: message.clone(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
let item = ThreadItem::McpToolCall {
|
||||
id: end_event.call_id,
|
||||
server: end_event.invocation.server,
|
||||
tool: end_event.invocation.tool,
|
||||
status,
|
||||
arguments: end_event.invocation.arguments.unwrap_or(JsonValue::Null),
|
||||
mcp_app_resource_uri: end_event.mcp_app_resource_uri,
|
||||
result,
|
||||
error,
|
||||
duration_ms,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentSpawnBegin(begin_event) => {
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: begin_event.call_id,
|
||||
tool: CollabAgentTool::SpawnAgent,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: begin_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: Vec::new(),
|
||||
prompt: Some(begin_event.prompt),
|
||||
model: Some(begin_event.model),
|
||||
reasoning_effort: Some(begin_event.reasoning_effort),
|
||||
agents_states: HashMap::new(),
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentSpawnEnd(end_event) => {
|
||||
let has_receiver = end_event.new_thread_id.is_some();
|
||||
let status = match &end_event.status {
|
||||
codex_protocol::protocol::AgentStatus::Errored(_)
|
||||
| codex_protocol::protocol::AgentStatus::NotFound => {
|
||||
CollabAgentToolCallStatus::Failed
|
||||
}
|
||||
_ if has_receiver => CollabAgentToolCallStatus::Completed,
|
||||
_ => CollabAgentToolCallStatus::Failed,
|
||||
};
|
||||
let (receiver_thread_ids, agents_states) = match end_event.new_thread_id {
|
||||
Some(id) => {
|
||||
let receiver_id = id.to_string();
|
||||
let received_status = CollabAgentState::from(end_event.status.clone());
|
||||
(
|
||||
vec![receiver_id.clone()],
|
||||
[(receiver_id, received_status)].into_iter().collect(),
|
||||
)
|
||||
}
|
||||
None => (Vec::new(), HashMap::new()),
|
||||
};
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: end_event.call_id,
|
||||
tool: CollabAgentTool::SpawnAgent,
|
||||
status,
|
||||
sender_thread_id: end_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids,
|
||||
prompt: Some(end_event.prompt),
|
||||
model: Some(end_event.model),
|
||||
reasoning_effort: Some(end_event.reasoning_effort),
|
||||
agents_states,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentInteractionBegin(begin_event) => {
|
||||
let receiver_thread_ids = vec![begin_event.receiver_thread_id.to_string()];
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: begin_event.call_id,
|
||||
tool: CollabAgentTool::SendInput,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: begin_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids,
|
||||
prompt: Some(begin_event.prompt),
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: HashMap::new(),
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabAgentInteractionEnd(end_event) => {
|
||||
let status = match &end_event.status {
|
||||
codex_protocol::protocol::AgentStatus::Errored(_)
|
||||
| codex_protocol::protocol::AgentStatus::NotFound => {
|
||||
CollabAgentToolCallStatus::Failed
|
||||
}
|
||||
_ => CollabAgentToolCallStatus::Completed,
|
||||
};
|
||||
let receiver_id = end_event.receiver_thread_id.to_string();
|
||||
let received_status = CollabAgentState::from(end_event.status);
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: end_event.call_id,
|
||||
tool: CollabAgentTool::SendInput,
|
||||
status,
|
||||
sender_thread_id: end_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![receiver_id.clone()],
|
||||
prompt: Some(end_event.prompt),
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: [(receiver_id, received_status)].into_iter().collect(),
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabWaitingBegin(begin_event) => {
|
||||
let receiver_thread_ids = begin_event
|
||||
.receiver_thread_ids
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: begin_event.call_id,
|
||||
tool: CollabAgentTool::Wait,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: begin_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids,
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: HashMap::new(),
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabWaitingEnd(end_event) => {
|
||||
let status = if end_event.statuses.values().any(|status| {
|
||||
matches!(
|
||||
status,
|
||||
codex_protocol::protocol::AgentStatus::Errored(_)
|
||||
| codex_protocol::protocol::AgentStatus::NotFound
|
||||
)
|
||||
}) {
|
||||
CollabAgentToolCallStatus::Failed
|
||||
} else {
|
||||
CollabAgentToolCallStatus::Completed
|
||||
};
|
||||
let receiver_thread_ids = end_event.statuses.keys().map(ToString::to_string).collect();
|
||||
let agents_states = end_event
|
||||
.statuses
|
||||
.iter()
|
||||
.map(|(id, status)| (id.to_string(), CollabAgentState::from(status.clone())))
|
||||
.collect();
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: end_event.call_id,
|
||||
tool: CollabAgentTool::Wait,
|
||||
status,
|
||||
sender_thread_id: end_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids,
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabCloseBegin(begin_event) => {
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: begin_event.call_id,
|
||||
tool: CollabAgentTool::CloseAgent,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: begin_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![begin_event.receiver_thread_id.to_string()],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: HashMap::new(),
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabCloseEnd(end_event) => {
|
||||
let status = match &end_event.status {
|
||||
codex_protocol::protocol::AgentStatus::Errored(_)
|
||||
| codex_protocol::protocol::AgentStatus::NotFound => {
|
||||
CollabAgentToolCallStatus::Failed
|
||||
}
|
||||
_ => CollabAgentToolCallStatus::Completed,
|
||||
};
|
||||
let receiver_id = end_event.receiver_thread_id.to_string();
|
||||
let agents_states = [(
|
||||
receiver_id.clone(),
|
||||
CollabAgentState::from(end_event.status),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: end_event.call_id,
|
||||
tool: CollabAgentTool::CloseAgent,
|
||||
status,
|
||||
sender_thread_id: end_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![receiver_id],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabResumeBegin(begin_event) => {
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: begin_event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: begin_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![begin_event.receiver_thread_id.to_string()],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: HashMap::new(),
|
||||
};
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::CollabResumeEnd(end_event) => {
|
||||
let status = match &end_event.status {
|
||||
codex_protocol::protocol::AgentStatus::Errored(_)
|
||||
| codex_protocol::protocol::AgentStatus::NotFound => {
|
||||
CollabAgentToolCallStatus::Failed
|
||||
}
|
||||
_ => CollabAgentToolCallStatus::Completed,
|
||||
};
|
||||
let receiver_id = end_event.receiver_thread_id.to_string();
|
||||
let agents_states = [(
|
||||
receiver_id.clone(),
|
||||
CollabAgentState::from(end_event.status),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let item = ThreadItem::CollabAgentToolCall {
|
||||
id: end_event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
status,
|
||||
sender_thread_id: end_event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![receiver_id],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states,
|
||||
};
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item,
|
||||
})
|
||||
}
|
||||
EventMsg::AgentMessageContentDelta(event) => {
|
||||
let codex_protocol::protocol::AgentMessageContentDeltaEvent { item_id, delta, .. } =
|
||||
event;
|
||||
ServerNotification::AgentMessageDelta(AgentMessageDeltaNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id,
|
||||
delta,
|
||||
})
|
||||
}
|
||||
EventMsg::PlanDelta(event) => ServerNotification::PlanDelta(PlanDeltaNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: event.item_id,
|
||||
delta: event.delta,
|
||||
}),
|
||||
EventMsg::ReasoningContentDelta(event) => {
|
||||
ServerNotification::ReasoningSummaryTextDelta(ReasoningSummaryTextDeltaNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: event.item_id,
|
||||
delta: event.delta,
|
||||
summary_index: event.summary_index,
|
||||
})
|
||||
}
|
||||
EventMsg::ReasoningRawContentDelta(event) => {
|
||||
ServerNotification::ReasoningTextDelta(ReasoningTextDeltaNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: event.item_id,
|
||||
delta: event.delta,
|
||||
content_index: event.content_index,
|
||||
})
|
||||
}
|
||||
EventMsg::AgentReasoningSectionBreak(event) => {
|
||||
ServerNotification::ReasoningSummaryPartAdded(ReasoningSummaryPartAddedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: event.item_id,
|
||||
summary_index: event.summary_index,
|
||||
})
|
||||
}
|
||||
EventMsg::ItemStarted(item_started_event) => {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: item_started_event.item.into(),
|
||||
})
|
||||
}
|
||||
EventMsg::ItemCompleted(item_completed_event) => {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: item_completed_event.item.into(),
|
||||
})
|
||||
}
|
||||
EventMsg::PatchApplyBegin(patch_begin_event) => {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: build_file_change_begin_item(&patch_begin_event),
|
||||
})
|
||||
}
|
||||
EventMsg::PatchApplyUpdated(event) => {
|
||||
ServerNotification::FileChangePatchUpdated(FileChangePatchUpdatedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: event.call_id,
|
||||
changes: convert_patch_changes(&event.changes),
|
||||
})
|
||||
}
|
||||
EventMsg::ExecCommandBegin(exec_command_begin_event) => {
|
||||
ServerNotification::ItemStarted(ItemStartedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: build_command_execution_begin_item(&exec_command_begin_event),
|
||||
})
|
||||
}
|
||||
EventMsg::ExecCommandOutputDelta(exec_command_output_delta_event) => {
|
||||
let item_id = exec_command_output_delta_event.call_id;
|
||||
let delta = String::from_utf8_lossy(&exec_command_output_delta_event.chunk).to_string();
|
||||
ServerNotification::CommandExecutionOutputDelta(
|
||||
CommandExecutionOutputDeltaNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id,
|
||||
delta,
|
||||
},
|
||||
)
|
||||
}
|
||||
EventMsg::TerminalInteraction(terminal_event) => {
|
||||
ServerNotification::TerminalInteraction(TerminalInteractionNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item_id: terminal_event.call_id,
|
||||
process_id: terminal_event.process_id,
|
||||
stdin: terminal_event.stdin,
|
||||
})
|
||||
}
|
||||
EventMsg::ExecCommandEnd(exec_command_end_event) => {
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: build_command_execution_end_item(&exec_command_end_event),
|
||||
})
|
||||
}
|
||||
_ => unreachable!("unsupported item event"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::protocol::CollabResumeBeginEvent;
|
||||
use codex_protocol::protocol::CollabResumeEndEvent;
|
||||
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
|
||||
use codex_protocol::protocol::ExecOutputStream;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_protocol::protocol::McpToolCallBeginEvent;
|
||||
use codex_protocol::protocol::McpToolCallEndEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::Content;
|
||||
use std::time::Duration;
|
||||
|
||||
fn assert_item_started_server_notification(
|
||||
notification: ServerNotification,
|
||||
expected: ItemStartedNotification,
|
||||
) {
|
||||
match notification {
|
||||
ServerNotification::ItemStarted(payload) => assert_eq!(payload, expected),
|
||||
other => panic!("expected item started notification, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_item_completed_server_notification(
|
||||
notification: ServerNotification,
|
||||
expected: ItemCompletedNotification,
|
||||
) {
|
||||
match notification {
|
||||
ServerNotification::ItemCompleted(payload) => assert_eq!(payload, expected),
|
||||
other => panic!("expected item completed notification, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_command_execution_output_delta_server_notification(
|
||||
notification: ServerNotification,
|
||||
expected: CommandExecutionOutputDeltaNotification,
|
||||
) {
|
||||
match notification {
|
||||
ServerNotification::CommandExecutionOutputDelta(payload) => {
|
||||
assert_eq!(payload, expected)
|
||||
}
|
||||
other => panic!("expected command execution output delta, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_resume_begin_maps_to_item_started_resume_agent() {
|
||||
let event = CollabResumeBeginEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
sender_thread_id: ThreadId::new(),
|
||||
receiver_thread_id: ThreadId::new(),
|
||||
receiver_agent_nickname: None,
|
||||
receiver_agent_role: None,
|
||||
};
|
||||
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::CollabResumeBegin(event.clone()),
|
||||
"thread-1",
|
||||
"turn-1",
|
||||
);
|
||||
assert_item_started_server_notification(
|
||||
notification,
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item: ThreadItem::CollabAgentToolCall {
|
||||
id: event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
status: CollabAgentToolCallStatus::InProgress,
|
||||
sender_thread_id: event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![event.receiver_thread_id.to_string()],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: HashMap::new(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_resume_end_maps_to_item_completed_resume_agent() {
|
||||
let event = CollabResumeEndEvent {
|
||||
call_id: "call-2".to_string(),
|
||||
sender_thread_id: ThreadId::new(),
|
||||
receiver_thread_id: ThreadId::new(),
|
||||
receiver_agent_nickname: None,
|
||||
receiver_agent_role: None,
|
||||
status: codex_protocol::protocol::AgentStatus::NotFound,
|
||||
};
|
||||
|
||||
let receiver_id = event.receiver_thread_id.to_string();
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::CollabResumeEnd(event.clone()),
|
||||
"thread-2",
|
||||
"turn-2",
|
||||
);
|
||||
assert_item_completed_server_notification(
|
||||
notification,
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
item: ThreadItem::CollabAgentToolCall {
|
||||
id: event.call_id,
|
||||
tool: CollabAgentTool::ResumeAgent,
|
||||
status: CollabAgentToolCallStatus::Failed,
|
||||
sender_thread_id: event.sender_thread_id.to_string(),
|
||||
receiver_thread_ids: vec![receiver_id.clone()],
|
||||
prompt: None,
|
||||
model: None,
|
||||
reasoning_effort: None,
|
||||
agents_states: [(
|
||||
receiver_id,
|
||||
CollabAgentState::from(codex_protocol::protocol::AgentStatus::NotFound),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_call_begin_maps_to_item_started_notification_with_args() {
|
||||
let begin_event = McpToolCallBeginEvent {
|
||||
call_id: "call_123".to_string(),
|
||||
invocation: McpInvocation {
|
||||
server: "codex".to_string(),
|
||||
tool: "list_mcp_resources".to_string(),
|
||||
arguments: Some(serde_json::json!({"server": ""})),
|
||||
},
|
||||
mcp_app_resource_uri: Some("ui://widget/list-resources.html".to_string()),
|
||||
};
|
||||
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::McpToolCallBegin(begin_event.clone()),
|
||||
"thread-1",
|
||||
"turn_1",
|
||||
);
|
||||
assert_item_started_server_notification(
|
||||
notification,
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn_1".to_string(),
|
||||
item: ThreadItem::McpToolCall {
|
||||
id: begin_event.call_id,
|
||||
server: begin_event.invocation.server,
|
||||
tool: begin_event.invocation.tool,
|
||||
status: McpToolCallStatus::InProgress,
|
||||
arguments: serde_json::json!({"server": ""}),
|
||||
mcp_app_resource_uri: Some("ui://widget/list-resources.html".to_string()),
|
||||
result: None,
|
||||
error: None,
|
||||
duration_ms: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_call_begin_maps_to_item_started_notification_without_args() {
|
||||
let begin_event = McpToolCallBeginEvent {
|
||||
call_id: "call_456".to_string(),
|
||||
invocation: McpInvocation {
|
||||
server: "codex".to_string(),
|
||||
tool: "list_mcp_resources".to_string(),
|
||||
arguments: None,
|
||||
},
|
||||
mcp_app_resource_uri: None,
|
||||
};
|
||||
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::McpToolCallBegin(begin_event.clone()),
|
||||
"thread-2",
|
||||
"turn_2",
|
||||
);
|
||||
assert_item_started_server_notification(
|
||||
notification,
|
||||
ItemStartedNotification {
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn_2".to_string(),
|
||||
item: ThreadItem::McpToolCall {
|
||||
id: begin_event.call_id,
|
||||
server: begin_event.invocation.server,
|
||||
tool: begin_event.invocation.tool,
|
||||
status: McpToolCallStatus::InProgress,
|
||||
arguments: JsonValue::Null,
|
||||
mcp_app_resource_uri: None,
|
||||
result: None,
|
||||
error: None,
|
||||
duration_ms: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_call_end_maps_to_item_completed_notification_on_success() {
|
||||
let content = vec![
|
||||
serde_json::to_value(Content::text("{\"resources\":[]}"))
|
||||
.expect("content should serialize"),
|
||||
];
|
||||
let result = CallToolResult {
|
||||
content: content.clone(),
|
||||
is_error: Some(false),
|
||||
structured_content: None,
|
||||
meta: Some(serde_json::json!({
|
||||
"ui/resourceUri": "ui://widget/list-resources.html"
|
||||
})),
|
||||
};
|
||||
|
||||
let end_event = McpToolCallEndEvent {
|
||||
call_id: "call_789".to_string(),
|
||||
invocation: McpInvocation {
|
||||
server: "codex".to_string(),
|
||||
tool: "list_mcp_resources".to_string(),
|
||||
arguments: Some(serde_json::json!({"server": ""})),
|
||||
},
|
||||
mcp_app_resource_uri: Some("ui://widget/list-resources.html".to_string()),
|
||||
duration: Duration::from_nanos(92708),
|
||||
result: Ok(result),
|
||||
};
|
||||
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::McpToolCallEnd(end_event.clone()),
|
||||
"thread-3",
|
||||
"turn_3",
|
||||
);
|
||||
assert_item_completed_server_notification(
|
||||
notification,
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-3".to_string(),
|
||||
turn_id: "turn_3".to_string(),
|
||||
item: ThreadItem::McpToolCall {
|
||||
id: end_event.call_id,
|
||||
server: end_event.invocation.server,
|
||||
tool: end_event.invocation.tool,
|
||||
status: McpToolCallStatus::Completed,
|
||||
arguments: serde_json::json!({"server": ""}),
|
||||
mcp_app_resource_uri: Some("ui://widget/list-resources.html".to_string()),
|
||||
result: Some(Box::new(McpToolCallResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
meta: Some(serde_json::json!({
|
||||
"ui/resourceUri": "ui://widget/list-resources.html"
|
||||
})),
|
||||
})),
|
||||
error: None,
|
||||
duration_ms: Some(0),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_call_end_maps_to_item_completed_notification_on_error() {
|
||||
let end_event = McpToolCallEndEvent {
|
||||
call_id: "call_err".to_string(),
|
||||
invocation: McpInvocation {
|
||||
server: "codex".to_string(),
|
||||
tool: "list_mcp_resources".to_string(),
|
||||
arguments: None,
|
||||
},
|
||||
mcp_app_resource_uri: None,
|
||||
duration: Duration::from_millis(1),
|
||||
result: Err("boom".to_string()),
|
||||
};
|
||||
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::McpToolCallEnd(end_event.clone()),
|
||||
"thread-4",
|
||||
"turn_4",
|
||||
);
|
||||
assert_item_completed_server_notification(
|
||||
notification,
|
||||
ItemCompletedNotification {
|
||||
thread_id: "thread-4".to_string(),
|
||||
turn_id: "turn_4".to_string(),
|
||||
item: ThreadItem::McpToolCall {
|
||||
id: end_event.call_id,
|
||||
server: end_event.invocation.server,
|
||||
tool: end_event.invocation.tool,
|
||||
status: McpToolCallStatus::Failed,
|
||||
arguments: JsonValue::Null,
|
||||
mcp_app_resource_uri: None,
|
||||
result: None,
|
||||
error: Some(McpToolCallError {
|
||||
message: "boom".to_string(),
|
||||
}),
|
||||
duration_ms: Some(1),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_command_output_delta_maps_to_command_execution_output_delta() {
|
||||
let notification = item_event_to_server_notification(
|
||||
EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent {
|
||||
call_id: "call-1".to_string(),
|
||||
stream: ExecOutputStream::Stdout,
|
||||
chunk: b"hello".to_vec(),
|
||||
}),
|
||||
"thread-1",
|
||||
"turn-1",
|
||||
);
|
||||
|
||||
assert_command_execution_output_delta_server_notification(
|
||||
notification,
|
||||
CommandExecutionOutputDeltaNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "call-1".to_string(),
|
||||
delta: "hello".to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Exposes protocol pieces used by `lib.rs` via `pub use protocol::common::*;`.
|
||||
|
||||
pub mod common;
|
||||
pub mod event_mapping;
|
||||
pub mod item_builders;
|
||||
mod mappers;
|
||||
mod serde_helpers;
|
||||
|
||||
@@ -38,6 +38,8 @@ use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
|
||||
use codex_protocol::mcp::Tool as McpTool;
|
||||
use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;
|
||||
use codex_protocol::memory_citation::MemoryCitationEntry as CoreMemoryCitationEntry;
|
||||
use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification as CoreActivePermissionProfileModification;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions as CoreManagedFileSystemPermissions;
|
||||
@@ -469,6 +471,8 @@ v2_enum_from_core!(
|
||||
Project,
|
||||
Mdm,
|
||||
SessionFlags,
|
||||
Plugin,
|
||||
CloudRequirements,
|
||||
LegacyManagedConfigFile,
|
||||
LegacyManagedConfigMdm,
|
||||
Unknown,
|
||||
@@ -1091,6 +1095,18 @@ pub enum ExternalAgentConfigMigrationItemType {
|
||||
#[serde(rename = "MCP_SERVER_CONFIG")]
|
||||
#[ts(rename = "MCP_SERVER_CONFIG")]
|
||||
McpServerConfig,
|
||||
#[serde(rename = "SUBAGENTS")]
|
||||
#[ts(rename = "SUBAGENTS")]
|
||||
Subagents,
|
||||
#[serde(rename = "HOOKS")]
|
||||
#[ts(rename = "HOOKS")]
|
||||
Hooks,
|
||||
#[serde(rename = "COMMANDS")]
|
||||
#[ts(rename = "COMMANDS")]
|
||||
Commands,
|
||||
#[serde(rename = "SESSIONS")]
|
||||
#[ts(rename = "SESSIONS")]
|
||||
Sessions,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
@@ -1108,8 +1124,56 @@ pub struct PluginsMigration {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SessionMigration {
|
||||
pub path: PathBuf,
|
||||
pub cwd: PathBuf,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SubagentMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MigrationDetails {
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<PluginsMigration>,
|
||||
#[serde(default)]
|
||||
pub sessions: Vec<SessionMigration>,
|
||||
#[serde(default)]
|
||||
pub mcp_servers: Vec<McpServerMigration>,
|
||||
#[serde(default)]
|
||||
pub hooks: Vec<HookMigration>,
|
||||
#[serde(default)]
|
||||
pub subagents: Vec<SubagentMigration>,
|
||||
#[serde(default)]
|
||||
pub commands: Vec<CommandMigration>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
@@ -1642,6 +1706,109 @@ impl From<PermissionProfile> for CorePermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ActivePermissionProfile {
|
||||
/// Identifier from `default_permissions` or the implicit built-in default,
|
||||
/// such as `:workspace` or a user-defined `[permissions.<id>]` profile.
|
||||
pub id: String,
|
||||
/// Parent profile identifier once permissions profiles support
|
||||
/// inheritance. This is currently always `null`.
|
||||
#[serde(default)]
|
||||
pub extends: Option<String>,
|
||||
/// Bounded user-requested modifications applied on top of the named
|
||||
/// profile, if any.
|
||||
#[serde(default)]
|
||||
pub modifications: Vec<ActivePermissionProfileModification>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ActivePermissionProfileModification {
|
||||
/// Additional concrete directory that should be writable.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
AdditionalWritableRoot { path: AbsolutePathBuf },
|
||||
}
|
||||
|
||||
impl From<CoreActivePermissionProfileModification> for ActivePermissionProfileModification {
|
||||
fn from(value: CoreActivePermissionProfileModification) -> Self {
|
||||
match value {
|
||||
CoreActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
Self::AdditionalWritableRoot { path }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivePermissionProfileModification> for CoreActivePermissionProfileModification {
|
||||
fn from(value: ActivePermissionProfileModification) -> Self {
|
||||
match value {
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
Self::AdditionalWritableRoot { path }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreActivePermissionProfile> for ActivePermissionProfile {
|
||||
fn from(value: CoreActivePermissionProfile) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
extends: value.extends,
|
||||
modifications: value
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(ActivePermissionProfileModification::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivePermissionProfile> for CoreActivePermissionProfile {
|
||||
fn from(value: ActivePermissionProfile) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
extends: value.extends,
|
||||
modifications: value
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(CoreActivePermissionProfileModification::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PermissionProfileSelectionParams {
|
||||
/// Select a named built-in or user-defined profile and optionally apply
|
||||
/// bounded modifications that Codex knows how to validate.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Profile {
|
||||
id: String,
|
||||
#[ts(optional = nullable)]
|
||||
modifications: Option<Vec<PermissionProfileModificationParams>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PermissionProfileModificationParams {
|
||||
/// Additional concrete directory that should be writable.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
AdditionalWritableRoot { path: AbsolutePathBuf },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -2113,9 +2280,12 @@ pub enum LoginAccountParams {
|
||||
#[ts(rename = "apiKey")]
|
||||
api_key: String,
|
||||
},
|
||||
#[serde(rename = "chatgpt")]
|
||||
#[ts(rename = "chatgpt")]
|
||||
Chatgpt,
|
||||
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
||||
Chatgpt {
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
codex_streamlined_login: bool,
|
||||
},
|
||||
#[serde(rename = "chatgptDeviceCode")]
|
||||
#[ts(rename = "chatgptDeviceCode")]
|
||||
ChatgptDeviceCode,
|
||||
@@ -2294,6 +2464,20 @@ pub struct GetAccountResponse {
|
||||
pub requires_openai_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelProviderCapabilitiesReadParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelProviderCapabilitiesReadResponse {
|
||||
pub namespace_tools: bool,
|
||||
pub image_generation: bool,
|
||||
pub web_search: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -2862,6 +3046,25 @@ pub struct DeviceKeyPublicResponse {
|
||||
pub protection_class: DeviceKeyProtectionClass,
|
||||
}
|
||||
|
||||
/// Current remote-control connection status and environment id exposed to clients.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RemoteControlStatusChangedNotification {
|
||||
pub status: RemoteControlConnectionStatus,
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase", export_to = "v2/")]
|
||||
pub enum RemoteControlConnectionStatus {
|
||||
Disabled,
|
||||
Connecting,
|
||||
Connected,
|
||||
Errored,
|
||||
}
|
||||
|
||||
/// Audience for a remote-control client connection device-key proof.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -3365,11 +3568,12 @@ pub struct ThreadStartParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for this thread. Cannot be combined with
|
||||
/// `sandbox`.
|
||||
#[experimental("thread/start.permissionProfile")]
|
||||
/// Named profile selection for this thread. Cannot be combined with
|
||||
/// `sandbox`. Use bounded `modifications` for supported turn/thread
|
||||
/// adjustments instead of replacing the full permissions profile.
|
||||
#[experimental("thread/start.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, JsonValue>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3446,14 +3650,20 @@ pub struct ThreadStartResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// Full active permissions for this thread. `activePermissionProfile`
|
||||
/// carries display/provenance metadata for this runtime profile.
|
||||
#[experimental("thread/start.permissionProfile")]
|
||||
#[serde(default)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
/// Named or implicit built-in profile that produced the active
|
||||
/// permissions, when known.
|
||||
#[experimental("thread/start.activePermissionProfile")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -3511,11 +3721,12 @@ pub struct ThreadResumeParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for the resumed thread. Cannot be combined
|
||||
/// with `sandbox`.
|
||||
#[experimental("thread/resume.permissionProfile")]
|
||||
/// Named profile selection for the resumed thread. Cannot be combined
|
||||
/// with `sandbox`. Use bounded `modifications` for supported thread
|
||||
/// adjustments instead of replacing the full permissions profile.
|
||||
#[experimental("thread/resume.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3552,14 +3763,20 @@ pub struct ThreadResumeResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// Full active permissions for this thread. `activePermissionProfile`
|
||||
/// carries display/provenance metadata for this runtime profile.
|
||||
#[experimental("thread/resume.permissionProfile")]
|
||||
#[serde(default)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
/// Named or implicit built-in profile that produced the active
|
||||
/// permissions, when known.
|
||||
#[experimental("thread/resume.activePermissionProfile")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -3608,11 +3825,12 @@ pub struct ThreadForkParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for the forked thread. Cannot be combined
|
||||
/// with `sandbox`.
|
||||
#[experimental("thread/fork.permissionProfile")]
|
||||
/// Named profile selection for the forked thread. Cannot be combined with
|
||||
/// `sandbox`. Use bounded `modifications` for supported thread
|
||||
/// adjustments instead of replacing the full permissions profile.
|
||||
#[experimental("thread/fork.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3649,14 +3867,20 @@ pub struct ThreadForkResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// Full active permissions for this thread. `activePermissionProfile`
|
||||
/// carries display/provenance metadata for this runtime profile.
|
||||
#[experimental("thread/fork.permissionProfile")]
|
||||
#[serde(default)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
/// Named or implicit built-in profile that produced the active
|
||||
/// permissions, when known.
|
||||
#[experimental("thread/fork.activePermissionProfile")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -4260,6 +4484,22 @@ pub struct SkillsListResponse {
|
||||
pub data: Vec<SkillsListEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HooksListParams {
|
||||
/// When empty, defaults to the current session working directory.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub cwds: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HooksListResponse {
|
||||
pub data: Vec<HooksListEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -4367,6 +4607,47 @@ pub struct PluginReadResponse {
|
||||
pub plugin: PluginDetail,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareSaveParams {
|
||||
pub plugin_path: AbsolutePathBuf,
|
||||
#[ts(optional = nullable)]
|
||||
pub remote_plugin_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareSaveResponse {
|
||||
pub remote_plugin_id: String,
|
||||
pub share_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareListParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareListResponse {
|
||||
pub data: Vec<PluginSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareDeleteParams {
|
||||
pub remote_plugin_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareDeleteResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
@@ -4463,6 +4744,43 @@ pub struct SkillsListEntry {
|
||||
pub errors: Vec<SkillErrorInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HooksListEntry {
|
||||
pub cwd: PathBuf,
|
||||
pub hooks: Vec<HookMetadata>,
|
||||
pub warnings: Vec<String>,
|
||||
pub errors: Vec<HookErrorInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookMetadata {
|
||||
pub key: String,
|
||||
pub event_name: HookEventName,
|
||||
pub handler_type: HookHandlerType,
|
||||
pub matcher: Option<String>,
|
||||
pub command: Option<String>,
|
||||
pub timeout_sec: u64,
|
||||
pub status_message: Option<String>,
|
||||
pub source_path: AbsolutePathBuf,
|
||||
pub source: HookSource,
|
||||
pub plugin_id: Option<String>,
|
||||
pub display_order: i64,
|
||||
pub enabled: bool,
|
||||
pub is_managed: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookErrorInfo {
|
||||
pub path: PathBuf,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -4975,7 +5293,7 @@ pub struct ThreadRealtimeStartParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub prompt: Option<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub session_id: Option<String>,
|
||||
pub realtime_session_id: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub transport: Option<ThreadRealtimeStartTransport>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -5065,7 +5383,7 @@ pub struct ThreadRealtimeListVoicesResponse {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStartedNotification {
|
||||
pub thread_id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub realtime_session_id: Option<String>,
|
||||
pub version: RealtimeConversationVersion,
|
||||
}
|
||||
|
||||
@@ -5191,11 +5509,13 @@ pub struct TurnStartParams {
|
||||
/// Override the sandbox policy for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
/// Override the full permissions profile for this turn and subsequent
|
||||
/// turns. Cannot be combined with `sandboxPolicy`.
|
||||
#[experimental("turn/start.permissionProfile")]
|
||||
/// Select a named permissions profile for this turn and subsequent turns.
|
||||
/// Cannot be combined with `sandboxPolicy`. Use bounded `modifications`
|
||||
/// for supported turn adjustments instead of replacing the full
|
||||
/// permissions profile.
|
||||
#[experimental("turn/start.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
/// Override the model for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub model: Option<String>,
|
||||
@@ -6672,6 +6992,9 @@ pub struct CommandExecOutputDeltaNotification {
|
||||
pub cap_reached: bool,
|
||||
}
|
||||
|
||||
/// Deprecated legacy notification for `apply_patch` textual output.
|
||||
///
|
||||
/// The server no longer emits this notification.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -7841,11 +8164,50 @@ mod tests {
|
||||
marketplace_name: "team-marketplace".to_string(),
|
||||
plugin_names: vec!["asana".to_string()],
|
||||
}],
|
||||
..Default::default()
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_import_params_accept_legacy_plugin_details() {
|
||||
let params: ExternalAgentConfigImportParams = serde_json::from_value(json!({
|
||||
"migrationItems": [{
|
||||
"itemType": "PLUGINS",
|
||||
"description": "Install supported plugins from Claude settings",
|
||||
"cwd": absolute_path_string("repo"),
|
||||
"details": {
|
||||
"plugins": [
|
||||
{
|
||||
"marketplaceName": "team-marketplace",
|
||||
"pluginNames": ["asana"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}))
|
||||
.expect("legacy plugin import params should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
params,
|
||||
ExternalAgentConfigImportParams {
|
||||
migration_items: vec![ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Plugins,
|
||||
description: "Install supported plugins from Claude settings".to_string(),
|
||||
cwd: Some(PathBuf::from(absolute_path_string("repo"))),
|
||||
details: Some(MigrationDetails {
|
||||
plugins: vec![PluginsMigration {
|
||||
marketplace_name: "team-marketplace".to_string(),
|
||||
plugin_names: vec!["asana".to_string()],
|
||||
}],
|
||||
..Default::default()
|
||||
}),
|
||||
}],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_request_approval_rejects_relative_additional_permission_paths() {
|
||||
let err = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
@@ -10285,6 +10647,101 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
|
||||
let plugin_path = if cfg!(windows) {
|
||||
r"C:\plugins\gmail"
|
||||
} else {
|
||||
"/plugins/gmail"
|
||||
};
|
||||
let plugin_path = AbsolutePathBuf::try_from(PathBuf::from(plugin_path)).unwrap();
|
||||
let plugin_path_json = plugin_path.as_path().display().to_string();
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareSaveParams {
|
||||
plugin_path: plugin_path.clone(),
|
||||
remote_plugin_id: None,
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
"pluginPath": plugin_path_json,
|
||||
"remotePluginId": null,
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareSaveParams {
|
||||
plugin_path,
|
||||
remote_plugin_id: Some(
|
||||
"plugins~Plugin_00000000000000000000000000000000".to_string(),
|
||||
),
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
"pluginPath": plugin_path_json,
|
||||
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareSaveResponse {
|
||||
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
|
||||
share_url: String::new(),
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
|
||||
"shareUrl": "",
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::from_value::<PluginShareListParams>(json!({})).unwrap(),
|
||||
PluginShareListParams {},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareDeleteParams {
|
||||
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_share_list_response_serializes_plugin_summaries() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareListResponse {
|
||||
data: vec![PluginSummary {
|
||||
id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
|
||||
name: "gmail".to_string(),
|
||||
source: PluginSource::Remote,
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
interface: None,
|
||||
}],
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
"data": [{
|
||||
"id": "plugins~Plugin_00000000000000000000000000000000",
|
||||
"name": "gmail",
|
||||
"source": { "type": "remote" },
|
||||
"installed": false,
|
||||
"enabled": false,
|
||||
"installPolicy": "AVAILABLE",
|
||||
"authPolicy": "ON_USE",
|
||||
"interface": null,
|
||||
}],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_uninstall_params_serialization_omits_force_remote_sync() {
|
||||
assert_eq!(
|
||||
@@ -10604,6 +11061,9 @@ mod tests {
|
||||
assert_eq!(start.permission_profile, None);
|
||||
assert_eq!(resume.permission_profile, None);
|
||||
assert_eq!(fork.permission_profile, None);
|
||||
assert_eq!(start.active_permission_profile, None);
|
||||
assert_eq!(resume.active_permission_profile, None);
|
||||
assert_eq!(fork.active_permission_profile, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10631,7 +11091,7 @@ mod tests {
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
effort: None,
|
||||
|
||||
@@ -1607,7 +1607,9 @@ impl CodexClient {
|
||||
let request_id = self.request_id();
|
||||
let request = ClientRequest::LoginAccount {
|
||||
request_id: request_id.clone(),
|
||||
params: codex_app_server_protocol::LoginAccountParams::Chatgpt,
|
||||
params: codex_app_server_protocol::LoginAccountParams::Chatgpt {
|
||||
codex_streamlined_login: false,
|
||||
},
|
||||
};
|
||||
|
||||
self.send_request(request, request_id, "account/login/start")
|
||||
|
||||
@@ -38,9 +38,13 @@ codex-core = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-device-key = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-external-agent-migration = { workspace = true }
|
||||
codex-external-agent-sessions = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-hooks = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-utils-cli = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
|
||||
@@ -88,7 +88,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
|
||||
- Initialize once per connection: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request on that connection before this handshake gets rejected.
|
||||
- Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and you’ll also get a `thread/started` notification. If you’re continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history. Like `thread/start`, `thread/fork` also accepts `ephemeral: true` for an in-memory temporary thread.
|
||||
The returned `thread.ephemeral` flag tells you whether the session is intentionally in-memory only; when it is `true`, `thread.path` is `null`.
|
||||
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy or `permissionProfile`, approval policy, approvals reviewer, etc. This immediately returns the new turn object. The app-server emits `turn/started` when that turn actually begins running.
|
||||
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy or experimental `permissions` profile selection, approval policy, approvals reviewer, etc. This immediately returns the new turn object. The app-server emits `turn/started` when that turn actually begins running.
|
||||
- Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. You’ll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
|
||||
- Finish the turn: When the model is done (or the turn is interrupted via making the `turn/interrupt` call), the server sends `turn/completed` with the final turn state and token usage.
|
||||
|
||||
@@ -142,9 +142,10 @@ Example with notification opt-out:
|
||||
|
||||
## API Overview
|
||||
|
||||
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. For permissions, prefer `permissionProfile`; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissionProfile`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`.
|
||||
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. For permissions, prefer experimental `permissions` profile selection; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissions`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`.
|
||||
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. Accepts the same permission override rules as `thread/start`.
|
||||
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. Pass `excludeTurns: true` when the client plans to page fork history via `thread/turns/list` instead of receiving the full turn array immediately. Accepts the same permission override rules as `thread/start`.
|
||||
- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. Experimental clients can read response `permissionProfile` for the exact active runtime permissions and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known.
|
||||
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
|
||||
- `thread/loaded/list` — list the thread ids currently loaded in memory.
|
||||
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
|
||||
@@ -166,7 +167,7 @@ Example with notification opt-out:
|
||||
- `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream.
|
||||
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
|
||||
- `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
|
||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. Prefer `permissionProfile` for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissionProfile`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. Prefer experimental `permissions` profile selection for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
||||
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
|
||||
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. Review and manual compaction turns reject `turn/steer`.
|
||||
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
|
||||
@@ -191,10 +192,12 @@ Example with notification opt-out:
|
||||
- `fs/unwatch` — stop sending notifications for a prior `fs/watch`; returns `{}`.
|
||||
- `fs/changed` — notification emitted when watched paths change, including the `watchId` and `changedPaths`.
|
||||
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, `additionalSpeedTiers`, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
|
||||
- `modelProvider/capabilities/read` — read provider-level capabilities for the currently configured model provider.
|
||||
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
|
||||
- `experimentalFeature/enablement/set` — patch the in-memory process-wide runtime feature enablement for the currently supported feature keys (`apps`, `plugins`). For each feature, precedence is: cloud requirements > --enable <feature_name> > config.toml > experimentalFeature/enablement/set (new) > code default.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
|
||||
- `experimentalFeature/enablement/set` — patch the in-memory process-wide runtime feature enablement for the currently supported feature keys (`apps`, `memories`, `plugins`, `remote_control`, `tool_search`, `tool_suggest`, `tool_call_mcp_elicitation`). For each feature, precedence is: cloud requirements > --enable <feature_name> > config.toml > experimentalFeature/enablement/set (new) > code default.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). Built-in presets do not select a model; the Plan preset selects medium reasoning effort. This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
|
||||
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
|
||||
- `hooks/list` — list discovered hooks for one or more `cwd` values.
|
||||
- `marketplace/add` — add a remote plugin marketplace from an HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand, then persist it into the user marketplace config. Returns the installed root path plus whether the marketplace was already present.
|
||||
- `marketplace/remove` — remove a configured marketplace by name from the user marketplace config, and delete its installed marketplace root when one exists.
|
||||
- `marketplace/upgrade` — upgrade all configured Git plugin marketplaces, or one named marketplace when `marketplaceName` is provided. Returns selected marketplace names, upgraded roots, and per-marketplace errors.
|
||||
@@ -205,6 +208,7 @@ Example with notification opt-out:
|
||||
- `device/key/create` — create or load a controller-local device signing key for an account/client binding. This local-key API is available only over local transports such as stdio and in-process; remote transports reject it. Hardware-backed providers are the target protection class; an OS-protected non-extractable fallback is allowed only with `protectionPolicy: "allow_os_protected_nonextractable"` and returns the reported `protectionClass`.
|
||||
- `device/key/public` — return a device key's SPKI DER public key as base64 plus its `algorithm` and `protectionClass`.
|
||||
- `device/key/sign` — sign one of the accepted structured payload variants with a controller-local device key. The only accepted payload today is `remoteControlClientConnection`, which binds a server-issued `/client` websocket challenge to the enrolled controller device without signing the bearer token itself; this is intentionally not an arbitrary-byte signing API.
|
||||
- `remoteControl/status/changed` — notification emitted when the remote-control status or client-visible environment id changes. `status` is one of `disabled`, `connecting`, `connected`, or `errored`; `environmentId` is a string when the app-server has a current enrollment and `null` when that enrollment is cleared, invalidated, or remote control is disabled. Newly initialized app-server clients always receive the current status snapshot.
|
||||
- `skills/config/write` — write user-level skill config by name or absolute path.
|
||||
- `plugin/install` — install a plugin from a discovered marketplace entry, rejecting marketplace entries marked unavailable for install, install MCPs if any, and return the effective plugin auth policy plus any apps that still need auth (**under development; do not call from production clients yet**).
|
||||
- `plugin/uninstall` — uninstall a local plugin by `pluginId` in `<plugin>@<marketplace>` form by removing its cached files and clearing its user-level config entry, or uninstall a remote ChatGPT plugin by backend `pluginId` by forwarding the uninstall to the ChatGPT plugin backend and removing any downloaded remote-plugin cache (**under development; do not call from production clients yet**).
|
||||
@@ -217,8 +221,8 @@ Example with notification opt-out:
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
|
||||
- `config/read` — fetch the effective config on disk after resolving config layering.
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome` and optional `cwds`; each detected item includes `cwd` (`null` for home), and plugin migration items may additionally include structured `details` grouping plugin ids under each detected marketplace name.
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin `details` returned by detect. When a request includes plugin imports, the server emits `externalAgentConfig/import/completed` after the full import finishes (immediately after the response when everything completed synchronously, or after background remote imports finish).
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome` and optional `cwds`; each detected item includes `cwd` (`null` for home), and plugin/session migration items may additionally include structured `details` grouping plugin ids or session metadata.
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin/session `details` returned by detect. When a request includes migration items, the server emits `externalAgentConfig/import/completed` once after the full import finishes (immediately after the response when everything completed synchronously, or after background imports finish).
|
||||
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
|
||||
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
|
||||
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
|
||||
@@ -235,8 +239,9 @@ Start a fresh thread when you need a new Codex conversation.
|
||||
"cwd": "/Users/me/project",
|
||||
"approvalPolicy": "never",
|
||||
"sandbox": "workspaceWrite",
|
||||
// Prefer "permissionProfile" for full permission overrides. Do not send
|
||||
// both "sandbox" and "permissionProfile".
|
||||
// Prefer experimental profile selection:
|
||||
// "permissions": { "type": "profile", "id": ":workspace" }
|
||||
// Do not send both "sandbox" and "permissions".
|
||||
"personality": "friendly",
|
||||
"serviceName": "my_app_server_client", // optional metrics tag (`service_name`)
|
||||
"sessionStartSource": "startup", // optional: "startup" (default) or "clear"
|
||||
@@ -633,8 +638,9 @@ You can optionally specify config overrides on the new turn. If specified, these
|
||||
"writableRoots": ["/Users/me/project"],
|
||||
"networkAccess": true
|
||||
},
|
||||
// Prefer "permissionProfile" for full permission overrides. Do not send
|
||||
// both "sandboxPolicy" and "permissionProfile".
|
||||
// Prefer experimental profile selection:
|
||||
// "permissions": { "type": "profile", "id": ":workspace" }
|
||||
// Do not send both "sandboxPolicy" and "permissions".
|
||||
"model": "gpt-5.1-codex",
|
||||
"effort": "medium",
|
||||
"summary": "concise",
|
||||
@@ -755,14 +761,14 @@ const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
```
|
||||
|
||||
Then send `offer.sdp` to app-server. Core uses `experimental_realtime_ws_backend_prompt` for the backend instructions and the thread conversation id for the realtime session id. The start response is `{}`; the remote answer SDP arrives later as `thread/realtime/sdp` and should be passed to `setRemoteDescription()`:
|
||||
Then send `offer.sdp` to app-server. Core uses `experimental_realtime_ws_backend_prompt` for the backend instructions and the thread conversation id as the default Realtime API session identifier. This `realtimeSessionId` value refers to the upstream Realtime API session, not a Codex session/thread-group id. The start response is `{}`; the remote answer SDP arrives later as `thread/realtime/sdp` and should be passed to `setRemoteDescription()`:
|
||||
|
||||
```json
|
||||
{ "method": "thread/realtime/start", "id": 40, "params": {
|
||||
"threadId": "thr_123",
|
||||
"outputModality": "audio",
|
||||
"prompt": "You are on a call.",
|
||||
"sessionId": null,
|
||||
"realtimeSessionId": null,
|
||||
"transport": { "type": "webrtc", "sdp": "v=0\r\no=..." }
|
||||
} }
|
||||
{ "id": 40, "result": {} }
|
||||
@@ -1094,7 +1100,7 @@ The fuzzy file search session API emits per-query notifications:
|
||||
|
||||
The thread realtime API emits thread-scoped notifications for session lifecycle and streaming media:
|
||||
|
||||
- `thread/realtime/started` — `{ threadId, sessionId }` once realtime starts for the thread (experimental).
|
||||
- `thread/realtime/started` — `{ threadId, realtimeSessionId }` once realtime starts for the thread (experimental). `realtimeSessionId` is the upstream Realtime API session identifier, not a Codex session/thread-group id.
|
||||
- `thread/realtime/itemAdded` — `{ threadId, item }` for raw non-audio realtime items that do not have a dedicated typed app-server notification, including `handoff_request` (experimental). `item` is forwarded as raw JSON while the upstream websocket item schema remains unstable.
|
||||
- `thread/realtime/transcript/delta` — `{ threadId, role, delta }` for live realtime transcript deltas (experimental).
|
||||
- `thread/realtime/transcript/done` — `{ threadId, role, text }` when realtime emits the final full text for a transcript part (experimental).
|
||||
@@ -1177,7 +1183,7 @@ There are additional item-specific events:
|
||||
#### fileChange
|
||||
|
||||
- `item/fileChange/patchUpdated` - when `features.apply_patch_streaming_events` is enabled, streams structured file-change snapshots parsed from the model-generated patch before it is executed.
|
||||
- `item/fileChange/outputDelta` - contains the tool call response of the underlying `apply_patch` tool call.
|
||||
- `item/fileChange/outputDelta` - deprecated legacy protocol entry for `apply_patch` text output; retained for compatibility but no longer emitted by the server.
|
||||
|
||||
### Errors
|
||||
|
||||
@@ -1450,6 +1456,68 @@ To enable or disable a skill by name:
|
||||
}
|
||||
```
|
||||
|
||||
Use `hooks/list` to fetch the discovered hooks for one or more `cwds`. Each entry is evaluated using that `cwd`'s effective config, so feature gating and discovered config layers can differ across entries in the same request. Disabled hooks are still returned with `"enabled": false` so clients can render and re-enable them. Hook state is stored under `hooks.state`; clients should treat hooks from managed sources as non-configurable, and user config entries for those keys are ignored during loading. Hook keys combine the source identity with a trailing event/group/handler selector that is currently positional.
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "hooks/list",
|
||||
"id": 28,
|
||||
"params": {
|
||||
"cwds": ["/Users/me/project"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 28,
|
||||
"result": {
|
||||
"data": [{
|
||||
"cwd": "/Users/me/project",
|
||||
"hooks": [{
|
||||
"key": "/Users/me/.codex/config.toml:pre_tool_use:0:0",
|
||||
"eventName": "pre_tool_use",
|
||||
"handlerType": "command",
|
||||
"isManaged": false,
|
||||
"matcher": "Bash",
|
||||
"command": "python3 /Users/me/hook.py",
|
||||
"timeoutSec": 5,
|
||||
"statusMessage": "running hook",
|
||||
"sourcePath": "/Users/me/.codex/config.toml",
|
||||
"source": "user",
|
||||
"pluginId": null,
|
||||
"displayOrder": 0,
|
||||
"enabled": true
|
||||
}],
|
||||
"warnings": [],
|
||||
"errors": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To disable a non-managed hook, upsert a state entry at `hooks.state` with `config/batchWrite`:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "config/batchWrite",
|
||||
"id": 29,
|
||||
"params": {
|
||||
"edits": [{
|
||||
"keyPath": "hooks.state",
|
||||
"value": {
|
||||
"/Users/me/.codex/config.toml:pre_tool_use:0:0": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"mergeStrategy": "upsert"
|
||||
}],
|
||||
"reloadUserConfig": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To re-enable it, upsert the same hook key with `"enabled": true`.
|
||||
## Apps
|
||||
|
||||
Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, `branding`, `appMetadata`, `labels`, whether it is currently accessible, and whether it is enabled in config.
|
||||
|
||||
16
codex-rs/app-server/src/analytics_utils.rs
Normal file
16
codex-rs/app-server/src/analytics_utils.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_core::config::Config;
|
||||
use codex_login::AuthManager;
|
||||
|
||||
pub(crate) fn analytics_events_client_from_config(
|
||||
auth_manager: Arc<AuthManager>,
|
||||
config: &Config,
|
||||
) -> AnalyticsEventsClient {
|
||||
AnalyticsEventsClient::new(
|
||||
auth_manager,
|
||||
config.chatgpt_base_url.trim_end_matches('/').to_string(),
|
||||
config.analytics_enabled,
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user