mirror of
https://github.com/openai/codex.git
synced 2026-05-11 23:02:39 +00:00
Compare commits
35 Commits
aibrahim/c
...
xli-codex/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1ec596a53 | ||
|
|
bdf075769d | ||
|
|
d923e47cb9 | ||
|
|
34f09a0ca1 | ||
|
|
ed7a129ecc | ||
|
|
0029bf63be | ||
|
|
06e5dfa4dd | ||
|
|
fe24a180ab | ||
|
|
b5e965e1d7 | ||
|
|
a98623511b | ||
|
|
f9a907aebe | ||
|
|
22326e263c | ||
|
|
9766d3d51c | ||
|
|
41505bcea2 | ||
|
|
9f06d171e2 | ||
|
|
8ef31894dc | ||
|
|
5119680f85 | ||
|
|
b3d4f1a9f0 | ||
|
|
94db03d5af | ||
|
|
136e442e95 | ||
|
|
024118625e | ||
|
|
a736cb55a2 | ||
|
|
db22c91e61 | ||
|
|
794c240f25 | ||
|
|
2c1a361a2e | ||
|
|
3ec18a2c0a | ||
|
|
26f355b67b | ||
|
|
03d3403a41 | ||
|
|
d7de4dd3ac | ||
|
|
332b8b2c74 | ||
|
|
ee02cf26d6 | ||
|
|
d0f9d5eba2 | ||
|
|
7e310bc7f3 | ||
|
|
36460387ec | ||
|
|
bb2257e3f5 |
10
.bazelrc
10
.bazelrc
@@ -183,5 +183,15 @@ common:ci-v8 --build_metadata=TAG_os=linux
|
||||
common:ci-v8 --config=remote
|
||||
common:ci-v8 --strategy=remote
|
||||
|
||||
# Source-built Bazel V8 artifacts use the in-process sandbox by default. This
|
||||
# does not affect Cargo's default prebuilt rusty_v8 path.
|
||||
common --@v8//:v8_enable_pointer_compression=True
|
||||
common --@v8//:v8_enable_sandbox=True
|
||||
|
||||
# Keep currently published rusty_v8 release artifacts non-sandboxed until the
|
||||
# artifact migration ships matching Rust feature selection for Cargo consumers.
|
||||
common:v8-release-compat --@v8//:v8_enable_pointer_compression=False
|
||||
common:v8-release-compat --@v8//:v8_enable_sandbox=False
|
||||
|
||||
# Optional per-user local overrides.
|
||||
try-import %workspace%/user.bazelrc
|
||||
|
||||
16
.github/dotslash-config.json
vendored
16
.github/dotslash-config.json
vendored
@@ -11,11 +11,11 @@
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"regex": "^codex-x86_64-unknown-linux-musl\\.zst$",
|
||||
"regex": "^codex-x86_64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^codex-aarch64-unknown-linux-musl\\.zst$",
|
||||
"regex": "^codex-aarch64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
@@ -84,6 +84,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bwrap": {
|
||||
"platforms": {
|
||||
"linux-x86_64": {
|
||||
"regex": "^bwrap-x86_64-unknown-linux-musl\\.zst$",
|
||||
"path": "bwrap"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^bwrap-aarch64-unknown-linux-musl\\.zst$",
|
||||
"path": "bwrap"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codex-command-runner": {
|
||||
"platforms": {
|
||||
"windows-x86_64": {
|
||||
|
||||
39
.github/scripts/rusty_v8_bazel.py
vendored
39
.github/scripts/rusty_v8_bazel.py
vendored
@@ -63,8 +63,10 @@ def bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
expression = "set(" + " ".join(labels) + ")"
|
||||
bazel_configs = bazel_configs or []
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
@@ -72,6 +74,7 @@ def bazel_output_files(
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
*[f"--config={config}" for config in bazel_configs],
|
||||
"--output=files",
|
||||
expression,
|
||||
],
|
||||
@@ -87,7 +90,9 @@ def bazel_build(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> None:
|
||||
bazel_configs = bazel_configs or []
|
||||
subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
@@ -95,6 +100,7 @@ def bazel_build(
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
*[f"--config={config}" for config in bazel_configs],
|
||||
*labels,
|
||||
],
|
||||
cwd=ROOT,
|
||||
@@ -106,13 +112,14 @@ def ensure_bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
|
||||
if all(path.exists() for path in outputs):
|
||||
return outputs
|
||||
|
||||
bazel_build(platform, labels, compilation_mode)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
bazel_build(platform, labels, compilation_mode, bazel_configs)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
|
||||
missing = [str(path) for path in outputs if not path.exists()]
|
||||
if missing:
|
||||
raise SystemExit(f"missing built outputs for {labels}: {missing}")
|
||||
@@ -187,8 +194,9 @@ def single_bazel_output_file(
|
||||
platform: str,
|
||||
label: str,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> Path:
|
||||
outputs = ensure_bazel_output_files(platform, [label], compilation_mode)
|
||||
outputs = ensure_bazel_output_files(platform, [label], compilation_mode, bazel_configs)
|
||||
if len(outputs) != 1:
|
||||
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
|
||||
return outputs[0]
|
||||
@@ -198,11 +206,17 @@ def merged_musl_archive(
|
||||
platform: str,
|
||||
lib_path: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> Path:
|
||||
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode)
|
||||
llvm_ranlib = single_bazel_output_file(platform, LLVM_RANLIB_LABEL, compilation_mode)
|
||||
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode, bazel_configs)
|
||||
llvm_ranlib = single_bazel_output_file(
|
||||
platform,
|
||||
LLVM_RANLIB_LABEL,
|
||||
compilation_mode,
|
||||
bazel_configs,
|
||||
)
|
||||
runtime_archives = [
|
||||
single_bazel_output_file(platform, label, compilation_mode)
|
||||
single_bazel_output_file(platform, label, compilation_mode, bazel_configs)
|
||||
for label in MUSL_RUNTIME_ARCHIVE_LABELS
|
||||
]
|
||||
|
||||
@@ -233,11 +247,13 @@ def stage_release_pair(
|
||||
target: str,
|
||||
output_dir: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> None:
|
||||
outputs = ensure_bazel_output_files(
|
||||
platform,
|
||||
[release_pair_label(target)],
|
||||
compilation_mode,
|
||||
bazel_configs,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -254,7 +270,7 @@ def stage_release_pair(
|
||||
staged_library = output_dir / staged_archive_name(target, lib_path)
|
||||
staged_binding = output_dir / f"src_binding_release_{target}.rs"
|
||||
source_archive = (
|
||||
merged_musl_archive(platform, lib_path, compilation_mode)
|
||||
merged_musl_archive(platform, lib_path, compilation_mode, bazel_configs)
|
||||
if is_musl_archive_target(target, lib_path)
|
||||
else lib_path
|
||||
)
|
||||
@@ -293,6 +309,12 @@ def parse_args() -> argparse.Namespace:
|
||||
stage_release_pair_parser.add_argument("--platform", required=True)
|
||||
stage_release_pair_parser.add_argument("--target", required=True)
|
||||
stage_release_pair_parser.add_argument("--output-dir", required=True)
|
||||
stage_release_pair_parser.add_argument(
|
||||
"--bazel-config",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="bazel_configs",
|
||||
)
|
||||
stage_release_pair_parser.add_argument(
|
||||
"--compilation-mode",
|
||||
default="fastbuild",
|
||||
@@ -330,6 +352,7 @@ def main() -> int:
|
||||
target=args.target,
|
||||
output_dir=Path(args.output_dir),
|
||||
compilation_mode=args.compilation_mode,
|
||||
bazel_configs=args.bazel_configs,
|
||||
)
|
||||
return 0
|
||||
if args.command == "resolved-v8-crate-version":
|
||||
|
||||
@@ -25,7 +25,10 @@ TOP_LEVEL_NAME_EXCEPTIONS = {
|
||||
UTILITY_NAME_EXCEPTIONS = {
|
||||
"path-utils": "codex-utils-path",
|
||||
}
|
||||
MANIFEST_FEATURE_EXCEPTIONS = {}
|
||||
MANIFEST_FEATURE_EXCEPTIONS = {
|
||||
"codex-rs/code-mode/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
|
||||
"codex-rs/v8-poc/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
|
||||
}
|
||||
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
|
||||
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}
|
||||
|
||||
|
||||
16
.github/workflows/bazel.yml
vendored
16
.github/workflows/bazel.yml
vendored
@@ -371,6 +371,22 @@ jobs:
|
||||
-- \
|
||||
"${bazel_targets[@]}"
|
||||
|
||||
- name: Verify Bazel builds bwrap
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
--remote-download-toplevel \
|
||||
--print-failed-action-summary \
|
||||
-- \
|
||||
build \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
--build_metadata=TAG_job=verify-bwrap \
|
||||
-- \
|
||||
//codex-rs/bwrap:bwrap
|
||||
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
continue-on-error: true
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -52,10 +52,12 @@ jobs:
|
||||
CODEX_VERSION=0.125.0
|
||||
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/24901475298"
|
||||
OUTPUT_DIR="${RUNNER_TEMP}"
|
||||
# This reused workflow predates the standalone bwrap artifact.
|
||||
python3 ./scripts/stage_npm_packages.py \
|
||||
--release-version "$CODEX_VERSION" \
|
||||
--workflow-url "$WORKFLOW_URL" \
|
||||
--package codex \
|
||||
--allow-missing-native-component bwrap \
|
||||
--output-dir "$OUTPUT_DIR"
|
||||
PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz"
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
39
.github/workflows/rust-release.yml
vendored
39
.github/workflows/rust-release.yml
vendored
@@ -96,7 +96,7 @@ jobs:
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: x86_64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy"
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: aarch64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy"
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
@@ -255,6 +255,24 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- if: ${{ contains(matrix.target, 'linux') && matrix.bundle == 'primary' }}
|
||||
name: Build bwrap and export digest
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
target="${{ matrix.target }}"
|
||||
cargo build --target "$target" --release --timings --bin bwrap
|
||||
|
||||
bwrap_path="target/${target}/release/bwrap"
|
||||
if [[ ! -f "$bwrap_path" ]]; then
|
||||
echo "bwrap binary ${bwrap_path} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
digest="$(sha256sum "$bwrap_path" | awk '{print $1}')"
|
||||
echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV"
|
||||
echo "Built bwrap ${bwrap_path} with sha256:${digest}"
|
||||
|
||||
- name: Cargo build
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -361,6 +379,17 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.bundle }}" == "primary" ]]; then
|
||||
bundle_root="${RUNNER_TEMP}/codex-${{ matrix.target }}-bundle"
|
||||
rm -rf "$bundle_root"
|
||||
mkdir -p "$bundle_root/codex-resources"
|
||||
cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex"
|
||||
cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap"
|
||||
chmod 0755 "$bundle_root/codex" "$bundle_root/codex-resources/bwrap"
|
||||
tar -C "$bundle_root" -cf - codex codex-resources/bwrap |
|
||||
zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst"
|
||||
fi
|
||||
|
||||
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
|
||||
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
|
||||
fi
|
||||
@@ -384,7 +413,7 @@ jobs:
|
||||
base="$(basename "$f")"
|
||||
# Skip files that are already archives (shouldn't happen, but be
|
||||
# safe).
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -404,8 +433,8 @@ jobs:
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
# Upload the per-binary .zst files as well as the new .tar.gz
|
||||
# equivalents we generated in the previous step.
|
||||
# Upload the per-binary .zst files, .tar.gz equivalents, and any
|
||||
# prebuilt archives staged above.
|
||||
path: |
|
||||
codex-rs/dist/${{ matrix.target }}/*
|
||||
|
||||
|
||||
39
.github/workflows/rusty-v8-release.yml
vendored
39
.github/workflows/rusty-v8-release.yml
vendored
@@ -1,20 +1,12 @@
|
||||
name: rusty-v8-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Optional release tag. Defaults to rusty-v8-v<resolved_v8_version>.
|
||||
required: false
|
||||
type: string
|
||||
publish:
|
||||
description: Publish the staged musl artifacts to a GitHub release.
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
push:
|
||||
tags:
|
||||
- "rusty-v8-v*.*.*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ inputs.release_tag || github.run_id }}
|
||||
group: ${{ github.workflow }}::${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
@@ -43,15 +35,17 @@ jobs:
|
||||
- name: Resolve release tag
|
||||
id: release_tag
|
||||
env:
|
||||
RELEASE_TAG_INPUT: ${{ inputs.release_tag }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
V8_VERSION: ${{ steps.v8_version.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
release_tag="${RELEASE_TAG_INPUT}"
|
||||
if [[ -z "${release_tag}" ]]; then
|
||||
release_tag="rusty-v8-v${V8_VERSION}"
|
||||
expected_release_tag="rusty-v8-v${V8_VERSION}"
|
||||
release_tag="${GITHUB_REF_NAME}"
|
||||
if [[ "${release_tag}" != "${expected_release_tag}" ]]; then
|
||||
echo "Tag ${release_tag} does not match resolved v8 crate version ${V8_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
||||
@@ -111,6 +105,7 @@ jobs:
|
||||
-c
|
||||
opt
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=v8-release-compat
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
@@ -134,6 +129,7 @@ jobs:
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--compilation-mode opt \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
@@ -143,7 +139,6 @@ jobs:
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
publish-release:
|
||||
if: ${{ inputs.publish }}
|
||||
needs:
|
||||
- metadata
|
||||
- build
|
||||
@@ -153,16 +148,6 @@ jobs:
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Ensure publishing from default branch
|
||||
if: ${{ github.ref_name != github.event.repository.default_branch }}
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "Publishing is only allowed from ${DEFAULT_BRANCH}; current ref is ${GITHUB_REF_NAME}." >&2
|
||||
exit 1
|
||||
|
||||
- name: Ensure release tag is new
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
2
.github/workflows/v8-canary.yml
vendored
2
.github/workflows/v8-canary.yml
vendored
@@ -105,6 +105,7 @@ jobs:
|
||||
bazel_args=(
|
||||
build
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=v8-release-compat
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
@@ -127,6 +128,7 @@ jobs:
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
|
||||
12
MODULE.bazel
12
MODULE.bazel
@@ -327,6 +327,18 @@ crate.annotation(
|
||||
"RUSTY_V8_SRC_BINDING_PATH": "$(execpath @v8_targets//:rusty_v8_binding_for_target)",
|
||||
},
|
||||
crate = "v8",
|
||||
# Keep the Rust feature aligned with the source-built Bazel artifacts.
|
||||
# Windows MSVC still consumes upstream non-sandboxed prebuilts.
|
||||
crate_features_select = {
|
||||
"aarch64-apple-darwin": ["v8_enable_sandbox"],
|
||||
"aarch64-pc-windows-gnullvm": ["v8_enable_sandbox"],
|
||||
"aarch64-unknown-linux-gnu": ["v8_enable_sandbox"],
|
||||
"aarch64-unknown-linux-musl": ["v8_enable_sandbox"],
|
||||
"x86_64-apple-darwin": ["v8_enable_sandbox"],
|
||||
"x86_64-pc-windows-gnullvm": ["v8_enable_sandbox"],
|
||||
"x86_64-unknown-linux-gnu": ["v8_enable_sandbox"],
|
||||
"x86_64-unknown-linux-musl": ["v8_enable_sandbox"],
|
||||
},
|
||||
gen_build_script = "on",
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
|
||||
@@ -69,8 +69,8 @@ PACKAGE_EXPANSIONS: dict[str, list[str]] = {
|
||||
|
||||
PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
|
||||
"codex": [],
|
||||
"codex-linux-x64": ["codex", "rg"],
|
||||
"codex-linux-arm64": ["codex", "rg"],
|
||||
"codex-linux-x64": ["bwrap", "codex", "rg"],
|
||||
"codex-linux-arm64": ["bwrap", "codex", "rg"],
|
||||
"codex-darwin-x64": ["codex", "rg"],
|
||||
"codex-darwin-arm64": ["codex", "rg"],
|
||||
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
@@ -87,6 +87,7 @@ PACKAGE_TARGET_FILTERS: dict[str, str] = {
|
||||
PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS)
|
||||
|
||||
COMPONENT_DEST_DIR: dict[str, str] = {
|
||||
"bwrap": "codex-resources",
|
||||
"codex": "codex",
|
||||
"codex-responses-api-proxy": "codex-responses-api-proxy",
|
||||
"codex-windows-sandbox-setup": "codex",
|
||||
@@ -137,6 +138,16 @@ def parse_args() -> argparse.Namespace:
|
||||
type=Path,
|
||||
help="Directory containing pre-installed native binaries to bundle (vendor root).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-missing-native-component",
|
||||
dest="allow_missing_native_components",
|
||||
action="append",
|
||||
default=[],
|
||||
help=(
|
||||
"Native component that may be absent from --vendor-src. Intended for CI "
|
||||
"compatibility with older artifact workflows; releases should not use this."
|
||||
),
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -177,6 +188,7 @@ def main() -> int:
|
||||
staging_dir,
|
||||
native_components,
|
||||
target_filter={target_filter} if target_filter else None,
|
||||
allow_missing_components=set(args.allow_missing_native_components),
|
||||
)
|
||||
|
||||
if release_version:
|
||||
@@ -365,12 +377,14 @@ def copy_native_binaries(
|
||||
staging_dir: Path,
|
||||
components: list[str],
|
||||
target_filter: set[str] | None = None,
|
||||
allow_missing_components: set[str] | None = None,
|
||||
) -> None:
|
||||
vendor_src = vendor_src.resolve()
|
||||
if not vendor_src.exists():
|
||||
raise RuntimeError(f"Vendor source directory not found: {vendor_src}")
|
||||
|
||||
components_set = {component for component in components if component in COMPONENT_DEST_DIR}
|
||||
allow_missing_components = allow_missing_components or set()
|
||||
if not components_set:
|
||||
return
|
||||
|
||||
@@ -399,6 +413,8 @@ def copy_native_binaries(
|
||||
|
||||
src_component_dir = target_dir / dest_dir_name
|
||||
if not src_component_dir.exists():
|
||||
if component in allow_missing_components:
|
||||
continue
|
||||
raise RuntimeError(
|
||||
f"Missing native component '{component}' in vendor source: {src_component_dir}"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install Codex native binaries (Rust CLI plus ripgrep helpers)."""
|
||||
"""Install Codex native binaries (Rust CLI, bwrap, and ripgrep helpers)."""
|
||||
|
||||
import argparse
|
||||
from contextlib import contextmanager
|
||||
@@ -42,8 +42,15 @@ class BinaryComponent:
|
||||
|
||||
|
||||
WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target)
|
||||
LINUX_TARGETS = tuple(target for target in BINARY_TARGETS if "linux" in target)
|
||||
|
||||
BINARY_COMPONENTS = {
|
||||
"bwrap": BinaryComponent(
|
||||
artifact_prefix="bwrap",
|
||||
dest_dir="codex-resources",
|
||||
binary_basename="bwrap",
|
||||
targets=LINUX_TARGETS,
|
||||
),
|
||||
"codex": BinaryComponent(
|
||||
artifact_prefix="codex",
|
||||
dest_dir="codex",
|
||||
@@ -135,7 +142,7 @@ def parse_args() -> argparse.Namespace:
|
||||
choices=tuple(list(BINARY_COMPONENTS) + ["rg"]),
|
||||
help=(
|
||||
"Limit installation to the specified components."
|
||||
" May be repeated. Defaults to codex, codex-windows-sandbox-setup,"
|
||||
" May be repeated. Defaults to bwrap, codex, codex-windows-sandbox-setup,"
|
||||
" codex-command-runner, and rg."
|
||||
),
|
||||
)
|
||||
@@ -159,6 +166,7 @@ def main() -> int:
|
||||
vendor_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
components = args.components or [
|
||||
"bwrap",
|
||||
"codex",
|
||||
"codex-windows-sandbox-setup",
|
||||
"codex-command-runner",
|
||||
|
||||
16
codex-rs/Cargo.lock
generated
16
codex-rs/Cargo.lock
generated
@@ -1944,6 +1944,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
@@ -2125,6 +2126,15 @@ dependencies = [
|
||||
"serde_with",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-bwrap"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-chatgpt"
|
||||
version = "0.0.0"
|
||||
@@ -2411,6 +2421,7 @@ dependencies = [
|
||||
"bm25",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-agent-graph-store",
|
||||
"codex-analytics",
|
||||
"codex-api",
|
||||
"codex-app-server-protocol",
|
||||
@@ -2700,6 +2711,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.18",
|
||||
@@ -2708,6 +2720,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2902,7 +2915,6 @@ dependencies = [
|
||||
name = "codex-linux-sandbox"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"clap",
|
||||
"codex-core",
|
||||
"codex-process-hardening",
|
||||
@@ -2912,11 +2924,11 @@ dependencies = [
|
||||
"globset",
|
||||
"landlock",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"pretty_assertions",
|
||||
"seccompiler",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"url",
|
||||
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
"agent-graph-store",
|
||||
"agent-identity",
|
||||
"backend-client",
|
||||
"bwrap",
|
||||
"ansi-escape",
|
||||
"async-utils",
|
||||
"app-server",
|
||||
|
||||
@@ -79,6 +79,7 @@ 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::ThreadSource as AppServerThreadSource;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus;
|
||||
use codex_app_server_protocol::Turn;
|
||||
@@ -107,6 +108,7 @@ use codex_protocol::protocol::HookSource;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
@@ -118,14 +120,11 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
fn sample_thread(thread_id: &str, ephemeral: bool) -> Thread {
|
||||
sample_thread_with_source(thread_id, ephemeral, AppServerSessionSource::Exec)
|
||||
}
|
||||
|
||||
fn sample_thread_with_source(
|
||||
fn sample_thread_with_metadata(
|
||||
thread_id: &str,
|
||||
ephemeral: bool,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
@@ -140,6 +139,7 @@ fn sample_thread_with_source(
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source,
|
||||
thread_source,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
@@ -154,7 +154,13 @@ fn sample_thread_start_response(
|
||||
model: &str,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
thread: sample_thread(thread_id, ephemeral),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
@@ -198,6 +204,7 @@ fn sample_thread_resume_response(
|
||||
ephemeral,
|
||||
model,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -206,9 +213,11 @@ fn sample_thread_resume_response_with_source(
|
||||
ephemeral: bool,
|
||||
model: &str,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
thread: sample_thread_with_source(thread_id, ephemeral, source),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
@@ -753,7 +762,7 @@ fn compaction_event_serializes_expected_shape() {
|
||||
},
|
||||
sample_app_server_client_metadata(),
|
||||
sample_runtime_metadata(),
|
||||
Some("user"),
|
||||
Some(ThreadSource::User),
|
||||
/*subagent_source*/ None,
|
||||
/*parent_thread_id*/ None,
|
||||
),
|
||||
@@ -852,7 +861,7 @@ fn thread_initialized_event_serializes_expected_shape() {
|
||||
},
|
||||
model: "gpt-5".to_string(),
|
||||
ephemeral: true,
|
||||
thread_source: Some("user"),
|
||||
thread_source: Some(ThreadSource::User),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
@@ -1196,6 +1205,7 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
}),
|
||||
Some(AppServerThreadSource::Subagent),
|
||||
)),
|
||||
},
|
||||
&mut events,
|
||||
@@ -2116,7 +2126,7 @@ fn turn_event_serializes_expected_shape() {
|
||||
runtime: sample_runtime_metadata(),
|
||||
submission_type: None,
|
||||
ephemeral: false,
|
||||
thread_source: Some("user".to_string()),
|
||||
thread_source: Some(ThreadSource::User),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
|
||||
@@ -87,6 +87,7 @@ fn sample_thread(thread_id: &str) -> Thread {
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: AppServerSessionSource::Exec,
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
@@ -101,6 +102,7 @@ fn sample_permission_profile() -> AppServerPermissionProfile {
|
||||
|
||||
fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
session_id: "session-1".to_string(),
|
||||
thread: sample_thread("thread-1"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -118,6 +120,7 @@ fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
session_id: "session-2".to_string(),
|
||||
thread: sample_thread("thread-2"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -135,6 +138,7 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_fork_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadFork(ThreadForkResponse {
|
||||
session_id: "session-3".to_string(),
|
||||
thread: sample_thread("thread-3"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
|
||||
@@ -33,6 +33,7 @@ use codex_protocol::protocol::HookEventName;
|
||||
use codex_protocol::protocol::HookRunStatus;
|
||||
use codex_protocol::protocol::HookSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -126,7 +127,7 @@ pub(crate) struct ThreadInitializedEventParams {
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) model: String,
|
||||
pub(crate) ephemeral: bool,
|
||||
pub(crate) thread_source: Option<&'static str>,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) initialization_mode: ThreadInitializationMode,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
@@ -647,7 +648,7 @@ pub(crate) struct CodexCompactionEventParams {
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<&'static str>,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) trigger: CompactionTrigger,
|
||||
@@ -680,7 +681,7 @@ pub(crate) struct CodexTurnEventParams {
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) ephemeral: bool,
|
||||
pub(crate) thread_source: Option<String>,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) initialization_mode: ThreadInitializationMode,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
@@ -733,7 +734,7 @@ pub(crate) struct CodexTurnSteerEventParams {
|
||||
pub(crate) accepted_turn_id: Option<String>,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<String>,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) num_input_images: usize,
|
||||
@@ -836,7 +837,7 @@ pub(crate) fn codex_compaction_event_params(
|
||||
input: CodexCompactionEvent,
|
||||
app_server_client: CodexAppServerClientMetadata,
|
||||
runtime: CodexRuntimeMetadata,
|
||||
thread_source: Option<&'static str>,
|
||||
thread_source: Option<ThreadSource>,
|
||||
subagent_source: Option<String>,
|
||||
parent_thread_id: Option<String>,
|
||||
) -> CodexCompactionEventParams {
|
||||
@@ -940,7 +941,7 @@ pub(crate) fn subagent_thread_started_event_request(
|
||||
runtime: current_runtime_metadata(),
|
||||
model: input.model,
|
||||
ephemeral: input.ephemeral,
|
||||
thread_source: Some("subagent"),
|
||||
thread_source: Some(ThreadSource::Subagent),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: Some(subagent_source_name(&input.subagent_source)),
|
||||
parent_thread_id: input
|
||||
|
||||
@@ -64,6 +64,7 @@ use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_protocol::protocol::ThreadSource;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use sha1::Digest;
|
||||
use std::collections::HashMap;
|
||||
@@ -147,7 +148,7 @@ enum MissingAnalyticsContext {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ThreadMetadataState {
|
||||
thread_source: Option<&'static str>,
|
||||
thread_source: Option<ThreadSource>,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
subagent_source: Option<String>,
|
||||
parent_thread_id: Option<String>,
|
||||
@@ -156,6 +157,7 @@ struct ThreadMetadataState {
|
||||
impl ThreadMetadataState {
|
||||
fn from_thread_metadata(
|
||||
session_source: &SessionSource,
|
||||
thread_source: Option<ThreadSource>,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
) -> Self {
|
||||
let (subagent_source, parent_thread_id) = match session_source {
|
||||
@@ -172,7 +174,7 @@ impl ThreadMetadataState {
|
||||
| SessionSource::Unknown => (None, None),
|
||||
};
|
||||
Self {
|
||||
thread_source: session_source.thread_source_name(),
|
||||
thread_source,
|
||||
initialization_mode,
|
||||
subagent_source,
|
||||
parent_thread_id,
|
||||
@@ -348,7 +350,7 @@ impl AnalyticsReducer {
|
||||
thread_state
|
||||
.metadata
|
||||
.get_or_insert_with(|| ThreadMetadataState {
|
||||
thread_source: Some("subagent"),
|
||||
thread_source: Some(ThreadSource::Subagent),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: Some(subagent_source_name(&input.subagent_source)),
|
||||
parent_thread_id,
|
||||
@@ -749,13 +751,16 @@ impl AnalyticsReducer {
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let thread_source: SessionSource = thread.source.into();
|
||||
let session_source: SessionSource = thread.source.into();
|
||||
let thread_id = thread.id;
|
||||
let Some(connection_state) = self.connections.get(&connection_id) else {
|
||||
return;
|
||||
};
|
||||
let thread_metadata =
|
||||
ThreadMetadataState::from_thread_metadata(&thread_source, initialization_mode);
|
||||
let thread_metadata = ThreadMetadataState::from_thread_metadata(
|
||||
&session_source,
|
||||
thread.thread_source.map(Into::into),
|
||||
initialization_mode,
|
||||
);
|
||||
self.threads.insert(
|
||||
thread_id.clone(),
|
||||
ThreadAnalyticsState {
|
||||
@@ -857,7 +862,7 @@ impl AnalyticsReducer {
|
||||
accepted_turn_id,
|
||||
app_server_client: connection_state.app_server_client.clone(),
|
||||
runtime: connection_state.runtime.clone(),
|
||||
thread_source: thread_metadata.thread_source.map(str::to_string),
|
||||
thread_source: thread_metadata.thread_source,
|
||||
subagent_source: thread_metadata.subagent_source.clone(),
|
||||
parent_thread_id: thread_metadata.parent_thread_id.clone(),
|
||||
num_input_images: pending_request.num_input_images,
|
||||
@@ -1023,7 +1028,7 @@ fn codex_turn_event_params(
|
||||
runtime,
|
||||
submission_type,
|
||||
ephemeral,
|
||||
thread_source: thread_metadata.thread_source.map(str::to_string),
|
||||
thread_source: thread_metadata.thread_source,
|
||||
initialization_mode: thread_metadata.initialization_mode,
|
||||
subagent_source: thread_metadata.subagent_source.clone(),
|
||||
parent_thread_id: thread_metadata.parent_thread_id.clone(),
|
||||
|
||||
@@ -33,4 +33,5 @@ url = { workspace = true }
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
@@ -29,7 +29,6 @@ pub use codex_app_server::in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
|
||||
pub use codex_app_server::in_process::InProcessServerEvent;
|
||||
use codex_app_server::in_process::InProcessStartArgs;
|
||||
use codex_app_server::in_process::LogDbLayer;
|
||||
pub use codex_app_server::in_process::StateDbHandle;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientNotification;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
@@ -47,6 +46,7 @@ use codex_config::LoaderOverrides;
|
||||
use codex_config::NoopThreadConfigLoader;
|
||||
use codex_config::RemoteThreadConfigLoader;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
pub use codex_core::StateDbHandle;
|
||||
use codex_core::config::Config;
|
||||
pub use codex_exec_server::EnvironmentManager;
|
||||
pub use codex_exec_server::EnvironmentManagerArgs;
|
||||
@@ -954,9 +954,13 @@ mod tests {
|
||||
use codex_app_server_protocol::ToolRequestUserInputParams;
|
||||
use codex_app_server_protocol::ToolRequestUserInputQuestion;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::init_state_db_from_config;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
@@ -975,19 +979,59 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_test_config_for_codex_home(codex_home: &Path) -> Config {
|
||||
match ConfigBuilder::default()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(_) => Config::load_default_with_cli_overrides_for_codex_home(
|
||||
codex_home.to_path_buf(),
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
.expect("default config should load"),
|
||||
}
|
||||
}
|
||||
|
||||
struct TestClient {
|
||||
_codex_home: TempDir,
|
||||
client: InProcessAppServerClient,
|
||||
}
|
||||
|
||||
impl Deref for TestClient {
|
||||
type Target = InProcessAppServerClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
impl TestClient {
|
||||
async fn shutdown(self) -> IoResult<()> {
|
||||
self.client.shutdown().await
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_test_client_with_capacity(
|
||||
session_source: SessionSource,
|
||||
channel_capacity: usize,
|
||||
) -> InProcessAppServerClient {
|
||||
InProcessAppServerClient::start(InProcessClientStartArgs {
|
||||
) -> TestClient {
|
||||
let codex_home = TempDir::new().expect("temp dir");
|
||||
let config = Arc::new(build_test_config_for_codex_home(codex_home.path()).await);
|
||||
let state_db = init_state_db_from_config(config.as_ref())
|
||||
.await
|
||||
.expect("state db should initialize for in-process test");
|
||||
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
config: Arc::new(build_test_config().await),
|
||||
config,
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
state_db: Some(state_db),
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
config_warnings: Vec::new(),
|
||||
session_source,
|
||||
@@ -999,10 +1043,15 @@ mod tests {
|
||||
channel_capacity,
|
||||
})
|
||||
.await
|
||||
.expect("in-process app-server client should start")
|
||||
.expect("in-process app-server client should start");
|
||||
|
||||
TestClient {
|
||||
_codex_home: codex_home,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_test_client(session_source: SessionSource) -> InProcessAppServerClient {
|
||||
async fn start_test_client(session_source: SessionSource) -> TestClient {
|
||||
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
|
||||
}
|
||||
|
||||
|
||||
@@ -2197,11 +2197,37 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareListParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -2210,6 +2236,15 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2217,6 +2252,39 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSkillReadParams": {
|
||||
"properties": {
|
||||
"remoteMarketplaceName": {
|
||||
@@ -3558,6 +3626,17 @@
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4032,6 +4111,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSourceKind": {
|
||||
"enum": [
|
||||
"cli",
|
||||
@@ -4165,6 +4252,17 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this thread."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -5147,6 +5245,30 @@
|
||||
"title": "Plugin/share/saveRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/updateTargets"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginShareUpdateTargetsParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -3088,6 +3088,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -4094,6 +4105,14 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStartedNotification": {
|
||||
"properties": {
|
||||
"thread": {
|
||||
@@ -6074,4 +6093,4 @@
|
||||
}
|
||||
],
|
||||
"title": "ServerNotification"
|
||||
}
|
||||
}
|
||||
@@ -810,6 +810,30 @@
|
||||
"title": "Plugin/share/saveRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/updateTargets"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/PluginShareUpdateTargetsParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -12479,6 +12503,14 @@
|
||||
"title": "PluginShareDeleteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareListItem": {
|
||||
"properties": {
|
||||
"localPluginPath": {
|
||||
@@ -12525,9 +12557,46 @@
|
||||
"title": "PluginShareListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/v2/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
@@ -12536,6 +12605,15 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -12561,6 +12639,57 @@
|
||||
"title": "PluginShareSaveResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/v2/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateTargetsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginSharePrincipal"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSkillReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -12710,6 +12839,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -15225,6 +15361,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -15439,6 +15586,17 @@
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -15506,6 +15664,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
@@ -17013,6 +17176,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
@@ -17119,6 +17287,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSourceKind": {
|
||||
"enum": [
|
||||
"cli",
|
||||
@@ -17253,6 +17429,17 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this thread."
|
||||
}
|
||||
},
|
||||
"title": "ThreadStartParams",
|
||||
@@ -17317,6 +17504,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
@@ -18553,4 +18745,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocol",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1569,6 +1569,30 @@
|
||||
"title": "Plugin/share/saveRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/share/updateTargets"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginShareUpdateTargetsParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/share/updateTargetsRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -9090,6 +9114,14 @@
|
||||
"title": "PluginShareDeleteResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareListItem": {
|
||||
"properties": {
|
||||
"localPluginPath": {
|
||||
@@ -9136,9 +9168,46 @@
|
||||
"title": "PluginShareListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareSaveParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -9147,6 +9216,15 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -9172,6 +9250,57 @@
|
||||
"title": "PluginShareSaveResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateTargetsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSkillReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -9321,6 +9450,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -13111,6 +13247,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -13325,6 +13472,17 @@
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -13392,6 +13550,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
@@ -14899,6 +15062,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
@@ -15005,6 +15173,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSourceKind": {
|
||||
"enum": [
|
||||
"cli",
|
||||
@@ -15139,6 +15315,17 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this thread."
|
||||
}
|
||||
},
|
||||
"title": "ThreadStartParams",
|
||||
@@ -15203,6 +15390,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
@@ -16438,4 +16630,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocolV2",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1393,4 +1393,4 @@
|
||||
],
|
||||
"title": "ItemCompletedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1393,4 +1393,4 @@
|
||||
],
|
||||
"title": "ItemStartedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -347,6 +347,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -366,6 +366,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -307,6 +307,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -4,9 +4,50 @@
|
||||
"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"
|
||||
},
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginPath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
@@ -15,6 +56,15 @@
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
45
codex-rs/app-server-protocol/schema/json/v2/PluginShareUpdateTargetsParams.json
generated
Normal file
45
codex-rs/app-server-protocol/schema/json/v2/PluginShareUpdateTargetsParams.json
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareTarget": {
|
||||
"properties": {
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareTargets": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginShareTarget"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsParams",
|
||||
"type": "object"
|
||||
}
|
||||
45
codex-rs/app-server-protocol/schema/json/v2/PluginShareUpdateTargetsResponse.json
generated
Normal file
45
codex-rs/app-server-protocol/schema/json/v2/PluginShareUpdateTargetsResponse.json
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"principalType": {
|
||||
"$ref": "#/definitions/PluginSharePrincipalType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"principalId",
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSharePrincipalType": {
|
||||
"enum": [
|
||||
"user",
|
||||
"group",
|
||||
"workspace"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -137,6 +137,14 @@
|
||||
"flex"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
|
||||
@@ -232,6 +240,17 @@
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this forked thread."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -1419,6 +1419,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -2117,6 +2128,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -2600,6 +2619,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -1419,6 +1419,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -2117,6 +2128,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -2600,6 +2619,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -172,6 +172,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStartSource": {
|
||||
"enum": [
|
||||
"startup",
|
||||
@@ -312,6 +320,17 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional client-supplied analytics source classification for this thread."
|
||||
}
|
||||
},
|
||||
"title": "ThreadStartParams",
|
||||
|
||||
@@ -1419,6 +1419,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -2117,6 +2128,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -2600,6 +2619,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -869,6 +869,17 @@
|
||||
],
|
||||
"description": "Current runtime status for the thread."
|
||||
},
|
||||
"threadSource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadSource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional analytics source classification for this thread."
|
||||
},
|
||||
"turns": {
|
||||
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
|
||||
"items": {
|
||||
@@ -1567,6 +1578,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
"subagent",
|
||||
"memory_consolidation"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ThreadStatus": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDiscoverability.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareDiscoverability.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 PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE";
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginSharePrincipal.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginSharePrincipal.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 { PluginSharePrincipalType } from "./PluginSharePrincipalType";
|
||||
|
||||
export type PluginSharePrincipal = { principalType: PluginSharePrincipalType, principalId: string, name: string, };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginSharePrincipalType.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginSharePrincipalType.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 PluginSharePrincipalType = "user" | "group" | "workspace";
|
||||
@@ -2,5 +2,7 @@
|
||||
|
||||
// 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 { PluginShareDiscoverability } from "./PluginShareDiscoverability";
|
||||
import type { PluginShareTarget } from "./PluginShareTarget";
|
||||
|
||||
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, };
|
||||
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, discoverability?: PluginShareDiscoverability | null, shareTargets?: Array<PluginShareTarget> | null, };
|
||||
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareTarget.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareTarget.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 { PluginSharePrincipalType } from "./PluginSharePrincipalType";
|
||||
|
||||
export type PluginShareTarget = { principalType: PluginSharePrincipalType, principalId: string, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateTargetsParams.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateTargetsParams.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 { PluginShareTarget } from "./PluginShareTarget";
|
||||
|
||||
export type PluginShareUpdateTargetsParams = { remotePluginId: string, shareTargets: Array<PluginShareTarget>, };
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateTargetsResponse.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateTargetsResponse.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 { PluginSharePrincipal } from "./PluginSharePrincipal";
|
||||
|
||||
export type PluginShareUpdateTargetsResponse = { principals: Array<PluginSharePrincipal>, };
|
||||
@@ -11,4 +11,4 @@ export type PluginSummary = { id: string, name: string, source: PluginSource, in
|
||||
/**
|
||||
* Availability state for installing and using the plugin.
|
||||
*/
|
||||
availability: PluginAvailability, interface: PluginInterface | null, };
|
||||
availability: PluginAvailability, interface: PluginInterface | null, keywords: Array<string>, };
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { GitInfo } from "./GitInfo";
|
||||
import type { SessionSource } from "./SessionSource";
|
||||
import type { ThreadSource } from "./ThreadSource";
|
||||
import type { ThreadStatus } from "./ThreadStatus";
|
||||
import type { Turn } from "./Turn";
|
||||
|
||||
@@ -52,6 +53,10 @@ cliVersion: string,
|
||||
* Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).
|
||||
*/
|
||||
source: SessionSource,
|
||||
/**
|
||||
* Optional analytics source classification for this thread.
|
||||
*/
|
||||
threadSource: ThreadSource | null,
|
||||
/**
|
||||
* Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxMode } from "./SandboxMode";
|
||||
import type { ThreadSource } from "./ThreadSource";
|
||||
|
||||
/**
|
||||
* There are two ways to fork a thread:
|
||||
@@ -23,4 +24,7 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
|
||||
* Override where approval requests are routed for review on this thread
|
||||
* and subsequent turns.
|
||||
*/
|
||||
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean};
|
||||
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
|
||||
* Optional client-supplied analytics source classification for this forked thread.
|
||||
*/
|
||||
threadSource?: ThreadSource | null};
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadForkResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadResumeResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/ThreadSource.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/ThreadSource.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 ThreadSource = "user" | "subagent" | "memory_consolidation";
|
||||
@@ -7,10 +7,14 @@ import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxMode } from "./SandboxMode";
|
||||
import type { ThreadSource } from "./ThreadSource";
|
||||
import type { ThreadStartSource } from "./ThreadStartSource";
|
||||
|
||||
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
|
||||
* Override where approval requests are routed for review on this thread
|
||||
* and subsequent turns.
|
||||
*/
|
||||
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null};
|
||||
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
|
||||
* Optional client-supplied analytics source classification for this thread.
|
||||
*/
|
||||
threadSource?: ThreadSource | null};
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadStartResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -285,11 +285,17 @@ export type { PluginReadParams } from "./PluginReadParams";
|
||||
export type { PluginReadResponse } from "./PluginReadResponse";
|
||||
export type { PluginShareDeleteParams } from "./PluginShareDeleteParams";
|
||||
export type { PluginShareDeleteResponse } from "./PluginShareDeleteResponse";
|
||||
export type { PluginShareDiscoverability } from "./PluginShareDiscoverability";
|
||||
export type { PluginShareListItem } from "./PluginShareListItem";
|
||||
export type { PluginShareListParams } from "./PluginShareListParams";
|
||||
export type { PluginShareListResponse } from "./PluginShareListResponse";
|
||||
export type { PluginSharePrincipal } from "./PluginSharePrincipal";
|
||||
export type { PluginSharePrincipalType } from "./PluginSharePrincipalType";
|
||||
export type { PluginShareSaveParams } from "./PluginShareSaveParams";
|
||||
export type { PluginShareSaveResponse } from "./PluginShareSaveResponse";
|
||||
export type { PluginShareTarget } from "./PluginShareTarget";
|
||||
export type { PluginShareUpdateTargetsParams } from "./PluginShareUpdateTargetsParams";
|
||||
export type { PluginShareUpdateTargetsResponse } from "./PluginShareUpdateTargetsResponse";
|
||||
export type { PluginSkillReadParams } from "./PluginSkillReadParams";
|
||||
export type { PluginSkillReadResponse } from "./PluginSkillReadResponse";
|
||||
export type { PluginSource } from "./PluginSource";
|
||||
@@ -396,6 +402,7 @@ export type { ThreadSetNameResponse } from "./ThreadSetNameResponse";
|
||||
export type { ThreadShellCommandParams } from "./ThreadShellCommandParams";
|
||||
export type { ThreadShellCommandResponse } from "./ThreadShellCommandResponse";
|
||||
export type { ThreadSortKey } from "./ThreadSortKey";
|
||||
export type { ThreadSource } from "./ThreadSource";
|
||||
export type { ThreadSourceKind } from "./ThreadSourceKind";
|
||||
export type { ThreadStartParams } from "./ThreadStartParams";
|
||||
export type { ThreadStartResponse } from "./ThreadStartResponse";
|
||||
|
||||
@@ -77,6 +77,7 @@ macro_rules! experimental_type_entry {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ClientRequestSerializationScope {
|
||||
Global(&'static str),
|
||||
GlobalSharedRead(&'static str),
|
||||
Thread { thread_id: String },
|
||||
ThreadPath { path: PathBuf },
|
||||
CommandExecProcess { process_id: String },
|
||||
@@ -93,6 +94,9 @@ macro_rules! serialization_scope_expr {
|
||||
($actual_params:ident, global($key:literal)) => {
|
||||
Some(ClientRequestSerializationScope::Global($key))
|
||||
};
|
||||
($actual_params:ident, global_shared_read($key:literal)) => {
|
||||
Some(ClientRequestSerializationScope::GlobalSharedRead($key))
|
||||
};
|
||||
($actual_params:ident, thread_id($params:ident . $field:ident)) => {
|
||||
Some(ClientRequestSerializationScope::Thread {
|
||||
thread_id: $actual_params.$field.clone(),
|
||||
@@ -585,7 +589,7 @@ client_request_definitions! {
|
||||
},
|
||||
SkillsList => "skills/list" {
|
||||
params: v2::SkillsListParams,
|
||||
serialization: global("config"),
|
||||
serialization: global_shared_read("config"),
|
||||
response: v2::SkillsListResponse,
|
||||
},
|
||||
HooksList => "hooks/list" {
|
||||
@@ -610,7 +614,7 @@ client_request_definitions! {
|
||||
},
|
||||
PluginList => "plugin/list" {
|
||||
params: v2::PluginListParams,
|
||||
serialization: global("config"),
|
||||
serialization: global_shared_read("config"),
|
||||
response: v2::PluginListResponse,
|
||||
},
|
||||
PluginRead => "plugin/read" {
|
||||
@@ -628,6 +632,11 @@ client_request_definitions! {
|
||||
serialization: global("config"),
|
||||
response: v2::PluginShareSaveResponse,
|
||||
},
|
||||
PluginShareUpdateTargets => "plugin/share/updateTargets" {
|
||||
params: v2::PluginShareUpdateTargetsParams,
|
||||
serialization: global("config"),
|
||||
response: v2::PluginShareUpdateTargetsResponse,
|
||||
},
|
||||
PluginShareList => "plugin/share/list" {
|
||||
params: v2::PluginShareListParams,
|
||||
serialization: global("config"),
|
||||
@@ -942,7 +951,7 @@ client_request_definitions! {
|
||||
|
||||
ConfigRead => "config/read" {
|
||||
params: v2::ConfigReadParams,
|
||||
serialization: global("config"),
|
||||
serialization: global_shared_read("config"),
|
||||
response: v2::ConfigReadResponse,
|
||||
},
|
||||
ExternalAgentConfigDetect => "externalAgentConfig/detect" {
|
||||
@@ -1650,6 +1659,28 @@ mod tests {
|
||||
Some(ClientRequestSerializationScope::Global("config"))
|
||||
);
|
||||
|
||||
let skills_list = ClientRequest::SkillsList {
|
||||
request_id: request_id(),
|
||||
params: v2::SkillsListParams {
|
||||
cwds: Vec::new(),
|
||||
force_reload: false,
|
||||
per_cwd_extra_user_roots: None,
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
skills_list.serialization_scope(),
|
||||
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
|
||||
);
|
||||
|
||||
let plugin_list = ClientRequest::PluginList {
|
||||
request_id: request_id(),
|
||||
params: v2::PluginListParams { cwds: None },
|
||||
};
|
||||
assert_eq!(
|
||||
plugin_list.serialization_scope(),
|
||||
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
|
||||
);
|
||||
|
||||
let plugin_uninstall = ClientRequest::PluginUninstall {
|
||||
request_id: request_id(),
|
||||
params: v2::PluginUninstallParams {
|
||||
@@ -1700,7 +1731,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
config_read.serialization_scope(),
|
||||
Some(ClientRequestSerializationScope::Global("config"))
|
||||
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
|
||||
);
|
||||
|
||||
let account_read = ClientRequest::GetAccount {
|
||||
@@ -2171,6 +2202,7 @@ mod tests {
|
||||
let response = ClientResponse::ThreadStart {
|
||||
request_id: RequestId::Integer(7),
|
||||
response: v2::ThreadStartResponse {
|
||||
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
|
||||
thread: v2::Thread {
|
||||
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||
forked_from_id: None,
|
||||
@@ -2184,6 +2216,7 @@ mod tests {
|
||||
cwd: cwd.clone(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: v2::SessionSource::Exec,
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
git_info: None,
|
||||
@@ -2211,6 +2244,7 @@ mod tests {
|
||||
"method": "thread/start",
|
||||
"id": 7,
|
||||
"response": {
|
||||
"sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7",
|
||||
"thread": {
|
||||
"id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
|
||||
"forkedFromId": null,
|
||||
@@ -2226,6 +2260,7 @@ mod tests {
|
||||
"cwd": absolute_path_string("tmp"),
|
||||
"cliVersion": "0.0.0",
|
||||
"source": "exec",
|
||||
"threadSource": null,
|
||||
"agentNickname": null,
|
||||
"agentRole": null,
|
||||
"gitInfo": null,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
383
codex-rs/app-server-protocol/src/protocol/v2/account.rs
Normal file
383
codex-rs/app-server-protocol/src/protocol/v2/account.rs
Normal file
@@ -0,0 +1,383 @@
|
||||
use crate::protocol::common::AuthMode;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::account::ProviderAccount;
|
||||
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum Account {
|
||||
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
||||
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
||||
ApiKey {},
|
||||
|
||||
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
||||
Chatgpt { email: String, plan_type: PlanType },
|
||||
|
||||
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
AmazonBedrock {},
|
||||
}
|
||||
|
||||
impl From<ProviderAccount> for Account {
|
||||
fn from(account: ProviderAccount) -> Self {
|
||||
match account {
|
||||
ProviderAccount::ApiKey => Self::ApiKey {},
|
||||
ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type },
|
||||
ProviderAccount::AmazonBedrock => Self::AmazonBedrock {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(tag = "type")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum LoginAccountParams {
|
||||
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
||||
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
||||
ApiKey {
|
||||
#[serde(rename = "apiKey")]
|
||||
#[ts(rename = "apiKey")]
|
||||
api_key: String,
|
||||
},
|
||||
#[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,
|
||||
/// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.
|
||||
/// The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.
|
||||
#[experimental("account/login/start.chatgptAuthTokens")]
|
||||
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
ChatgptAuthTokens {
|
||||
/// Access token (JWT) supplied by the client.
|
||||
/// This token is used for backend API requests and email extraction.
|
||||
access_token: String,
|
||||
/// Workspace/account identifier supplied by the client.
|
||||
chatgpt_account_id: String,
|
||||
/// Optional plan type supplied by the client.
|
||||
///
|
||||
/// When `null`, Codex attempts to derive the plan type from access-token
|
||||
/// claims. If unavailable, the plan defaults to `unknown`.
|
||||
#[ts(optional = nullable)]
|
||||
chatgpt_plan_type: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum LoginAccountResponse {
|
||||
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
||||
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
||||
ApiKey {},
|
||||
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
||||
Chatgpt {
|
||||
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
||||
// Convert to/from UUIDs at the application layer as needed.
|
||||
login_id: String,
|
||||
/// URL the client should open in a browser to initiate the OAuth flow.
|
||||
auth_url: String,
|
||||
},
|
||||
#[serde(rename = "chatgptDeviceCode", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgptDeviceCode", rename_all = "camelCase")]
|
||||
ChatgptDeviceCode {
|
||||
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
||||
// Convert to/from UUIDs at the application layer as needed.
|
||||
login_id: String,
|
||||
/// URL the client should open in a browser to complete device code authorization.
|
||||
verification_url: String,
|
||||
/// One-time code the user must enter after signing in.
|
||||
user_code: String,
|
||||
},
|
||||
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
ChatgptAuthTokens {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CancelLoginAccountParams {
|
||||
pub login_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CancelLoginAccountStatus {
|
||||
Canceled,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CancelLoginAccountResponse {
|
||||
pub status: CancelLoginAccountStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct LogoutAccountResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ChatgptAuthTokensRefreshReason {
|
||||
/// Codex attempted a backend request and received `401 Unauthorized`.
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ChatgptAuthTokensRefreshParams {
|
||||
pub reason: ChatgptAuthTokensRefreshReason,
|
||||
/// Workspace/account identifier that Codex was previously using.
|
||||
///
|
||||
/// Clients that manage multiple accounts/workspaces can use this as a hint
|
||||
/// to refresh the token for the correct workspace.
|
||||
///
|
||||
/// This may be `null` when the prior auth state did not include a workspace
|
||||
/// identifier (`chatgpt_account_id`).
|
||||
#[ts(optional = nullable)]
|
||||
pub previous_account_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ChatgptAuthTokensRefreshResponse {
|
||||
pub access_token: String,
|
||||
pub chatgpt_account_id: String,
|
||||
pub chatgpt_plan_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GetAccountRateLimitsResponse {
|
||||
/// Backward-compatible single-bucket view; mirrors the historical payload.
|
||||
pub rate_limits: RateLimitSnapshot,
|
||||
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
|
||||
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SendAddCreditsNudgeEmailParams {
|
||||
pub credit_type: AddCreditsNudgeCreditType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
pub enum AddCreditsNudgeCreditType {
|
||||
Credits,
|
||||
UsageLimit,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SendAddCreditsNudgeEmailResponse {
|
||||
pub status: AddCreditsNudgeEmailStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
pub enum AddCreditsNudgeEmailStatus {
|
||||
Sent,
|
||||
CooldownActive,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GetAccountParams {
|
||||
/// When `true`, requests a proactive token refresh before returning.
|
||||
///
|
||||
/// In managed auth mode this triggers the normal refresh-token flow. In
|
||||
/// external auth mode this flag is ignored. Clients should refresh tokens
|
||||
/// themselves and call `account/login/start` with `chatgptAuthTokens`.
|
||||
#[serde(default)]
|
||||
pub refresh_token: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GetAccountResponse {
|
||||
pub account: Option<Account>,
|
||||
pub requires_openai_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AccountUpdatedNotification {
|
||||
pub auth_mode: Option<AuthMode>,
|
||||
pub plan_type: Option<PlanType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AccountRateLimitsUpdatedNotification {
|
||||
pub rate_limits: RateLimitSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RateLimitSnapshot {
|
||||
pub limit_id: Option<String>,
|
||||
pub limit_name: Option<String>,
|
||||
pub primary: Option<RateLimitWindow>,
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
pub credits: Option<CreditsSnapshot>,
|
||||
pub plan_type: Option<PlanType>,
|
||||
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
}
|
||||
|
||||
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
|
||||
fn from(value: CoreRateLimitSnapshot) -> Self {
|
||||
Self {
|
||||
limit_id: value.limit_id,
|
||||
limit_name: value.limit_name,
|
||||
primary: value.primary.map(RateLimitWindow::from),
|
||||
secondary: value.secondary.map(RateLimitWindow::from),
|
||||
credits: value.credits.map(CreditsSnapshot::from),
|
||||
plan_type: value.plan_type,
|
||||
rate_limit_reached_type: value
|
||||
.rate_limit_reached_type
|
||||
.map(RateLimitReachedType::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
pub enum RateLimitReachedType {
|
||||
RateLimitReached,
|
||||
WorkspaceOwnerCreditsDepleted,
|
||||
WorkspaceMemberCreditsDepleted,
|
||||
WorkspaceOwnerUsageLimitReached,
|
||||
WorkspaceMemberUsageLimitReached,
|
||||
}
|
||||
|
||||
impl From<CoreRateLimitReachedType> for RateLimitReachedType {
|
||||
fn from(value: CoreRateLimitReachedType) -> Self {
|
||||
match value {
|
||||
CoreRateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
||||
CoreRateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
||||
Self::WorkspaceOwnerCreditsDepleted
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
||||
Self::WorkspaceMemberCreditsDepleted
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
||||
Self::WorkspaceOwnerUsageLimitReached
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
||||
Self::WorkspaceMemberUsageLimitReached
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RateLimitReachedType> for CoreRateLimitReachedType {
|
||||
fn from(value: RateLimitReachedType) -> Self {
|
||||
match value {
|
||||
RateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
||||
RateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
||||
Self::WorkspaceOwnerCreditsDepleted
|
||||
}
|
||||
RateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
||||
Self::WorkspaceMemberCreditsDepleted
|
||||
}
|
||||
RateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
||||
Self::WorkspaceOwnerUsageLimitReached
|
||||
}
|
||||
RateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
||||
Self::WorkspaceMemberUsageLimitReached
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RateLimitWindow {
|
||||
pub used_percent: i32,
|
||||
#[ts(type = "number | null")]
|
||||
pub window_duration_mins: Option<i64>,
|
||||
#[ts(type = "number | null")]
|
||||
pub resets_at: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<CoreRateLimitWindow> for RateLimitWindow {
|
||||
fn from(value: CoreRateLimitWindow) -> Self {
|
||||
Self {
|
||||
used_percent: value.used_percent.round() as i32,
|
||||
window_duration_mins: value.window_minutes,
|
||||
resets_at: value.resets_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CreditsSnapshot {
|
||||
pub has_credits: bool,
|
||||
pub unlimited: bool,
|
||||
pub balance: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CoreCreditsSnapshot> for CreditsSnapshot {
|
||||
fn from(value: CoreCreditsSnapshot) -> Self {
|
||||
Self {
|
||||
has_credits: value.has_credits,
|
||||
unlimited: value.unlimited,
|
||||
balance: value.balance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AccountLoginCompletedNotification {
|
||||
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
||||
// Convert to/from UUIDs at the application layer as needed.
|
||||
pub login_id: Option<String>,
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
146
codex-rs/app-server-protocol/src/protocol/v2/apps.rs
Normal file
146
codex-rs/app-server-protocol/src/protocol/v2/apps.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use super::shared::default_enabled;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - list available apps/connectors.
|
||||
pub struct AppsListParams {
|
||||
/// Opaque pagination cursor returned by a previous call.
|
||||
#[ts(optional = nullable)]
|
||||
pub cursor: Option<String>,
|
||||
/// Optional page size; defaults to a reasonable server-side value.
|
||||
#[ts(optional = nullable)]
|
||||
pub limit: Option<u32>,
|
||||
/// Optional thread id used to evaluate app feature gating from that thread's config.
|
||||
#[ts(optional = nullable)]
|
||||
pub thread_id: Option<String>,
|
||||
/// When true, bypass app caches and fetch the latest data from sources.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub force_refetch: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - app metadata returned by app-list APIs.
|
||||
pub struct AppBranding {
|
||||
pub category: Option<String>,
|
||||
pub developer: Option<String>,
|
||||
pub website: Option<String>,
|
||||
pub privacy_policy: Option<String>,
|
||||
pub terms_of_service: Option<String>,
|
||||
pub is_discoverable_app: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppReview {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppScreenshot {
|
||||
pub url: Option<String>,
|
||||
#[serde(alias = "file_id")]
|
||||
pub file_id: Option<String>,
|
||||
#[serde(alias = "user_prompt")]
|
||||
pub user_prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppMetadata {
|
||||
pub review: Option<AppReview>,
|
||||
pub categories: Option<Vec<String>>,
|
||||
pub sub_categories: Option<Vec<String>>,
|
||||
pub seo_description: Option<String>,
|
||||
pub screenshots: Option<Vec<AppScreenshot>>,
|
||||
pub developer: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub version_id: Option<String>,
|
||||
pub version_notes: Option<String>,
|
||||
pub first_party_type: Option<String>,
|
||||
pub first_party_requires_install: Option<bool>,
|
||||
pub show_in_composer_when_unlinked: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - app metadata returned by app-list APIs.
|
||||
pub struct AppInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub logo_url: Option<String>,
|
||||
pub logo_url_dark: Option<String>,
|
||||
pub distribution_channel: Option<String>,
|
||||
pub branding: Option<AppBranding>,
|
||||
pub app_metadata: Option<AppMetadata>,
|
||||
pub labels: Option<HashMap<String, String>>,
|
||||
pub install_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub is_accessible: bool,
|
||||
/// Whether this app is enabled in config.toml.
|
||||
/// Example:
|
||||
/// ```toml
|
||||
/// [apps.bad_app]
|
||||
/// enabled = false
|
||||
/// ```
|
||||
#[serde(default = "default_enabled")]
|
||||
pub is_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub plugin_display_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - app metadata summary for plugin responses.
|
||||
pub struct AppSummary {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub install_url: Option<String>,
|
||||
pub needs_auth: bool,
|
||||
}
|
||||
|
||||
impl From<AppInfo> for AppSummary {
|
||||
fn from(value: AppInfo) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
install_url: value.install_url,
|
||||
needs_auth: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - app list response.
|
||||
pub struct AppsListResponse {
|
||||
pub data: Vec<AppInfo>,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
/// If None, there are no more items to return.
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - notification emitted when the app list changes.
|
||||
pub struct AppListUpdatedNotification {
|
||||
pub data: Vec<AppInfo>,
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use codex_protocol::config_types::CollaborationModeMask as CoreCollaborationModeMask;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// EXPERIMENTAL - list collaboration mode presets.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CollaborationModeListParams {}
|
||||
|
||||
/// EXPERIMENTAL - collaboration mode preset metadata for clients.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CollaborationModeMask {
|
||||
pub name: String,
|
||||
pub mode: Option<ModeKind>,
|
||||
pub model: Option<String>,
|
||||
#[serde(rename = "reasoning_effort")]
|
||||
#[ts(rename = "reasoning_effort")]
|
||||
pub reasoning_effort: Option<Option<ReasoningEffort>>,
|
||||
}
|
||||
|
||||
impl From<CoreCollaborationModeMask> for CollaborationModeMask {
|
||||
fn from(value: CoreCollaborationModeMask) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
mode: value.mode,
|
||||
model: value.model,
|
||||
reasoning_effort: value.reasoning_effort,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - collaboration mode presets response.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CollaborationModeListResponse {
|
||||
pub data: Vec<CollaborationModeMask>,
|
||||
}
|
||||
214
codex-rs/app-server-protocol/src/protocol/v2/command_exec.rs
Normal file
214
codex-rs/app-server-protocol/src/protocol/v2/command_exec.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use super::PermissionProfile;
|
||||
use super::SandboxPolicy;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// PTY size in character cells for `command/exec` PTY sessions.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecTerminalSize {
|
||||
/// Terminal height in character cells.
|
||||
pub rows: u16,
|
||||
/// Terminal width in character cells.
|
||||
pub cols: u16,
|
||||
}
|
||||
|
||||
/// Run a standalone command (argv vector) in the server sandbox without
|
||||
/// creating a thread or turn.
|
||||
///
|
||||
/// The final `command/exec` response is deferred until the process exits and is
|
||||
/// sent only after all `command/exec/outputDelta` notifications for that
|
||||
/// connection have been emitted.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecParams {
|
||||
/// Command argv vector. Empty arrays are rejected.
|
||||
pub command: Vec<String>,
|
||||
/// Optional client-supplied, connection-scoped process id.
|
||||
///
|
||||
/// Required for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up
|
||||
/// `command/exec/write`, `command/exec/resize`, and
|
||||
/// `command/exec/terminate` calls. When omitted, buffered execution gets an
|
||||
/// internal id that is not exposed to the client.
|
||||
#[ts(optional = nullable)]
|
||||
pub process_id: Option<String>,
|
||||
/// Enable PTY mode.
|
||||
///
|
||||
/// This implies `streamStdin` and `streamStdoutStderr`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub tty: bool,
|
||||
/// Allow follow-up `command/exec/write` requests to write stdin bytes.
|
||||
///
|
||||
/// Requires a client-supplied `processId`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub stream_stdin: bool,
|
||||
/// Stream stdout/stderr via `command/exec/outputDelta` notifications.
|
||||
///
|
||||
/// Streamed bytes are not duplicated into the final response and require a
|
||||
/// client-supplied `processId`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub stream_stdout_stderr: bool,
|
||||
/// Optional per-stream stdout/stderr capture cap in bytes.
|
||||
///
|
||||
/// When omitted, the server default applies. Cannot be combined with
|
||||
/// `disableOutputCap`.
|
||||
#[ts(type = "number | null")]
|
||||
#[ts(optional = nullable)]
|
||||
pub output_bytes_cap: Option<usize>,
|
||||
/// Disable stdout/stderr capture truncation for this request.
|
||||
///
|
||||
/// Cannot be combined with `outputBytesCap`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub disable_output_cap: bool,
|
||||
/// Disable the timeout entirely for this request.
|
||||
///
|
||||
/// Cannot be combined with `timeoutMs`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub disable_timeout: bool,
|
||||
/// Optional timeout in milliseconds.
|
||||
///
|
||||
/// When omitted, the server default applies. Cannot be combined with
|
||||
/// `disableTimeout`.
|
||||
#[ts(type = "number | null")]
|
||||
#[ts(optional = nullable)]
|
||||
pub timeout_ms: Option<i64>,
|
||||
/// Optional working directory. Defaults to the server cwd.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Optional environment overrides merged into the server-computed
|
||||
/// environment.
|
||||
///
|
||||
/// Matching names override inherited values. Set a key to `null` to unset
|
||||
/// an inherited variable.
|
||||
#[ts(optional = nullable)]
|
||||
pub env: Option<HashMap<String, Option<String>>>,
|
||||
/// Optional initial PTY size in character cells. Only valid when `tty` is
|
||||
/// true.
|
||||
#[ts(optional = nullable)]
|
||||
pub size: Option<CommandExecTerminalSize>,
|
||||
/// Optional sandbox policy for this command.
|
||||
///
|
||||
/// Uses the same shape as thread/turn execution sandbox configuration and
|
||||
/// defaults to the user's configured policy when omitted. Cannot be
|
||||
/// combined with `permissionProfile`.
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
/// Optional full permissions profile for this command.
|
||||
///
|
||||
/// Defaults to the user's configured permissions when omitted. Cannot be
|
||||
/// combined with `sandboxPolicy`.
|
||||
#[experimental("command/exec.permissionProfile")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
}
|
||||
|
||||
/// Final buffered result for `command/exec`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecResponse {
|
||||
/// Process exit code.
|
||||
pub exit_code: i32,
|
||||
/// Buffered stdout capture.
|
||||
///
|
||||
/// Empty when stdout was streamed via `command/exec/outputDelta`.
|
||||
pub stdout: String,
|
||||
/// Buffered stderr capture.
|
||||
///
|
||||
/// Empty when stderr was streamed via `command/exec/outputDelta`.
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
/// Write stdin bytes to a running `command/exec` session, close stdin, or
|
||||
/// both.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecWriteParams {
|
||||
/// Client-supplied, connection-scoped `processId` from the original
|
||||
/// `command/exec` request.
|
||||
pub process_id: String,
|
||||
/// Optional base64-encoded stdin bytes to write.
|
||||
#[ts(optional = nullable)]
|
||||
pub delta_base64: Option<String>,
|
||||
/// Close stdin after writing `deltaBase64`, if present.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub close_stdin: bool,
|
||||
}
|
||||
|
||||
/// Empty success response for `command/exec/write`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecWriteResponse {}
|
||||
|
||||
/// Terminate a running `command/exec` session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecTerminateParams {
|
||||
/// Client-supplied, connection-scoped `processId` from the original
|
||||
/// `command/exec` request.
|
||||
pub process_id: String,
|
||||
}
|
||||
|
||||
/// Empty success response for `command/exec/terminate`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecTerminateResponse {}
|
||||
|
||||
/// Resize a running PTY-backed `command/exec` session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecResizeParams {
|
||||
/// Client-supplied, connection-scoped `processId` from the original
|
||||
/// `command/exec` request.
|
||||
pub process_id: String,
|
||||
/// New PTY size in character cells.
|
||||
pub size: CommandExecTerminalSize,
|
||||
}
|
||||
|
||||
/// Empty success response for `command/exec/resize`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecResizeResponse {}
|
||||
|
||||
/// Stream label for `command/exec/outputDelta` notifications.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CommandExecOutputStream {
|
||||
/// stdout stream. PTY mode multiplexes terminal output here.
|
||||
Stdout,
|
||||
/// stderr stream.
|
||||
Stderr,
|
||||
}
|
||||
/// Base64-encoded output chunk emitted for a streaming `command/exec` request.
|
||||
///
|
||||
/// These notifications are connection-scoped. If the originating connection
|
||||
/// closes, the server terminates the process.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandExecOutputDeltaNotification {
|
||||
/// Client-supplied, connection-scoped `processId` from the original
|
||||
/// `command/exec` request.
|
||||
pub process_id: String,
|
||||
/// Output stream for this chunk.
|
||||
pub stream: CommandExecOutputStream,
|
||||
/// Base64-encoded output bytes.
|
||||
pub delta_base64: String,
|
||||
/// `true` on the final streamed chunk for a stream when `outputBytesCap`
|
||||
/// truncated later output on that stream.
|
||||
pub cap_reached: bool,
|
||||
}
|
||||
703
codex-rs/app-server-protocol/src/protocol/v2/config.rs
Normal file
703
codex-rs/app-server-protocol/src/protocol/v2/config.rs
Normal file
@@ -0,0 +1,703 @@
|
||||
use super::ApprovalsReviewer;
|
||||
use super::AskForApproval;
|
||||
use super::SandboxMode;
|
||||
use super::shared::default_enabled;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WebSearchToolConfig;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ConfigLayerSource {
|
||||
/// Managed preferences layer delivered by MDM (macOS only).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Mdm {
|
||||
domain: String,
|
||||
key: String,
|
||||
},
|
||||
|
||||
/// Managed config layer from a file (usually `managed_config.toml`).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
System {
|
||||
/// This is the path to the system config.toml file, though it is not
|
||||
/// guaranteed to exist.
|
||||
file: AbsolutePathBuf,
|
||||
},
|
||||
|
||||
/// User config layer from $CODEX_HOME/config.toml. This layer is special
|
||||
/// in that it is expected to be:
|
||||
/// - writable by the user
|
||||
/// - generally outside the workspace directory
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
User {
|
||||
/// This is the path to the user's config.toml file, though it is not
|
||||
/// guaranteed to exist.
|
||||
file: AbsolutePathBuf,
|
||||
},
|
||||
|
||||
/// Path to a .codex/ folder within a project. There could be multiple of
|
||||
/// these between `cwd` and the project/repo root.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Project {
|
||||
dot_codex_folder: AbsolutePathBuf,
|
||||
},
|
||||
|
||||
/// Session-layer overrides supplied via `-c`/`--config`.
|
||||
SessionFlags,
|
||||
|
||||
/// `managed_config.toml` was designed to be a config that was loaded
|
||||
/// as the last layer on top of everything else. This scheme did not quite
|
||||
/// work out as intended, but we keep this variant as a "best effort" while
|
||||
/// we phase out `managed_config.toml` in favor of `requirements.toml`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
LegacyManagedConfigTomlFromFile {
|
||||
file: AbsolutePathBuf,
|
||||
},
|
||||
|
||||
LegacyManagedConfigTomlFromMdm,
|
||||
}
|
||||
|
||||
impl ConfigLayerSource {
|
||||
/// A settings from a layer with a higher precedence will override a setting
|
||||
/// from a layer with a lower precedence.
|
||||
pub fn precedence(&self) -> i16 {
|
||||
match self {
|
||||
ConfigLayerSource::Mdm { .. } => 0,
|
||||
ConfigLayerSource::System { .. } => 10,
|
||||
ConfigLayerSource::User { .. } => 20,
|
||||
ConfigLayerSource::Project { .. } => 25,
|
||||
ConfigLayerSource::SessionFlags => 30,
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => 40,
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares [ConfigLayerSource] by precedence, so `A < B` means settings from
|
||||
/// layer `A` will be overridden by settings from layer `B`.
|
||||
impl PartialOrd for ConfigLayerSource {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.precedence().cmp(&other.precedence()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SandboxWorkspaceWrite {
|
||||
#[serde(default)]
|
||||
pub writable_roots: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub network_access: bool,
|
||||
#[serde(default)]
|
||||
pub exclude_tmpdir_env_var: bool,
|
||||
#[serde(default)]
|
||||
pub exclude_slash_tmp: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ToolsV2 {
|
||||
pub web_search: Option<WebSearchToolConfig>,
|
||||
pub view_image: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProfileV2 {
|
||||
pub model: Option<String>,
|
||||
pub model_provider: Option<String>,
|
||||
#[experimental(nested)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
/// [UNSTABLE] Optional profile-level override for where approval requests
|
||||
/// are routed for review. If omitted, the enclosing config default is
|
||||
/// used.
|
||||
#[experimental("config/read.approvalsReviewer")]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub service_tier: Option<ServiceTier>,
|
||||
pub model_reasoning_effort: Option<ReasoningEffort>,
|
||||
pub model_reasoning_summary: Option<ReasoningSummary>,
|
||||
pub model_verbosity: Option<Verbosity>,
|
||||
pub web_search: Option<WebSearchMode>,
|
||||
pub tools: Option<ToolsV2>,
|
||||
pub chatgpt_base_url: Option<String>,
|
||||
#[serde(default, flatten)]
|
||||
pub additional: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AnalyticsConfig {
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(default, flatten)]
|
||||
pub additional: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum AppToolApproval {
|
||||
Auto,
|
||||
Prompt,
|
||||
Approve,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppsDefaultConfig {
|
||||
#[serde(default = "default_enabled")]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_enabled")]
|
||||
pub destructive_enabled: bool,
|
||||
#[serde(default = "default_enabled")]
|
||||
pub open_world_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppToolConfig {
|
||||
pub enabled: Option<bool>,
|
||||
pub approval_mode: Option<AppToolApproval>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppToolsConfig {
|
||||
#[serde(default, flatten)]
|
||||
pub tools: HashMap<String, AppToolConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppConfig {
|
||||
#[serde(default = "default_enabled")]
|
||||
pub enabled: bool,
|
||||
pub destructive_enabled: Option<bool>,
|
||||
pub open_world_enabled: Option<bool>,
|
||||
pub default_tools_approval_mode: Option<AppToolApproval>,
|
||||
pub default_tools_enabled: Option<bool>,
|
||||
pub tools: Option<AppToolsConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AppsConfig {
|
||||
#[serde(default, rename = "_default")]
|
||||
pub default: Option<AppsDefaultConfig>,
|
||||
#[serde(default, flatten)]
|
||||
pub apps: HashMap<String, AppConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Config {
|
||||
pub model: Option<String>,
|
||||
pub review_model: Option<String>,
|
||||
pub model_context_window: Option<i64>,
|
||||
pub model_auto_compact_token_limit: Option<i64>,
|
||||
pub model_provider: Option<String>,
|
||||
#[experimental(nested)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
/// [UNSTABLE] Optional default for where approval requests are routed for
|
||||
/// review.
|
||||
#[experimental("config/read.approvalsReviewer")]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_mode: Option<SandboxMode>,
|
||||
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
|
||||
pub forced_chatgpt_workspace_id: Option<String>,
|
||||
pub forced_login_method: Option<ForcedLoginMethod>,
|
||||
pub web_search: Option<WebSearchMode>,
|
||||
pub tools: Option<ToolsV2>,
|
||||
pub profile: Option<String>,
|
||||
#[experimental(nested)]
|
||||
#[serde(default)]
|
||||
pub profiles: HashMap<String, ProfileV2>,
|
||||
pub instructions: Option<String>,
|
||||
pub developer_instructions: Option<String>,
|
||||
pub compact_prompt: Option<String>,
|
||||
pub model_reasoning_effort: Option<ReasoningEffort>,
|
||||
pub model_reasoning_summary: Option<ReasoningSummary>,
|
||||
pub model_verbosity: Option<Verbosity>,
|
||||
pub service_tier: Option<ServiceTier>,
|
||||
pub analytics: Option<AnalyticsConfig>,
|
||||
#[experimental("config/read.apps")]
|
||||
#[serde(default)]
|
||||
pub apps: Option<AppsConfig>,
|
||||
#[serde(default, flatten)]
|
||||
pub additional: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigLayerMetadata {
|
||||
pub name: ConfigLayerSource,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigLayer {
|
||||
pub name: ConfigLayerSource,
|
||||
pub version: String,
|
||||
pub config: JsonValue,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub disabled_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum MergeStrategy {
|
||||
Replace,
|
||||
Upsert,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum WriteStatus {
|
||||
Ok,
|
||||
OkOverridden,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct OverriddenMetadata {
|
||||
pub message: String,
|
||||
pub overriding_layer: ConfigLayerMetadata,
|
||||
pub effective_value: JsonValue,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigWriteResponse {
|
||||
pub status: WriteStatus,
|
||||
pub version: String,
|
||||
/// Canonical path to the config file that was written.
|
||||
pub file_path: AbsolutePathBuf,
|
||||
pub overridden_metadata: Option<OverriddenMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ConfigWriteErrorCode {
|
||||
ConfigLayerReadonly,
|
||||
ConfigVersionConflict,
|
||||
ConfigValidationError,
|
||||
ConfigPathNotFound,
|
||||
ConfigSchemaUnknownKey,
|
||||
UserLayerNotFound,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigReadParams {
|
||||
#[serde(default)]
|
||||
pub include_layers: bool,
|
||||
/// Optional working directory to resolve project config layers. If specified,
|
||||
/// return the effective config as seen from that directory (i.e., including any
|
||||
/// project layers between `cwd` and the project/repo root).
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigReadResponse {
|
||||
#[experimental(nested)]
|
||||
pub config: Config,
|
||||
pub origins: HashMap<String, ConfigLayerMetadata>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub layers: Option<Vec<ConfigLayer>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigRequirements {
|
||||
#[experimental(nested)]
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
#[experimental("configRequirements/read.allowedApprovalsReviewers")]
|
||||
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
|
||||
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
|
||||
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
|
||||
pub feature_requirements: Option<BTreeMap<String, bool>>,
|
||||
#[experimental("configRequirements/read.hooks")]
|
||||
pub hooks: Option<ManagedHooksRequirements>,
|
||||
pub enforce_residency: Option<ResidencyRequirement>,
|
||||
#[experimental("configRequirements/read.network")]
|
||||
pub network: Option<NetworkRequirements>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ManagedHooksRequirements {
|
||||
pub managed_dir: Option<PathBuf>,
|
||||
pub windows_managed_dir: Option<PathBuf>,
|
||||
#[serde(rename = "PreToolUse")]
|
||||
#[ts(rename = "PreToolUse")]
|
||||
pub pre_tool_use: Vec<ConfiguredHookMatcherGroup>,
|
||||
#[serde(rename = "PermissionRequest")]
|
||||
#[ts(rename = "PermissionRequest")]
|
||||
pub permission_request: Vec<ConfiguredHookMatcherGroup>,
|
||||
#[serde(rename = "PostToolUse")]
|
||||
#[ts(rename = "PostToolUse")]
|
||||
pub post_tool_use: Vec<ConfiguredHookMatcherGroup>,
|
||||
#[serde(rename = "SessionStart")]
|
||||
#[ts(rename = "SessionStart")]
|
||||
pub session_start: Vec<ConfiguredHookMatcherGroup>,
|
||||
#[serde(rename = "UserPromptSubmit")]
|
||||
#[ts(rename = "UserPromptSubmit")]
|
||||
pub user_prompt_submit: Vec<ConfiguredHookMatcherGroup>,
|
||||
#[serde(rename = "Stop")]
|
||||
#[ts(rename = "Stop")]
|
||||
pub stop: Vec<ConfiguredHookMatcherGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfiguredHookMatcherGroup {
|
||||
pub matcher: Option<String>,
|
||||
pub hooks: Vec<ConfiguredHookHandler>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type")]
|
||||
#[ts(tag = "type", export_to = "v2/")]
|
||||
pub enum ConfiguredHookHandler {
|
||||
#[serde(rename = "command")]
|
||||
#[ts(rename = "command")]
|
||||
Command {
|
||||
command: String,
|
||||
#[serde(rename = "timeoutSec")]
|
||||
#[ts(rename = "timeoutSec")]
|
||||
timeout_sec: Option<u64>,
|
||||
r#async: bool,
|
||||
#[serde(rename = "statusMessage")]
|
||||
#[ts(rename = "statusMessage")]
|
||||
status_message: Option<String>,
|
||||
},
|
||||
#[serde(rename = "prompt")]
|
||||
#[ts(rename = "prompt")]
|
||||
Prompt {},
|
||||
#[serde(rename = "agent")]
|
||||
#[ts(rename = "agent")]
|
||||
Agent {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct NetworkRequirements {
|
||||
pub enabled: Option<bool>,
|
||||
pub http_port: Option<u16>,
|
||||
pub socks_port: Option<u16>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
/// Canonical network permission map for `experimental_network`.
|
||||
pub domains: Option<BTreeMap<String, NetworkDomainPermission>>,
|
||||
/// When true, only managed allowlist entries are respected while managed
|
||||
/// network enforcement is active.
|
||||
pub managed_allowed_domains_only: Option<bool>,
|
||||
/// Legacy compatibility view derived from `domains`.
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
/// Legacy compatibility view derived from `domains`.
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
/// Canonical unix socket permission map for `experimental_network`.
|
||||
pub unix_sockets: Option<BTreeMap<String, NetworkUnixSocketPermission>>,
|
||||
/// Legacy compatibility view derived from `unix_sockets`.
|
||||
pub allow_unix_sockets: Option<Vec<String>>,
|
||||
pub allow_local_binding: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum NetworkDomainPermission {
|
||||
Allow,
|
||||
Deny,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum NetworkUnixSocketPermission {
|
||||
Allow,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ResidencyRequirement {
|
||||
Us,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigRequirementsReadResponse {
|
||||
/// Null if no requirements are configured (e.g. no requirements.toml/MDM entries).
|
||||
#[experimental(nested)]
|
||||
pub requirements: Option<ConfigRequirements>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ExternalAgentConfigMigrationItemType {
|
||||
#[serde(rename = "AGENTS_MD")]
|
||||
#[ts(rename = "AGENTS_MD")]
|
||||
AgentsMd,
|
||||
#[serde(rename = "CONFIG")]
|
||||
#[ts(rename = "CONFIG")]
|
||||
Config,
|
||||
#[serde(rename = "SKILLS")]
|
||||
#[ts(rename = "SKILLS")]
|
||||
Skills,
|
||||
#[serde(rename = "PLUGINS")]
|
||||
#[ts(rename = "PLUGINS")]
|
||||
Plugins,
|
||||
#[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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginsMigration {
|
||||
#[serde(rename = "marketplaceName")]
|
||||
#[ts(rename = "marketplaceName")]
|
||||
pub marketplace_name: String,
|
||||
#[serde(rename = "pluginNames")]
|
||||
#[ts(rename = "pluginNames")]
|
||||
pub plugin_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigMigrationItem {
|
||||
pub item_type: ExternalAgentConfigMigrationItemType,
|
||||
pub description: String,
|
||||
/// Null or empty means home-scoped migration; non-empty means repo-scoped migration.
|
||||
pub cwd: Option<PathBuf>,
|
||||
pub details: Option<MigrationDetails>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigDetectResponse {
|
||||
pub items: Vec<ExternalAgentConfigMigrationItem>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigDetectParams {
|
||||
/// If true, include detection under the user's home (~/.claude, ~/.codex, etc.).
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub include_home: bool,
|
||||
/// Zero or more working directories to include for repo-scoped detection.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwds: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigImportParams {
|
||||
pub migration_items: Vec<ExternalAgentConfigMigrationItem>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigImportResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExternalAgentConfigImportCompletedNotification {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigValueWriteParams {
|
||||
pub key_path: String,
|
||||
pub value: JsonValue,
|
||||
pub merge_strategy: MergeStrategy,
|
||||
/// Path to the config file to write; defaults to the user's `config.toml` when omitted.
|
||||
#[ts(optional = nullable)]
|
||||
pub file_path: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub expected_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigBatchWriteParams {
|
||||
pub edits: Vec<ConfigEdit>,
|
||||
/// Path to the config file to write; defaults to the user's `config.toml` when omitted.
|
||||
#[ts(optional = nullable)]
|
||||
pub file_path: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub expected_version: Option<String>,
|
||||
/// When true, hot-reload the updated user config into all loaded threads after writing.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub reload_user_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigEdit {
|
||||
pub key_path: String,
|
||||
pub value: JsonValue,
|
||||
pub merge_strategy: MergeStrategy,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TextPosition {
|
||||
/// 1-based line number.
|
||||
pub line: usize,
|
||||
/// 1-based column number (in Unicode scalar values).
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TextRange {
|
||||
pub start: TextPosition,
|
||||
pub end: TextPosition,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigWarningNotification {
|
||||
/// Concise summary of the warning.
|
||||
pub summary: String,
|
||||
/// Optional extra guidance or error details.
|
||||
pub details: Option<String>,
|
||||
/// Optional path to the config file that triggered the warning.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub path: Option<String>,
|
||||
/// Optional range for the error location inside the config file.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub range: Option<TextRange>,
|
||||
}
|
||||
181
codex-rs/app-server-protocol/src/protocol/v2/device_key.rs
Normal file
181
codex-rs/app-server-protocol/src/protocol/v2/device_key.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// Device-key algorithm reported at enrollment and signing boundaries.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum DeviceKeyAlgorithm {
|
||||
EcdsaP256Sha256,
|
||||
}
|
||||
|
||||
/// Platform protection class for a controller-local device key.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum DeviceKeyProtectionClass {
|
||||
HardwareSecureEnclave,
|
||||
HardwareTpm,
|
||||
OsProtectedNonextractable,
|
||||
}
|
||||
|
||||
/// Protection policy for creating or loading a controller-local device key.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum DeviceKeyProtectionPolicy {
|
||||
HardwareOnly,
|
||||
AllowOsProtectedNonextractable,
|
||||
}
|
||||
|
||||
/// Create a controller-local device key with a random key id.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeyCreateParams {
|
||||
/// Defaults to `hardware_only` when omitted.
|
||||
#[ts(optional = nullable)]
|
||||
pub protection_policy: Option<DeviceKeyProtectionPolicy>,
|
||||
pub account_user_id: String,
|
||||
pub client_id: String,
|
||||
}
|
||||
|
||||
/// Device-key metadata and public key returned by create/public APIs.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeyCreateResponse {
|
||||
pub key_id: String,
|
||||
/// SubjectPublicKeyInfo DER encoded as base64.
|
||||
pub public_key_spki_der_base64: String,
|
||||
pub algorithm: DeviceKeyAlgorithm,
|
||||
pub protection_class: DeviceKeyProtectionClass,
|
||||
}
|
||||
|
||||
/// Fetch a controller-local device key public key by id.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeyPublicParams {
|
||||
pub key_id: String,
|
||||
}
|
||||
|
||||
/// Device-key public metadata returned by `device/key/public`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeyPublicResponse {
|
||||
pub key_id: String,
|
||||
/// SubjectPublicKeyInfo DER encoded as base64.
|
||||
pub public_key_spki_der_base64: String,
|
||||
pub algorithm: DeviceKeyAlgorithm,
|
||||
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")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum RemoteControlClientConnectionAudience {
|
||||
RemoteControlClientWebsocket,
|
||||
}
|
||||
|
||||
/// Audience for a remote-control client enrollment device-key proof.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum RemoteControlClientEnrollmentAudience {
|
||||
RemoteControlClientEnrollment,
|
||||
}
|
||||
|
||||
/// Structured payloads accepted by `device/key/sign`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type", export_to = "v2/")]
|
||||
pub enum DeviceKeySignPayload {
|
||||
/// Payload bound to one remote-control controller websocket `/client` connection challenge.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
RemoteControlClientConnection {
|
||||
nonce: String,
|
||||
audience: RemoteControlClientConnectionAudience,
|
||||
/// Backend-issued websocket session id that this proof authorizes.
|
||||
session_id: String,
|
||||
/// Origin of the backend endpoint that issued the challenge and will verify this proof.
|
||||
target_origin: String,
|
||||
/// Websocket route path that this proof authorizes.
|
||||
target_path: String,
|
||||
account_user_id: String,
|
||||
client_id: String,
|
||||
/// Remote-control token expiration as Unix seconds.
|
||||
#[ts(type = "number")]
|
||||
token_expires_at: i64,
|
||||
/// SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.
|
||||
token_sha256_base64url: String,
|
||||
/// Must contain exactly `remote_control_controller_websocket`.
|
||||
scopes: Vec<String>,
|
||||
},
|
||||
/// Payload bound to a remote-control client `/client/enroll` ownership challenge.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
RemoteControlClientEnrollment {
|
||||
nonce: String,
|
||||
audience: RemoteControlClientEnrollmentAudience,
|
||||
/// Backend-issued enrollment challenge id that this proof authorizes.
|
||||
challenge_id: String,
|
||||
/// Origin of the backend endpoint that issued the challenge and will verify this proof.
|
||||
target_origin: String,
|
||||
/// HTTP route path that this proof authorizes.
|
||||
target_path: String,
|
||||
account_user_id: String,
|
||||
client_id: String,
|
||||
/// SHA-256 of the requested device identity operation, encoded as unpadded base64url.
|
||||
device_identity_sha256_base64url: String,
|
||||
/// Enrollment challenge expiration as Unix seconds.
|
||||
#[ts(type = "number")]
|
||||
challenge_expires_at: i64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Sign an accepted structured payload with a controller-local device key.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeySignParams {
|
||||
pub key_id: String,
|
||||
pub payload: DeviceKeySignPayload,
|
||||
}
|
||||
|
||||
/// ASN.1 DER signature returned by `device/key/sign`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeviceKeySignResponse {
|
||||
/// ECDSA signature DER encoded as base64.
|
||||
pub signature_der_base64: String,
|
||||
/// Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte
|
||||
/// string directly and must not reserialize `payload`.
|
||||
pub signed_payload_base64: String,
|
||||
pub algorithm: DeviceKeyAlgorithm,
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeatureListParams {
|
||||
/// Opaque pagination cursor returned by a previous call.
|
||||
#[ts(optional = nullable)]
|
||||
pub cursor: Option<String>,
|
||||
/// Optional page size; defaults to a reasonable server-side value.
|
||||
#[ts(optional = nullable)]
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ExperimentalFeatureStage {
|
||||
/// Feature is available for user testing and feedback.
|
||||
Beta,
|
||||
/// Feature is still being built and not ready for broad use.
|
||||
UnderDevelopment,
|
||||
/// Feature is production-ready.
|
||||
Stable,
|
||||
/// Feature is deprecated and should be avoided.
|
||||
Deprecated,
|
||||
/// Feature flag is retained only for backwards compatibility.
|
||||
Removed,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeature {
|
||||
/// Stable key used in config.toml and CLI flag toggles.
|
||||
pub name: String,
|
||||
/// Lifecycle stage of this feature flag.
|
||||
pub stage: ExperimentalFeatureStage,
|
||||
/// User-facing display name shown in the experimental features UI.
|
||||
/// Null when this feature is not in beta.
|
||||
pub display_name: Option<String>,
|
||||
/// Short summary describing what the feature does.
|
||||
/// Null when this feature is not in beta.
|
||||
pub description: Option<String>,
|
||||
/// Announcement copy shown to users when the feature is introduced.
|
||||
/// Null when this feature is not in beta.
|
||||
pub announcement: Option<String>,
|
||||
/// Whether this feature is currently enabled in the loaded config.
|
||||
pub enabled: bool,
|
||||
/// Whether this feature is enabled by default.
|
||||
pub default_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeatureListResponse {
|
||||
pub data: Vec<ExperimentalFeature>,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
/// If None, there are no more items to return.
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeatureEnablementSetParams {
|
||||
/// Process-wide runtime feature enablement keyed by canonical feature name.
|
||||
///
|
||||
/// Only named features are updated. Omitted features are left unchanged.
|
||||
/// Send an empty map for a no-op.
|
||||
pub enablement: BTreeMap<String, bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ExperimentalFeatureEnablementSetResponse {
|
||||
/// Feature enablement entries updated by this request.
|
||||
pub enablement: BTreeMap<String, bool>,
|
||||
}
|
||||
29
codex-rs/app-server-protocol/src/protocol/v2/feedback.rs
Normal file
29
codex-rs/app-server-protocol/src/protocol/v2/feedback.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FeedbackUploadParams {
|
||||
pub classification: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub reason: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub thread_id: Option<String>,
|
||||
pub include_logs: bool,
|
||||
#[ts(optional = nullable)]
|
||||
pub extra_log_files: Option<Vec<PathBuf>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub tags: Option<BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FeedbackUploadResponse {
|
||||
pub thread_id: String,
|
||||
}
|
||||
204
codex-rs/app-server-protocol/src/protocol/v2/fs.rs
Normal file
204
codex-rs/app-server-protocol/src/protocol/v2/fs.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// Read a file from the host filesystem.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsReadFileParams {
|
||||
/// Absolute path to read.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// Base64-encoded file contents returned by `fs/readFile`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsReadFileResponse {
|
||||
/// File contents encoded as base64.
|
||||
pub data_base64: String,
|
||||
}
|
||||
|
||||
/// Write a file on the host filesystem.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsWriteFileParams {
|
||||
/// Absolute path to write.
|
||||
pub path: AbsolutePathBuf,
|
||||
/// File contents encoded as base64.
|
||||
pub data_base64: String,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/writeFile`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsWriteFileResponse {}
|
||||
|
||||
/// Create a directory on the host filesystem.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsCreateDirectoryParams {
|
||||
/// Absolute directory path to create.
|
||||
pub path: AbsolutePathBuf,
|
||||
/// Whether parent directories should also be created. Defaults to `true`.
|
||||
#[ts(optional = nullable)]
|
||||
pub recursive: Option<bool>,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/createDirectory`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsCreateDirectoryResponse {}
|
||||
|
||||
/// Request metadata for an absolute path.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsGetMetadataParams {
|
||||
/// Absolute path to inspect.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// Metadata returned by `fs/getMetadata`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsGetMetadataResponse {
|
||||
/// Whether the path resolves to a directory.
|
||||
pub is_directory: bool,
|
||||
/// Whether the path resolves to a regular file.
|
||||
pub is_file: bool,
|
||||
/// Whether the path itself is a symbolic link.
|
||||
pub is_symlink: bool,
|
||||
/// File creation time in Unix milliseconds when available, otherwise `0`.
|
||||
#[ts(type = "number")]
|
||||
pub created_at_ms: i64,
|
||||
/// File modification time in Unix milliseconds when available, otherwise `0`.
|
||||
#[ts(type = "number")]
|
||||
pub modified_at_ms: i64,
|
||||
}
|
||||
|
||||
/// List direct child names for a directory.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsReadDirectoryParams {
|
||||
/// Absolute directory path to read.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// A directory entry returned by `fs/readDirectory`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsReadDirectoryEntry {
|
||||
/// Direct child entry name only, not an absolute or relative path.
|
||||
pub file_name: String,
|
||||
/// Whether this entry resolves to a directory.
|
||||
pub is_directory: bool,
|
||||
/// Whether this entry resolves to a regular file.
|
||||
pub is_file: bool,
|
||||
}
|
||||
|
||||
/// Directory entries returned by `fs/readDirectory`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsReadDirectoryResponse {
|
||||
/// Direct child entries in the requested directory.
|
||||
pub entries: Vec<FsReadDirectoryEntry>,
|
||||
}
|
||||
|
||||
/// Remove a file or directory tree from the host filesystem.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsRemoveParams {
|
||||
/// Absolute path to remove.
|
||||
pub path: AbsolutePathBuf,
|
||||
/// Whether directory removal should recurse. Defaults to `true`.
|
||||
#[ts(optional = nullable)]
|
||||
pub recursive: Option<bool>,
|
||||
/// Whether missing paths should be ignored. Defaults to `true`.
|
||||
#[ts(optional = nullable)]
|
||||
pub force: Option<bool>,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/remove`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsRemoveResponse {}
|
||||
|
||||
/// Copy a file or directory tree on the host filesystem.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsCopyParams {
|
||||
/// Absolute source path.
|
||||
pub source_path: AbsolutePathBuf,
|
||||
/// Absolute destination path.
|
||||
pub destination_path: AbsolutePathBuf,
|
||||
/// Required for directory copies; ignored for file copies.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub recursive: bool,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/copy`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsCopyResponse {}
|
||||
|
||||
/// Start filesystem watch notifications for an absolute path.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsWatchParams {
|
||||
/// Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
|
||||
pub watch_id: String,
|
||||
/// Absolute file or directory path to watch.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/watch`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsWatchResponse {
|
||||
/// Canonicalized path associated with the watch.
|
||||
pub path: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// Stop filesystem watch notifications for a prior `fs/watch`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsUnwatchParams {
|
||||
/// Watch identifier previously provided to `fs/watch`.
|
||||
pub watch_id: String,
|
||||
}
|
||||
|
||||
/// Successful response for `fs/unwatch`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsUnwatchResponse {}
|
||||
|
||||
/// Filesystem watch notification emitted for `fs/watch` subscribers.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FsChangedNotification {
|
||||
/// Watch identifier previously provided to `fs/watch`.
|
||||
pub watch_id: String,
|
||||
/// File or directory paths associated with this event.
|
||||
pub changed_paths: Vec<AbsolutePathBuf>,
|
||||
}
|
||||
154
codex-rs/app-server-protocol/src/protocol/v2/hook.rs
Normal file
154
codex-rs/app-server-protocol/src/protocol/v2/hook.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_protocol::protocol::HookEventName as CoreHookEventName;
|
||||
use codex_protocol::protocol::HookExecutionMode as CoreHookExecutionMode;
|
||||
use codex_protocol::protocol::HookHandlerType as CoreHookHandlerType;
|
||||
use codex_protocol::protocol::HookOutputEntry as CoreHookOutputEntry;
|
||||
use codex_protocol::protocol::HookOutputEntryKind as CoreHookOutputEntryKind;
|
||||
use codex_protocol::protocol::HookRunStatus as CoreHookRunStatus;
|
||||
use codex_protocol::protocol::HookRunSummary as CoreHookRunSummary;
|
||||
use codex_protocol::protocol::HookScope as CoreHookScope;
|
||||
use codex_protocol::protocol::HookSource as CoreHookSource;
|
||||
use codex_protocol::protocol::HookTrustStatus as CoreHookTrustStatus;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookEventName from CoreHookEventName {
|
||||
PreToolUse, PermissionRequest, PostToolUse, SessionStart, UserPromptSubmit, Stop
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookHandlerType from CoreHookHandlerType {
|
||||
Command, Prompt, Agent
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookExecutionMode from CoreHookExecutionMode {
|
||||
Sync, Async
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookScope from CoreHookScope {
|
||||
Thread, Turn
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookSource from CoreHookSource {
|
||||
System,
|
||||
User,
|
||||
Project,
|
||||
Mdm,
|
||||
SessionFlags,
|
||||
Plugin,
|
||||
CloudRequirements,
|
||||
LegacyManagedConfigFile,
|
||||
LegacyManagedConfigMdm,
|
||||
Unknown,
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookTrustStatus from CoreHookTrustStatus {
|
||||
Managed, Untrusted, Trusted, Modified
|
||||
}
|
||||
);
|
||||
|
||||
fn default_hook_source() -> HookSource {
|
||||
HookSource::Unknown
|
||||
}
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookRunStatus from CoreHookRunStatus {
|
||||
Running, Completed, Failed, Blocked, Stopped
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum HookOutputEntryKind from CoreHookOutputEntryKind {
|
||||
Warning, Stop, Feedback, Context, Error
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookOutputEntry {
|
||||
pub kind: HookOutputEntryKind,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl From<CoreHookOutputEntry> for HookOutputEntry {
|
||||
fn from(value: CoreHookOutputEntry) -> Self {
|
||||
Self {
|
||||
kind: value.kind.into(),
|
||||
text: value.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookRunSummary {
|
||||
pub id: String,
|
||||
pub event_name: HookEventName,
|
||||
pub handler_type: HookHandlerType,
|
||||
pub execution_mode: HookExecutionMode,
|
||||
pub scope: HookScope,
|
||||
pub source_path: AbsolutePathBuf,
|
||||
#[serde(default = "default_hook_source")]
|
||||
pub source: HookSource,
|
||||
pub display_order: i64,
|
||||
pub status: HookRunStatus,
|
||||
pub status_message: Option<String>,
|
||||
pub started_at: i64,
|
||||
pub completed_at: Option<i64>,
|
||||
pub duration_ms: Option<i64>,
|
||||
pub entries: Vec<HookOutputEntry>,
|
||||
}
|
||||
|
||||
impl From<CoreHookRunSummary> for HookRunSummary {
|
||||
fn from(value: CoreHookRunSummary) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
event_name: value.event_name.into(),
|
||||
handler_type: value.handler_type.into(),
|
||||
execution_mode: value.execution_mode.into(),
|
||||
scope: value.scope.into(),
|
||||
source_path: value.source_path,
|
||||
source: value.source.into(),
|
||||
display_order: value.display_order,
|
||||
status: value.status.into(),
|
||||
status_message: value.status_message,
|
||||
started_at: value.started_at,
|
||||
completed_at: value.completed_at,
|
||||
duration_ms: value.duration_ms,
|
||||
entries: value.entries.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookStartedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: Option<String>,
|
||||
pub run: HookRunSummary,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookCompletedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: Option<String>,
|
||||
pub run: HookRunSummary,
|
||||
}
|
||||
1432
codex-rs/app-server-protocol/src/protocol/v2/item.rs
Normal file
1432
codex-rs/app-server-protocol/src/protocol/v2/item.rs
Normal file
File diff suppressed because it is too large
Load Diff
703
codex-rs/app-server-protocol/src/protocol/v2/mcp.rs
Normal file
703
codex-rs/app-server-protocol/src/protocol/v2/mcp.rs
Normal file
@@ -0,0 +1,703 @@
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
|
||||
use codex_protocol::items::McpToolCallError as CoreMcpToolCallError;
|
||||
use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;
|
||||
use codex_protocol::mcp::Resource as McpResource;
|
||||
pub use codex_protocol::mcp::ResourceContent as McpResourceContent;
|
||||
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
|
||||
use codex_protocol::mcp::Tool as McpTool;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum McpAuthStatus from codex_protocol::protocol::McpAuthStatus {
|
||||
Unsupported,
|
||||
NotLoggedIn,
|
||||
BearerToken,
|
||||
OAuth
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ListMcpServerStatusParams {
|
||||
/// Opaque pagination cursor returned by a previous call.
|
||||
#[ts(optional = nullable)]
|
||||
pub cursor: Option<String>,
|
||||
/// Optional page size; defaults to a server-defined value.
|
||||
#[ts(optional = nullable)]
|
||||
pub limit: Option<u32>,
|
||||
/// Controls how much MCP inventory data to fetch for each server.
|
||||
/// Defaults to `Full` when omitted.
|
||||
#[ts(optional = nullable)]
|
||||
pub detail: Option<McpServerStatusDetail>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase", export_to = "v2/")]
|
||||
pub enum McpServerStatusDetail {
|
||||
Full,
|
||||
ToolsAndAuthOnly,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerStatus {
|
||||
pub name: String,
|
||||
pub tools: std::collections::HashMap<String, McpTool>,
|
||||
pub resources: Vec<McpResource>,
|
||||
pub resource_templates: Vec<McpResourceTemplate>,
|
||||
pub auth_status: McpAuthStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ListMcpServerStatusResponse {
|
||||
pub data: Vec<McpServerStatus>,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
/// If None, there are no more items to return.
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpResourceReadParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub thread_id: Option<String>,
|
||||
pub server: String,
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpResourceReadResponse {
|
||||
pub contents: Vec<McpResourceContent>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerToolCallParams {
|
||||
pub thread_id: String,
|
||||
pub server: String,
|
||||
pub tool: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub arguments: Option<JsonValue>,
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub meta: Option<JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerToolCallResponse {
|
||||
pub content: Vec<JsonValue>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub structured_content: Option<JsonValue>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub is_error: Option<bool>,
|
||||
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub meta: Option<JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpToolCallResult {
|
||||
// NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust
|
||||
// representation of MCP content blocks. We intentionally use `serde_json::Value` here because
|
||||
// this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types
|
||||
// aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust
|
||||
// representations). Using `JsonValue` keeps the payload wire-shaped and easy to export.
|
||||
pub content: Vec<JsonValue>,
|
||||
pub structured_content: Option<JsonValue>,
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
pub meta: Option<JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpToolCallError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl From<CoreMcpCallToolResult> for McpServerToolCallResponse {
|
||||
fn from(result: CoreMcpCallToolResult) -> Self {
|
||||
Self {
|
||||
content: result.content,
|
||||
structured_content: result.structured_content,
|
||||
is_error: result.is_error,
|
||||
meta: result.meta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreMcpCallToolResult> for McpToolCallResult {
|
||||
fn from(result: CoreMcpCallToolResult) -> Self {
|
||||
Self {
|
||||
content: result.content,
|
||||
structured_content: result.structured_content,
|
||||
meta: result.meta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreMcpToolCallError> for McpToolCallError {
|
||||
fn from(error: CoreMcpToolCallError) -> Self {
|
||||
Self {
|
||||
message: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerRefreshParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerRefreshResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerOauthLoginParams {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional = nullable)]
|
||||
pub scopes: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional = nullable)]
|
||||
pub timeout_secs: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerOauthLoginResponse {
|
||||
pub authorization_url: String,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpToolCallProgressNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub item_id: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerOauthLoginCompletedNotification {
|
||||
pub name: String,
|
||||
pub success: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerStartupState {
|
||||
Starting,
|
||||
Ready,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerStatusUpdatedNotification {
|
||||
pub name: String,
|
||||
pub status: McpServerStartupState,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerElicitationAction {
|
||||
Accept,
|
||||
Decline,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
impl McpServerElicitationAction {
|
||||
pub fn to_core(self) -> codex_protocol::approvals::ElicitationAction {
|
||||
match self {
|
||||
Self::Accept => codex_protocol::approvals::ElicitationAction::Accept,
|
||||
Self::Decline => codex_protocol::approvals::ElicitationAction::Decline,
|
||||
Self::Cancel => codex_protocol::approvals::ElicitationAction::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<McpServerElicitationAction> for rmcp::model::ElicitationAction {
|
||||
fn from(value: McpServerElicitationAction) -> Self {
|
||||
match value {
|
||||
McpServerElicitationAction::Accept => Self::Accept,
|
||||
McpServerElicitationAction::Decline => Self::Decline,
|
||||
McpServerElicitationAction::Cancel => Self::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {
|
||||
fn from(value: rmcp::model::ElicitationAction) -> Self {
|
||||
match value {
|
||||
rmcp::model::ElicitationAction::Accept => Self::Accept,
|
||||
rmcp::model::ElicitationAction::Decline => Self::Decline,
|
||||
rmcp::model::ElicitationAction::Cancel => Self::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerElicitationRequestParams {
|
||||
pub thread_id: String,
|
||||
/// Active Codex turn when this elicitation was observed, if app-server could correlate one.
|
||||
///
|
||||
/// This is nullable because MCP models elicitation as a standalone server-to-client request
|
||||
/// identified by the MCP server request id. It may be triggered during a turn, but turn
|
||||
/// context is app-server correlation rather than part of the protocol identity of the
|
||||
/// elicitation itself.
|
||||
pub turn_id: Option<String>,
|
||||
pub server_name: String,
|
||||
#[serde(flatten)]
|
||||
pub request: McpServerElicitationRequest,
|
||||
// TODO: When core can correlate an elicitation with an MCP tool call, expose the associated
|
||||
// McpToolCall item id here as an optional field. The current core event does not carry that
|
||||
// association.
|
||||
}
|
||||
|
||||
/// Typed form schema for MCP `elicitation/create` requests.
|
||||
///
|
||||
/// This matches the `requestedSchema` shape from the MCP 2025-11-25
|
||||
/// `ElicitRequestFormParams` schema.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationSchema {
|
||||
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional, rename = "$schema")]
|
||||
pub schema_uri: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationObjectType,
|
||||
pub properties: BTreeMap<String, McpElicitationPrimitiveSchema>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub required: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationObjectType {
|
||||
Object,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationPrimitiveSchema {
|
||||
Enum(McpElicitationEnumSchema),
|
||||
String(McpElicitationStringSchema),
|
||||
Number(McpElicitationNumberSchema),
|
||||
Boolean(McpElicitationBooleanSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationStringSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_length: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_length: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub format: Option<McpElicitationStringFormat>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationStringType {
|
||||
String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum McpElicitationStringFormat {
|
||||
Email,
|
||||
Uri,
|
||||
Date,
|
||||
DateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationNumberSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationNumberType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub minimum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub maximum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationNumberType {
|
||||
Number,
|
||||
Integer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationBooleanSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationBooleanType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationBooleanType {
|
||||
Boolean,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationEnumSchema {
|
||||
SingleSelect(McpElicitationSingleSelectEnumSchema),
|
||||
MultiSelect(McpElicitationMultiSelectEnumSchema),
|
||||
Legacy(McpElicitationLegacyTitledEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationLegacyTitledEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
#[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional, rename = "enumNames")]
|
||||
pub enum_names: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationSingleSelectEnumSchema {
|
||||
Untitled(McpElicitationUntitledSingleSelectEnumSchema),
|
||||
Titled(McpElicitationTitledSingleSelectEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledSingleSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledSingleSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "oneOf")]
|
||||
#[ts(rename = "oneOf")]
|
||||
pub one_of: Vec<McpElicitationConstOption>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationMultiSelectEnumSchema {
|
||||
Untitled(McpElicitationUntitledMultiSelectEnumSchema),
|
||||
Titled(McpElicitationTitledMultiSelectEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledMultiSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationArrayType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_items: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_items: Option<u64>,
|
||||
pub items: McpElicitationUntitledEnumItems,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledMultiSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationArrayType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_items: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_items: Option<u64>,
|
||||
pub items: McpElicitationTitledEnumItems,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationArrayType {
|
||||
Array,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledEnumItems {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledEnumItems {
|
||||
#[serde(rename = "anyOf", alias = "oneOf")]
|
||||
#[ts(rename = "anyOf")]
|
||||
pub any_of: Vec<McpElicitationConstOption>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationConstOption {
|
||||
#[serde(rename = "const")]
|
||||
#[ts(rename = "const")]
|
||||
pub const_: String,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "mode", rename_all = "camelCase")]
|
||||
#[ts(tag = "mode")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerElicitationRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Form {
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
meta: Option<JsonValue>,
|
||||
message: String,
|
||||
requested_schema: McpElicitationSchema,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Url {
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
meta: Option<JsonValue>,
|
||||
message: String,
|
||||
url: String,
|
||||
elicitation_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
CoreElicitationRequest::Form {
|
||||
meta,
|
||||
message,
|
||||
requested_schema,
|
||||
} => Ok(Self::Form {
|
||||
meta,
|
||||
message,
|
||||
requested_schema: serde_json::from_value(requested_schema)?,
|
||||
}),
|
||||
CoreElicitationRequest::Url {
|
||||
meta,
|
||||
message,
|
||||
url,
|
||||
elicitation_id,
|
||||
} => Ok(Self::Url {
|
||||
meta,
|
||||
message,
|
||||
url,
|
||||
elicitation_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerElicitationRequestResponse {
|
||||
pub action: McpServerElicitationAction,
|
||||
/// Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.
|
||||
///
|
||||
/// This is nullable because decline/cancel responses have no content.
|
||||
pub content: Option<JsonValue>,
|
||||
/// Optional client metadata for form-mode action handling.
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
pub meta: Option<JsonValue>,
|
||||
}
|
||||
|
||||
impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {
|
||||
fn from(value: McpServerElicitationRequestResponse) -> Self {
|
||||
Self {
|
||||
action: value.action.into(),
|
||||
content: value.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestResponse {
|
||||
fn from(value: rmcp::model::CreateElicitationResult) -> Self {
|
||||
Self {
|
||||
action: value.action.into(),
|
||||
content: value.content,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
53
codex-rs/app-server-protocol/src/protocol/v2/mod.rs
Normal file
53
codex-rs/app-server-protocol/src/protocol/v2/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
mod shared;
|
||||
|
||||
mod account;
|
||||
mod apps;
|
||||
mod collaboration_mode;
|
||||
mod command_exec;
|
||||
mod config;
|
||||
mod device_key;
|
||||
mod experimental_feature;
|
||||
mod feedback;
|
||||
mod fs;
|
||||
mod hook;
|
||||
mod item;
|
||||
mod mcp;
|
||||
mod model;
|
||||
mod notification;
|
||||
mod permissions;
|
||||
mod plugin;
|
||||
mod process;
|
||||
mod realtime;
|
||||
mod review;
|
||||
mod thread;
|
||||
mod thread_data;
|
||||
mod turn;
|
||||
mod windows_sandbox;
|
||||
|
||||
pub use account::*;
|
||||
pub use apps::*;
|
||||
pub use collaboration_mode::*;
|
||||
pub use command_exec::*;
|
||||
pub use config::*;
|
||||
pub use device_key::*;
|
||||
pub use experimental_feature::*;
|
||||
pub use feedback::*;
|
||||
pub use fs::*;
|
||||
pub use hook::*;
|
||||
pub use item::*;
|
||||
pub use mcp::*;
|
||||
pub use model::*;
|
||||
pub use notification::*;
|
||||
pub use permissions::*;
|
||||
pub use plugin::*;
|
||||
pub use process::*;
|
||||
pub use realtime::*;
|
||||
pub use review::*;
|
||||
pub use shared::*;
|
||||
pub use thread::*;
|
||||
pub use thread_data::*;
|
||||
pub use turn::*;
|
||||
pub use windows_sandbox::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
151
codex-rs/app-server-protocol/src/protocol/v2/model.rs
Normal file
151
codex-rs/app-server-protocol/src/protocol/v2/model.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelAvailabilityNux as CoreModelAvailabilityNux;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::openai_models::default_input_modalities;
|
||||
use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason;
|
||||
use codex_protocol::protocol::ModelVerification as CoreModelVerification;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum ModelRerouteReason from CoreModelRerouteReason {
|
||||
HighRiskCyberActivity
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum ModelVerification from CoreModelVerification {
|
||||
TrustedAccessForCyber
|
||||
}
|
||||
);
|
||||
|
||||
#[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/")]
|
||||
pub struct ModelListParams {
|
||||
/// Opaque pagination cursor returned by a previous call.
|
||||
#[ts(optional = nullable)]
|
||||
pub cursor: Option<String>,
|
||||
/// Optional page size; defaults to a reasonable server-side value.
|
||||
#[ts(optional = nullable)]
|
||||
pub limit: Option<u32>,
|
||||
/// When true, include models that are hidden from the default picker list.
|
||||
#[ts(optional = nullable)]
|
||||
pub include_hidden: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelAvailabilityNux {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl From<CoreModelAvailabilityNux> for ModelAvailabilityNux {
|
||||
fn from(value: CoreModelAvailabilityNux) -> Self {
|
||||
Self {
|
||||
message: value.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelServiceTier {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Model {
|
||||
pub id: String,
|
||||
pub model: String,
|
||||
pub upgrade: Option<String>,
|
||||
pub upgrade_info: Option<ModelUpgradeInfo>,
|
||||
pub availability_nux: Option<ModelAvailabilityNux>,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub hidden: bool,
|
||||
pub supported_reasoning_efforts: Vec<ReasoningEffortOption>,
|
||||
pub default_reasoning_effort: ReasoningEffort,
|
||||
#[serde(default = "default_input_modalities")]
|
||||
pub input_modalities: Vec<InputModality>,
|
||||
#[serde(default)]
|
||||
pub supports_personality: bool,
|
||||
/// Deprecated: use `serviceTiers` instead.
|
||||
#[serde(default)]
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub service_tiers: Vec<ModelServiceTier>,
|
||||
// Only one model should be marked as default.
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelUpgradeInfo {
|
||||
pub model: String,
|
||||
pub upgrade_copy: Option<String>,
|
||||
pub model_link: Option<String>,
|
||||
pub migration_markdown: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ReasoningEffortOption {
|
||||
pub reasoning_effort: ReasoningEffort,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelListResponse {
|
||||
pub data: Vec<Model>,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
/// If None, there are no more items to return.
|
||||
pub next_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelReroutedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub from_model: String,
|
||||
pub to_model: String,
|
||||
pub reason: ModelRerouteReason,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ModelVerificationNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub verifications: Vec<ModelVerification>,
|
||||
}
|
||||
56
codex-rs/app-server-protocol/src/protocol/v2/notification.rs
Normal file
56
codex-rs/app-server-protocol/src/protocol/v2/notification.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use super::TurnError;
|
||||
use crate::RequestId;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DeprecationNoticeNotification {
|
||||
/// Concise summary of what is deprecated.
|
||||
pub summary: String,
|
||||
/// Optional extra guidance, such as migration steps or rationale.
|
||||
pub details: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WarningNotification {
|
||||
/// Optional thread target when the warning applies to a specific thread.
|
||||
pub thread_id: Option<String>,
|
||||
/// Concise warning message for the user.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GuardianWarningNotification {
|
||||
/// Thread target for the guardian warning.
|
||||
pub thread_id: String,
|
||||
/// Concise guardian warning message for the user.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ErrorNotification {
|
||||
pub error: TurnError,
|
||||
// Set to true if the error is transient and the app-server process will automatically retry.
|
||||
// If true, this will not interrupt a turn.
|
||||
pub will_retry: bool,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ServerRequestResolvedNotification {
|
||||
pub thread_id: String,
|
||||
pub request_id: RequestId,
|
||||
}
|
||||
854
codex-rs/app-server-protocol/src/protocol/v2/permissions.rs
Normal file
854
codex-rs/app-server-protocol/src/protocol/v2/permissions.rs
Normal file
@@ -0,0 +1,854 @@
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
|
||||
use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleAction;
|
||||
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;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode as CoreFileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath as CoreFileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry as CoreFileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath as CoreFileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy as CoreNetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
v2_enum_from_core! {
|
||||
pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol {
|
||||
Http,
|
||||
Https,
|
||||
Socks5Tcp,
|
||||
Socks5Udp,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct NetworkApprovalContext {
|
||||
pub host: String,
|
||||
pub protocol: NetworkApprovalProtocol,
|
||||
}
|
||||
|
||||
impl From<CoreNetworkApprovalContext> for NetworkApprovalContext {
|
||||
fn from(value: CoreNetworkApprovalContext) -> Self {
|
||||
Self {
|
||||
host: value.host,
|
||||
protocol: value.protocol.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AdditionalFileSystemPermissions {
|
||||
/// This will be removed in favor of `entries`.
|
||||
pub read: Option<Vec<AbsolutePathBuf>>,
|
||||
/// This will be removed in favor of `entries`.
|
||||
pub write: Option<Vec<AbsolutePathBuf>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub glob_scan_max_depth: Option<NonZeroUsize>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub entries: Option<Vec<FileSystemSandboxEntry>>,
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
|
||||
fn from(value: CoreFileSystemPermissions) -> Self {
|
||||
if let Some((read, write)) = value.legacy_read_write_roots() {
|
||||
let mut entries = Vec::with_capacity(
|
||||
read.as_ref().map_or(0, Vec::len) + write.as_ref().map_or(0, Vec::len),
|
||||
);
|
||||
if let Some(paths) = read.as_ref() {
|
||||
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
}));
|
||||
}
|
||||
if let Some(paths) = write.as_ref() {
|
||||
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
access: FileSystemAccessMode::Write,
|
||||
}));
|
||||
}
|
||||
Self {
|
||||
read,
|
||||
write,
|
||||
glob_scan_max_depth: None,
|
||||
entries: Some(entries),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
read: None,
|
||||
write: None,
|
||||
glob_scan_max_depth: value.glob_scan_max_depth,
|
||||
entries: Some(
|
||||
value
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(FileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
|
||||
fn from(value: AdditionalFileSystemPermissions) -> Self {
|
||||
let mut permissions = if let Some(entries) = value.entries {
|
||||
Self {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(CoreFileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
glob_scan_max_depth: None,
|
||||
}
|
||||
} else {
|
||||
CoreFileSystemPermissions::from_read_write_roots(value.read, value.write)
|
||||
};
|
||||
permissions.glob_scan_max_depth = value.glob_scan_max_depth;
|
||||
permissions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AdditionalNetworkPermissions {
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PermissionProfileNetworkPermissions {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl From<CoreNetworkPermissions> for AdditionalNetworkPermissions {
|
||||
fn from(value: CoreNetworkPermissions) -> Self {
|
||||
Self {
|
||||
enabled: value.enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdditionalNetworkPermissions> for CoreNetworkPermissions {
|
||||
fn from(value: AdditionalNetworkPermissions) -> Self {
|
||||
Self {
|
||||
enabled: value.enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreNetworkSandboxPolicy> for PermissionProfileNetworkPermissions {
|
||||
fn from(value: CoreNetworkSandboxPolicy) -> Self {
|
||||
Self {
|
||||
enabled: value.is_enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionProfileNetworkPermissions> for CoreNetworkSandboxPolicy {
|
||||
fn from(value: PermissionProfileNetworkPermissions) -> Self {
|
||||
if value.enabled {
|
||||
Self::Enabled
|
||||
} else {
|
||||
Self::Restricted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RequestPermissionProfile {
|
||||
pub network: Option<AdditionalNetworkPermissions>,
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
impl From<CoreRequestPermissionProfile> for RequestPermissionProfile {
|
||||
fn from(value: CoreRequestPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network.map(AdditionalNetworkPermissions::from),
|
||||
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestPermissionProfile> for CoreRequestPermissionProfile {
|
||||
fn from(value: RequestPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum FileSystemAccessMode from CoreFileSystemAccessMode {
|
||||
Read,
|
||||
Write,
|
||||
None
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
#[ts(tag = "kind")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum FileSystemSpecialPath {
|
||||
Root,
|
||||
Minimal,
|
||||
#[serde(alias = "current_working_directory")]
|
||||
ProjectRoots {
|
||||
subpath: Option<PathBuf>,
|
||||
},
|
||||
Tmpdir,
|
||||
SlashTmp,
|
||||
Unknown {
|
||||
path: String,
|
||||
subpath: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemSpecialPath> for FileSystemSpecialPath {
|
||||
fn from(value: CoreFileSystemSpecialPath) -> Self {
|
||||
match value {
|
||||
CoreFileSystemSpecialPath::Root => Self::Root,
|
||||
CoreFileSystemSpecialPath::Minimal => Self::Minimal,
|
||||
CoreFileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
|
||||
CoreFileSystemSpecialPath::Tmpdir => Self::Tmpdir,
|
||||
CoreFileSystemSpecialPath::SlashTmp => Self::SlashTmp,
|
||||
CoreFileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown { path, subpath },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
|
||||
fn from(value: FileSystemSpecialPath) -> Self {
|
||||
match value {
|
||||
FileSystemSpecialPath::Root => Self::Root,
|
||||
FileSystemSpecialPath::Minimal => Self::Minimal,
|
||||
FileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
|
||||
FileSystemSpecialPath::Tmpdir => Self::Tmpdir,
|
||||
FileSystemSpecialPath::SlashTmp => Self::SlashTmp,
|
||||
FileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown { path, subpath },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum FileSystemPath {
|
||||
Path { path: AbsolutePathBuf },
|
||||
GlobPattern { pattern: String },
|
||||
Special { value: FileSystemSpecialPath },
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemPath> for FileSystemPath {
|
||||
fn from(value: CoreFileSystemPath) -> Self {
|
||||
match value {
|
||||
CoreFileSystemPath::Path { path } => Self::Path { path },
|
||||
CoreFileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
||||
CoreFileSystemPath::Special { value } => Self::Special {
|
||||
value: value.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSystemPath> for CoreFileSystemPath {
|
||||
fn from(value: FileSystemPath) -> Self {
|
||||
match value {
|
||||
FileSystemPath::Path { path } => Self::Path { path },
|
||||
FileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
||||
FileSystemPath::Special { value } => Self::Special {
|
||||
value: value.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct FileSystemSandboxEntry {
|
||||
pub path: FileSystemPath,
|
||||
pub access: FileSystemAccessMode,
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
|
||||
fn from(value: CoreFileSystemSandboxEntry) -> Self {
|
||||
Self {
|
||||
path: value.path.into(),
|
||||
access: value.access.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
|
||||
fn from(value: FileSystemSandboxEntry) -> Self {
|
||||
Self {
|
||||
path: value.path.into(),
|
||||
access: value.access.to_core(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PermissionProfileFileSystemPermissions {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Restricted {
|
||||
entries: Vec<FileSystemSandboxEntry>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
glob_scan_max_depth: Option<NonZeroUsize>,
|
||||
},
|
||||
Unrestricted,
|
||||
}
|
||||
|
||||
impl From<CoreManagedFileSystemPermissions> for PermissionProfileFileSystemPermissions {
|
||||
fn from(value: CoreManagedFileSystemPermissions) -> Self {
|
||||
match value {
|
||||
CoreManagedFileSystemPermissions::Restricted {
|
||||
entries,
|
||||
glob_scan_max_depth,
|
||||
} => Self::Restricted {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(FileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
glob_scan_max_depth,
|
||||
},
|
||||
CoreManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionProfileFileSystemPermissions> for CoreManagedFileSystemPermissions {
|
||||
fn from(value: PermissionProfileFileSystemPermissions) -> Self {
|
||||
match value {
|
||||
PermissionProfileFileSystemPermissions::Restricted {
|
||||
entries,
|
||||
glob_scan_max_depth,
|
||||
} => Self::Restricted {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(CoreFileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
glob_scan_max_depth,
|
||||
},
|
||||
PermissionProfileFileSystemPermissions::Unrestricted => Self::Unrestricted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PermissionProfile {
|
||||
/// Codex owns sandbox construction for this profile.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Managed {
|
||||
network: PermissionProfileNetworkPermissions,
|
||||
file_system: PermissionProfileFileSystemPermissions,
|
||||
},
|
||||
/// Do not apply an outer sandbox.
|
||||
Disabled,
|
||||
/// Filesystem isolation is enforced by an external caller.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
External {
|
||||
network: PermissionProfileNetworkPermissions,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CorePermissionProfile> for PermissionProfile {
|
||||
fn from(value: CorePermissionProfile) -> Self {
|
||||
match value {
|
||||
CorePermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => Self::Managed {
|
||||
network: network.into(),
|
||||
file_system: file_system.into(),
|
||||
},
|
||||
CorePermissionProfile::Disabled => Self::Disabled,
|
||||
CorePermissionProfile::External { network } => Self::External {
|
||||
network: network.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionProfile> for CorePermissionProfile {
|
||||
fn from(value: PermissionProfile) -> Self {
|
||||
match value {
|
||||
PermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => Self::Managed {
|
||||
file_system: file_system.into(),
|
||||
network: network.into(),
|
||||
},
|
||||
PermissionProfile::Disabled => Self::Disabled,
|
||||
PermissionProfile::External { network } => Self::External {
|
||||
network: network.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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/")]
|
||||
pub struct AdditionalPermissionProfile {
|
||||
/// Partial overlay used for per-command permission requests.
|
||||
pub network: Option<AdditionalNetworkPermissions>,
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
impl From<CoreAdditionalPermissionProfile> for AdditionalPermissionProfile {
|
||||
fn from(value: CoreAdditionalPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network.map(AdditionalNetworkPermissions::from),
|
||||
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
fn from(value: AdditionalPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GrantedPermissionProfile {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub network: Option<AdditionalNetworkPermissions>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
impl From<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
fn from(value: GrantedPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum NetworkAccess {
|
||||
#[default]
|
||||
Restricted,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum SandboxPolicy {
|
||||
DangerFullAccess,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ReadOnly {
|
||||
#[serde(default)]
|
||||
network_access: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ExternalSandbox {
|
||||
#[serde(default)]
|
||||
network_access: NetworkAccess,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
WorkspaceWrite {
|
||||
#[serde(default)]
|
||||
writable_roots: Vec<AbsolutePathBuf>,
|
||||
#[serde(default)]
|
||||
network_access: bool,
|
||||
#[serde(default)]
|
||||
exclude_tmpdir_env_var: bool,
|
||||
#[serde(default)]
|
||||
exclude_slash_tmp: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
enum SandboxPolicyDeserialize {
|
||||
DangerFullAccess,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ReadOnly {
|
||||
#[serde(default)]
|
||||
network_access: bool,
|
||||
#[serde(default)]
|
||||
access: Option<LegacyReadOnlyAccess>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ExternalSandbox {
|
||||
#[serde(default)]
|
||||
network_access: NetworkAccess,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WorkspaceWrite {
|
||||
#[serde(default)]
|
||||
writable_roots: Vec<AbsolutePathBuf>,
|
||||
#[serde(default)]
|
||||
read_only_access: Option<LegacyReadOnlyAccess>,
|
||||
#[serde(default)]
|
||||
network_access: bool,
|
||||
#[serde(default)]
|
||||
exclude_tmpdir_env_var: bool,
|
||||
#[serde(default)]
|
||||
exclude_slash_tmp: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
enum LegacyReadOnlyAccess {
|
||||
FullAccess,
|
||||
Restricted,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SandboxPolicy {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
match SandboxPolicyDeserialize::deserialize(deserializer)? {
|
||||
SandboxPolicyDeserialize::DangerFullAccess => Ok(SandboxPolicy::DangerFullAccess),
|
||||
SandboxPolicyDeserialize::ReadOnly {
|
||||
network_access,
|
||||
access,
|
||||
} => {
|
||||
if matches!(access, Some(LegacyReadOnlyAccess::Restricted)) {
|
||||
return Err(serde::de::Error::custom(
|
||||
"readOnly.access is no longer supported; use permissionProfile for restricted reads",
|
||||
));
|
||||
}
|
||||
Ok(SandboxPolicy::ReadOnly { network_access })
|
||||
}
|
||||
SandboxPolicyDeserialize::ExternalSandbox { network_access } => {
|
||||
Ok(SandboxPolicy::ExternalSandbox { network_access })
|
||||
}
|
||||
SandboxPolicyDeserialize::WorkspaceWrite {
|
||||
writable_roots,
|
||||
read_only_access,
|
||||
network_access,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
} => {
|
||||
if matches!(read_only_access, Some(LegacyReadOnlyAccess::Restricted)) {
|
||||
return Err(serde::de::Error::custom(
|
||||
"workspaceWrite.readOnlyAccess is no longer supported; use permissionProfile for restricted reads",
|
||||
));
|
||||
}
|
||||
Ok(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SandboxPolicy {
|
||||
pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy {
|
||||
match self {
|
||||
SandboxPolicy::DangerFullAccess => {
|
||||
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
}
|
||||
SandboxPolicy::ReadOnly { network_access } => {
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly {
|
||||
network_access: *network_access,
|
||||
}
|
||||
}
|
||||
SandboxPolicy::ExternalSandbox { network_access } => {
|
||||
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
|
||||
network_access: match network_access {
|
||||
NetworkAccess::Restricted => CoreNetworkAccess::Restricted,
|
||||
NetworkAccess::Enabled => CoreNetworkAccess::Enabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
} => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: writable_roots.clone(),
|
||||
network_access: *network_access,
|
||||
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp: *exclude_slash_tmp,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
|
||||
fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self {
|
||||
match value {
|
||||
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
|
||||
SandboxPolicy::DangerFullAccess
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly { network_access } => {
|
||||
SandboxPolicy::ReadOnly { network_access }
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
|
||||
SandboxPolicy::ExternalSandbox {
|
||||
network_access: match network_access {
|
||||
CoreNetworkAccess::Restricted => NetworkAccess::Restricted,
|
||||
CoreNetworkAccess::Enabled => NetworkAccess::Enabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
} => SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(transparent)]
|
||||
#[ts(type = "Array<string>", export_to = "v2/")]
|
||||
pub struct ExecPolicyAmendment {
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExecPolicyAmendment {
|
||||
pub fn into_core(self) -> CoreExecPolicyAmendment {
|
||||
CoreExecPolicyAmendment::new(self.command)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreExecPolicyAmendment> for ExecPolicyAmendment {
|
||||
fn from(value: CoreExecPolicyAmendment) -> Self {
|
||||
Self {
|
||||
command: value.command().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum NetworkPolicyRuleAction from CoreNetworkPolicyRuleAction {
|
||||
Allow, Deny
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct NetworkPolicyAmendment {
|
||||
pub host: String,
|
||||
pub action: NetworkPolicyRuleAction,
|
||||
}
|
||||
|
||||
impl NetworkPolicyAmendment {
|
||||
pub fn into_core(self) -> CoreNetworkPolicyAmendment {
|
||||
CoreNetworkPolicyAmendment {
|
||||
host: self.host,
|
||||
action: self.action.to_core(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreNetworkPolicyAmendment> for NetworkPolicyAmendment {
|
||||
fn from(value: CoreNetworkPolicyAmendment) -> Self {
|
||||
Self {
|
||||
host: value.host,
|
||||
action: NetworkPolicyRuleAction::from(value.action),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PermissionsRequestApprovalParams {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub item_id: String,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub reason: Option<String>,
|
||||
pub permissions: RequestPermissionProfile,
|
||||
}
|
||||
|
||||
v2_enum_from_core!(
|
||||
#[derive(Default)]
|
||||
pub enum PermissionGrantScope from CorePermissionGrantScope {
|
||||
#[default]
|
||||
Turn,
|
||||
Session
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PermissionsRequestApprovalResponse {
|
||||
pub permissions: GrantedPermissionProfile,
|
||||
#[serde(default)]
|
||||
pub scope: PermissionGrantScope,
|
||||
/// Review every subsequent command in this turn before normal sandboxed execution.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub strict_auto_review: Option<bool>,
|
||||
}
|
||||
715
codex-rs/app-server-protocol/src/protocol/v2/plugin.rs
Normal file
715
codex-rs/app-server-protocol/src/protocol/v2/plugin.rs
Normal file
@@ -0,0 +1,715 @@
|
||||
use super::AppSummary;
|
||||
use super::HookEventName;
|
||||
use super::HookHandlerType;
|
||||
use super::HookSource;
|
||||
use super::HookTrustStatus;
|
||||
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
|
||||
use codex_protocol::protocol::SkillInterface as CoreSkillInterface;
|
||||
use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata;
|
||||
use codex_protocol::protocol::SkillScope as CoreSkillScope;
|
||||
use codex_protocol::protocol::SkillToolDependency as CoreSkillToolDependency;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillsListParams {
|
||||
/// When empty, defaults to the current session working directory.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub cwds: Vec<PathBuf>,
|
||||
|
||||
/// When true, bypass the skills cache and re-scan skills from disk.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub force_reload: bool,
|
||||
|
||||
/// Optional per-cwd extra roots to scan as user-scoped skills.
|
||||
#[serde(default)]
|
||||
#[ts(optional = nullable)]
|
||||
pub per_cwd_extra_user_roots: Option<Vec<SkillsListExtraRootsForCwd>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillsListExtraRootsForCwd {
|
||||
pub cwd: PathBuf,
|
||||
pub extra_user_roots: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
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/")]
|
||||
pub struct MarketplaceAddParams {
|
||||
pub source: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub ref_name: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sparse_paths: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceAddResponse {
|
||||
pub marketplace_name: String,
|
||||
pub installed_root: AbsolutePathBuf,
|
||||
pub already_added: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceRemoveParams {
|
||||
pub marketplace_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceRemoveResponse {
|
||||
pub marketplace_name: String,
|
||||
pub installed_root: Option<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceUpgradeParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub marketplace_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceUpgradeResponse {
|
||||
pub selected_marketplaces: Vec<String>,
|
||||
pub upgraded_roots: Vec<AbsolutePathBuf>,
|
||||
pub errors: Vec<MarketplaceUpgradeErrorInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceUpgradeErrorInfo {
|
||||
pub marketplace_name: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginListParams {
|
||||
/// Optional working directories used to discover repo marketplaces. When omitted,
|
||||
/// only home-scoped marketplaces and the official curated marketplace are considered.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwds: Option<Vec<AbsolutePathBuf>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginListResponse {
|
||||
pub marketplaces: Vec<PluginMarketplaceEntry>,
|
||||
#[serde(default)]
|
||||
pub marketplace_load_errors: Vec<MarketplaceLoadErrorInfo>,
|
||||
#[serde(default)]
|
||||
pub featured_plugin_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceLoadErrorInfo {
|
||||
pub marketplace_path: AbsolutePathBuf,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginReadParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub marketplace_path: Option<AbsolutePathBuf>,
|
||||
#[ts(optional = nullable)]
|
||||
pub remote_marketplace_name: Option<String>,
|
||||
pub plugin_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginReadResponse {
|
||||
pub plugin: PluginDetail,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginSkillReadParams {
|
||||
pub remote_marketplace_name: String,
|
||||
pub remote_plugin_id: String,
|
||||
pub skill_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginSkillReadResponse {
|
||||
pub contents: Option<String>,
|
||||
}
|
||||
|
||||
#[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>,
|
||||
#[ts(optional = nullable)]
|
||||
pub discoverability: Option<PluginShareDiscoverability>,
|
||||
#[ts(optional = nullable)]
|
||||
pub share_targets: Option<Vec<PluginShareTarget>>,
|
||||
}
|
||||
|
||||
#[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 PluginShareUpdateTargetsParams {
|
||||
pub remote_plugin_id: String,
|
||||
pub share_targets: Vec<PluginShareTarget>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareUpdateTargetsResponse {
|
||||
pub principals: Vec<PluginSharePrincipal>,
|
||||
}
|
||||
|
||||
#[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<PluginShareListItem>,
|
||||
}
|
||||
|
||||
#[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, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareListItem {
|
||||
pub plugin: PluginSummary,
|
||||
pub share_url: String,
|
||||
pub local_plugin_path: Option<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginShareDiscoverability {
|
||||
#[serde(rename = "LISTED")]
|
||||
#[ts(rename = "LISTED")]
|
||||
Listed,
|
||||
#[serde(rename = "UNLISTED")]
|
||||
#[ts(rename = "UNLISTED")]
|
||||
Unlisted,
|
||||
#[serde(rename = "PRIVATE")]
|
||||
#[ts(rename = "PRIVATE")]
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginSharePrincipalType {
|
||||
#[serde(rename = "user")]
|
||||
#[ts(rename = "user")]
|
||||
User,
|
||||
#[serde(rename = "group")]
|
||||
#[ts(rename = "group")]
|
||||
Group,
|
||||
#[serde(rename = "workspace")]
|
||||
#[ts(rename = "workspace")]
|
||||
Workspace,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareTarget {
|
||||
pub principal_type: PluginSharePrincipalType,
|
||||
pub principal_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginSharePrincipal {
|
||||
pub principal_type: PluginSharePrincipalType,
|
||||
pub principal_id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum SkillScope {
|
||||
User,
|
||||
Repo,
|
||||
System,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillMetadata {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
/// Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.
|
||||
pub short_description: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub interface: Option<SkillInterface>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub dependencies: Option<SkillDependencies>,
|
||||
pub path: AbsolutePathBuf,
|
||||
pub scope: SkillScope,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillInterface {
|
||||
#[ts(optional)]
|
||||
pub display_name: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub short_description: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub icon_small: Option<AbsolutePathBuf>,
|
||||
#[ts(optional)]
|
||||
pub icon_large: Option<AbsolutePathBuf>,
|
||||
#[ts(optional)]
|
||||
pub brand_color: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub default_prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillDependencies {
|
||||
pub tools: Vec<SkillToolDependency>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillToolDependency {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub r#type: String,
|
||||
pub value: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub transport: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub command: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillErrorInfo {
|
||||
pub path: PathBuf,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillsListEntry {
|
||||
pub cwd: PathBuf,
|
||||
pub skills: Vec<SkillMetadata>,
|
||||
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,
|
||||
pub current_hash: String,
|
||||
pub trust_status: HookTrustStatus,
|
||||
}
|
||||
|
||||
#[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/")]
|
||||
pub struct PluginMarketplaceEntry {
|
||||
pub name: String,
|
||||
/// Local marketplace file path when the marketplace is backed by a local file.
|
||||
/// Remote-only catalog marketplaces do not have a local path.
|
||||
pub path: Option<AbsolutePathBuf>,
|
||||
pub interface: Option<MarketplaceInterface>,
|
||||
pub plugins: Vec<PluginSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MarketplaceInterface {
|
||||
pub display_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginInstallPolicy {
|
||||
#[serde(rename = "NOT_AVAILABLE")]
|
||||
#[ts(rename = "NOT_AVAILABLE")]
|
||||
NotAvailable,
|
||||
#[serde(rename = "AVAILABLE")]
|
||||
#[ts(rename = "AVAILABLE")]
|
||||
Available,
|
||||
#[serde(rename = "INSTALLED_BY_DEFAULT")]
|
||||
#[ts(rename = "INSTALLED_BY_DEFAULT")]
|
||||
InstalledByDefault,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginAuthPolicy {
|
||||
#[serde(rename = "ON_INSTALL")]
|
||||
#[ts(rename = "ON_INSTALL")]
|
||||
OnInstall,
|
||||
#[serde(rename = "ON_USE")]
|
||||
#[ts(rename = "ON_USE")]
|
||||
OnUse,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginAvailability {
|
||||
/// Plugin-service currently sends `"ENABLED"` for available remote plugins.
|
||||
/// Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps
|
||||
/// decoding compatible with that upstream response.
|
||||
#[serde(rename = "AVAILABLE", alias = "ENABLED")]
|
||||
#[ts(rename = "AVAILABLE")]
|
||||
#[default]
|
||||
Available,
|
||||
#[serde(rename = "DISABLED_BY_ADMIN")]
|
||||
#[ts(rename = "DISABLED_BY_ADMIN")]
|
||||
DisabledByAdmin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginSummary {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub source: PluginSource,
|
||||
pub installed: bool,
|
||||
pub enabled: bool,
|
||||
pub install_policy: PluginInstallPolicy,
|
||||
pub auth_policy: PluginAuthPolicy,
|
||||
/// Availability state for installing and using the plugin.
|
||||
#[serde(default)]
|
||||
pub availability: PluginAvailability,
|
||||
pub interface: Option<PluginInterface>,
|
||||
#[serde(default)]
|
||||
pub keywords: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginDetail {
|
||||
pub marketplace_name: String,
|
||||
pub marketplace_path: Option<AbsolutePathBuf>,
|
||||
pub summary: PluginSummary,
|
||||
pub description: Option<String>,
|
||||
pub skills: Vec<SkillSummary>,
|
||||
pub apps: Vec<AppSummary>,
|
||||
pub mcp_servers: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillSummary {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub short_description: Option<String>,
|
||||
pub interface: Option<SkillInterface>,
|
||||
pub path: Option<AbsolutePathBuf>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInterface {
|
||||
pub display_name: Option<String>,
|
||||
pub short_description: Option<String>,
|
||||
pub long_description: Option<String>,
|
||||
pub developer_name: Option<String>,
|
||||
pub category: Option<String>,
|
||||
pub capabilities: Vec<String>,
|
||||
pub website_url: Option<String>,
|
||||
pub privacy_policy_url: Option<String>,
|
||||
pub terms_of_service_url: Option<String>,
|
||||
/// Starter prompts for the plugin. Capped at 3 entries with a maximum of
|
||||
/// 128 characters per entry.
|
||||
pub default_prompt: Option<Vec<String>>,
|
||||
pub brand_color: Option<String>,
|
||||
/// Local composer icon path, resolved from the installed plugin package.
|
||||
pub composer_icon: Option<AbsolutePathBuf>,
|
||||
/// Remote composer icon URL from the plugin catalog.
|
||||
pub composer_icon_url: Option<String>,
|
||||
/// Local logo path, resolved from the installed plugin package.
|
||||
pub logo: Option<AbsolutePathBuf>,
|
||||
/// Remote logo URL from the plugin catalog.
|
||||
pub logo_url: Option<String>,
|
||||
/// Local screenshot paths, resolved from the installed plugin package.
|
||||
pub screenshots: Vec<AbsolutePathBuf>,
|
||||
/// Remote screenshot URLs from the plugin catalog.
|
||||
pub screenshot_urls: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginSource {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Local { path: AbsolutePathBuf },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Git {
|
||||
url: String,
|
||||
path: Option<String>,
|
||||
ref_name: Option<String>,
|
||||
sha: Option<String>,
|
||||
},
|
||||
/// The plugin is available in the remote catalog. Download metadata is
|
||||
/// kept server-side and is not exposed through the app-server API.
|
||||
Remote,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillsConfigWriteParams {
|
||||
/// Path-based selector.
|
||||
#[ts(optional = nullable)]
|
||||
pub path: Option<AbsolutePathBuf>,
|
||||
/// Name-based selector.
|
||||
#[ts(optional = nullable)]
|
||||
pub name: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SkillsConfigWriteResponse {
|
||||
pub effective_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInstallParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub marketplace_path: Option<AbsolutePathBuf>,
|
||||
#[ts(optional = nullable)]
|
||||
pub remote_marketplace_name: Option<String>,
|
||||
pub plugin_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInstallResponse {
|
||||
pub auth_policy: PluginAuthPolicy,
|
||||
pub apps_needing_auth: Vec<AppSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginUninstallParams {
|
||||
pub plugin_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginUninstallResponse {}
|
||||
|
||||
impl From<CoreSkillMetadata> for SkillMetadata {
|
||||
fn from(value: CoreSkillMetadata) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
short_description: value.short_description,
|
||||
interface: value.interface.map(SkillInterface::from),
|
||||
dependencies: value.dependencies.map(SkillDependencies::from),
|
||||
path: value.path,
|
||||
scope: value.scope.into(),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreSkillInterface> for SkillInterface {
|
||||
fn from(value: CoreSkillInterface) -> Self {
|
||||
Self {
|
||||
display_name: value.display_name,
|
||||
short_description: value.short_description,
|
||||
brand_color: value.brand_color,
|
||||
default_prompt: value.default_prompt,
|
||||
icon_small: value.icon_small,
|
||||
icon_large: value.icon_large,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreSkillDependencies> for SkillDependencies {
|
||||
fn from(value: CoreSkillDependencies) -> Self {
|
||||
Self {
|
||||
tools: value
|
||||
.tools
|
||||
.into_iter()
|
||||
.map(SkillToolDependency::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreSkillToolDependency> for SkillToolDependency {
|
||||
fn from(value: CoreSkillToolDependency) -> Self {
|
||||
Self {
|
||||
r#type: value.r#type,
|
||||
value: value.value,
|
||||
description: value.description,
|
||||
transport: value.transport,
|
||||
command: value.command,
|
||||
url: value.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreSkillScope> for SkillScope {
|
||||
fn from(value: CoreSkillScope) -> Self {
|
||||
match value {
|
||||
CoreSkillScope::User => Self::User,
|
||||
CoreSkillScope::Repo => Self::Repo,
|
||||
CoreSkillScope::System => Self::System,
|
||||
CoreSkillScope::Admin => Self::Admin,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// Notification emitted when watched local skill files change.
|
||||
///
|
||||
/// Treat this as an invalidation signal and re-run `skills/list` with the
|
||||
/// client's current parameters when refreshed skill metadata is needed.
|
||||
pub struct SkillsChangedNotification {}
|
||||
204
codex-rs/app-server-protocol/src/protocol/v2/process.rs
Normal file
204
codex-rs/app-server-protocol/src/protocol/v2/process.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// PTY size in character cells for `process/spawn` PTY sessions.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessTerminalSize {
|
||||
/// Terminal height in character cells.
|
||||
pub rows: u16,
|
||||
/// Terminal width in character cells.
|
||||
pub cols: u16,
|
||||
}
|
||||
|
||||
/// Spawn a standalone process (argv vector) without a Codex sandbox on the host
|
||||
/// where the app server is running.
|
||||
///
|
||||
/// `process/spawn` returns after the process has started and the connection-scoped
|
||||
/// `processHandle` has been registered. Process output and exit are reported via
|
||||
/// `process/outputDelta` and `process/exited` notifications.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessSpawnParams {
|
||||
/// Command argv vector. Empty arrays are rejected.
|
||||
pub command: Vec<String>,
|
||||
/// Client-supplied, connection-scoped process handle.
|
||||
///
|
||||
/// Duplicate active handles are rejected on the same connection. The same
|
||||
/// handle can be reused after the prior process exits.
|
||||
pub process_handle: String,
|
||||
/// Absolute working directory for the process.
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Enable PTY mode.
|
||||
///
|
||||
/// This implies `streamStdin` and `streamStdoutStderr`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub tty: bool,
|
||||
/// Allow follow-up `process/writeStdin` requests to write stdin bytes.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub stream_stdin: bool,
|
||||
/// Stream stdout/stderr via `process/outputDelta` notifications.
|
||||
///
|
||||
/// Streamed bytes are not duplicated into the `process/exited` notification.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub stream_stdout_stderr: bool,
|
||||
/// Optional per-stream stdout/stderr capture cap in bytes.
|
||||
///
|
||||
/// When omitted, the server default applies. Set to `null` to disable the
|
||||
/// cap.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
|
||||
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[ts(type = "number | null")]
|
||||
#[ts(optional = nullable)]
|
||||
pub output_bytes_cap: Option<Option<usize>>,
|
||||
/// Optional timeout in milliseconds.
|
||||
///
|
||||
/// When omitted, the server default applies. Set to `null` to disable the
|
||||
/// timeout.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
|
||||
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[ts(type = "number | null")]
|
||||
#[ts(optional = nullable)]
|
||||
pub timeout_ms: Option<Option<i64>>,
|
||||
/// Optional environment overrides merged into the app-server process
|
||||
/// environment.
|
||||
///
|
||||
/// Matching names override inherited values. Set a key to `null` to unset
|
||||
/// an inherited variable.
|
||||
#[ts(optional = nullable)]
|
||||
pub env: Option<HashMap<String, Option<String>>>,
|
||||
/// Optional initial PTY size in character cells. Only valid when `tty` is
|
||||
/// true.
|
||||
#[ts(optional = nullable)]
|
||||
pub size: Option<ProcessTerminalSize>,
|
||||
}
|
||||
|
||||
/// Successful response for `process/spawn`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessSpawnResponse {}
|
||||
|
||||
/// Write stdin bytes to a running `process/spawn` session, close stdin, or
|
||||
/// both.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessWriteStdinParams {
|
||||
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
|
||||
pub process_handle: String,
|
||||
/// Optional base64-encoded stdin bytes to write.
|
||||
#[ts(optional = nullable)]
|
||||
pub delta_base64: Option<String>,
|
||||
/// Close stdin after writing `deltaBase64`, if present.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub close_stdin: bool,
|
||||
}
|
||||
|
||||
/// Empty success response for `process/writeStdin`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessWriteStdinResponse {}
|
||||
|
||||
/// Terminate a running `process/spawn` session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessKillParams {
|
||||
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
|
||||
pub process_handle: String,
|
||||
}
|
||||
|
||||
/// Empty success response for `process/kill`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessKillResponse {}
|
||||
|
||||
/// Resize a running PTY-backed `process/spawn` session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessResizePtyParams {
|
||||
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
|
||||
pub process_handle: String,
|
||||
/// New PTY size in character cells.
|
||||
pub size: ProcessTerminalSize,
|
||||
}
|
||||
|
||||
/// Empty success response for `process/resizePty`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessResizePtyResponse {}
|
||||
|
||||
/// Stream label for `process/outputDelta` notifications.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ProcessOutputStream {
|
||||
/// stdout stream. PTY mode multiplexes terminal output here.
|
||||
Stdout,
|
||||
/// stderr stream.
|
||||
Stderr,
|
||||
}
|
||||
|
||||
/// Base64-encoded output chunk emitted for a streaming `process/spawn` request.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessOutputDeltaNotification {
|
||||
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
|
||||
pub process_handle: String,
|
||||
/// Output stream this chunk belongs to.
|
||||
pub stream: ProcessOutputStream,
|
||||
/// Base64-encoded output bytes.
|
||||
pub delta_base64: String,
|
||||
/// True on the final streamed chunk for this stream when output was
|
||||
/// truncated by `outputBytesCap`.
|
||||
pub cap_reached: bool,
|
||||
}
|
||||
|
||||
/// Final process exit notification for `process/spawn`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ProcessExitedNotification {
|
||||
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
|
||||
pub process_handle: String,
|
||||
/// Process exit code.
|
||||
pub exit_code: i32,
|
||||
/// Buffered stdout capture.
|
||||
///
|
||||
/// Empty when stdout was streamed via `process/outputDelta`.
|
||||
pub stdout: String,
|
||||
/// Whether stdout reached `outputBytesCap`.
|
||||
///
|
||||
/// In streaming mode, stdout is empty and cap state is also reported on the
|
||||
/// final stdout `process/outputDelta` notification.
|
||||
pub stdout_cap_reached: bool,
|
||||
/// Buffered stderr capture.
|
||||
///
|
||||
/// Empty when stderr was streamed via `process/outputDelta`.
|
||||
pub stderr: String,
|
||||
/// Whether stderr reached `outputBytesCap`.
|
||||
///
|
||||
/// In streaming mode, stderr is empty and cap state is also reported on the
|
||||
/// final stderr `process/outputDelta` notification.
|
||||
pub stderr_cap_reached: bool,
|
||||
}
|
||||
241
codex-rs/app-server-protocol/src/protocol/v2/realtime.rs
Normal file
241
codex-rs/app-server-protocol/src/protocol/v2/realtime.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
|
||||
use codex_protocol::protocol::RealtimeConversationVersion;
|
||||
use codex_protocol::protocol::RealtimeOutputModality;
|
||||
use codex_protocol::protocol::RealtimeVoice;
|
||||
use codex_protocol::protocol::RealtimeVoicesList;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// EXPERIMENTAL - thread realtime audio chunk.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeAudioChunk {
|
||||
pub data: String,
|
||||
pub sample_rate: u32,
|
||||
pub num_channels: u16,
|
||||
pub samples_per_channel: Option<u32>,
|
||||
pub item_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CoreRealtimeAudioFrame> for ThreadRealtimeAudioChunk {
|
||||
fn from(value: CoreRealtimeAudioFrame) -> Self {
|
||||
let CoreRealtimeAudioFrame {
|
||||
data,
|
||||
sample_rate,
|
||||
num_channels,
|
||||
samples_per_channel,
|
||||
item_id,
|
||||
} = value;
|
||||
Self {
|
||||
data,
|
||||
sample_rate,
|
||||
num_channels,
|
||||
samples_per_channel,
|
||||
item_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThreadRealtimeAudioChunk> for CoreRealtimeAudioFrame {
|
||||
fn from(value: ThreadRealtimeAudioChunk) -> Self {
|
||||
let ThreadRealtimeAudioChunk {
|
||||
data,
|
||||
sample_rate,
|
||||
num_channels,
|
||||
samples_per_channel,
|
||||
item_id,
|
||||
} = value;
|
||||
Self {
|
||||
data,
|
||||
sample_rate,
|
||||
num_channels,
|
||||
samples_per_channel,
|
||||
item_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - start a thread-scoped realtime session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStartParams {
|
||||
pub thread_id: String,
|
||||
/// Selects text or audio output for the realtime session. Transport and voice stay
|
||||
/// independent so clients can choose how they connect separately from what the model emits.
|
||||
pub output_modality: RealtimeOutputModality,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
|
||||
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[ts(optional = nullable)]
|
||||
pub prompt: Option<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub realtime_session_id: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub transport: Option<ThreadRealtimeStartTransport>,
|
||||
#[ts(optional = nullable)]
|
||||
pub voice: Option<RealtimeVoice>,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - transport used by thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/", tag = "type")]
|
||||
pub enum ThreadRealtimeStartTransport {
|
||||
Websocket,
|
||||
Webrtc {
|
||||
/// SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the
|
||||
/// realtime events data channel.
|
||||
sdp: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - response for starting thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStartResponse {}
|
||||
|
||||
/// EXPERIMENTAL - append audio input to thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeAppendAudioParams {
|
||||
pub thread_id: String,
|
||||
pub audio: ThreadRealtimeAudioChunk,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - response for appending realtime audio input.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeAppendAudioResponse {}
|
||||
|
||||
/// EXPERIMENTAL - append text input to thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeAppendTextParams {
|
||||
pub thread_id: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - response for appending realtime text input.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeAppendTextResponse {}
|
||||
|
||||
/// EXPERIMENTAL - stop thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStopParams {
|
||||
pub thread_id: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - response for stopping thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStopResponse {}
|
||||
|
||||
/// EXPERIMENTAL - list voices supported by thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeListVoicesParams {}
|
||||
|
||||
/// EXPERIMENTAL - response for listing supported realtime voices.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeListVoicesResponse {
|
||||
pub voices: RealtimeVoicesList,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - emitted when thread realtime startup is accepted.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeStartedNotification {
|
||||
pub thread_id: String,
|
||||
pub realtime_session_id: Option<String>,
|
||||
pub version: RealtimeConversationVersion,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeItemAddedNotification {
|
||||
pub thread_id: String,
|
||||
pub item: JsonValue,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - flat transcript delta emitted whenever realtime
|
||||
/// transcript text changes.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeTranscriptDeltaNotification {
|
||||
pub thread_id: String,
|
||||
pub role: String,
|
||||
/// Live transcript delta from the realtime event.
|
||||
pub delta: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - final transcript text emitted when realtime completes
|
||||
/// a transcript part.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeTranscriptDoneNotification {
|
||||
pub thread_id: String,
|
||||
pub role: String,
|
||||
/// Final complete text for the transcript part.
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - streamed output audio emitted by thread realtime.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeOutputAudioDeltaNotification {
|
||||
pub thread_id: String,
|
||||
pub audio: ThreadRealtimeAudioChunk,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeSdpNotification {
|
||||
pub thread_id: String,
|
||||
pub sdp: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - emitted when thread realtime encounters an error.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeErrorNotification {
|
||||
pub thread_id: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL - emitted when thread realtime transport closes.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadRealtimeClosedNotification {
|
||||
pub thread_id: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
65
codex-rs/app-server-protocol/src/protocol/v2/review.rs
Normal file
65
codex-rs/app-server-protocol/src/protocol/v2/review.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use super::Turn;
|
||||
use super::shared::v2_enum_from_core;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum ReviewDelivery from codex_protocol::protocol::ReviewDelivery {
|
||||
Inline, Detached
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ReviewStartParams {
|
||||
pub thread_id: String,
|
||||
pub target: ReviewTarget,
|
||||
|
||||
/// Where to run the review: inline (default) on the current thread or
|
||||
/// detached on a new thread (returned in `reviewThreadId`).
|
||||
#[serde(default)]
|
||||
#[ts(optional = nullable)]
|
||||
pub delivery: Option<ReviewDelivery>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ReviewStartResponse {
|
||||
pub turn: Turn,
|
||||
/// Identifies the thread where the review runs.
|
||||
///
|
||||
/// For inline reviews, this is the original thread id.
|
||||
/// For detached reviews, this is the id of the new review thread.
|
||||
pub review_thread_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type", export_to = "v2/")]
|
||||
pub enum ReviewTarget {
|
||||
/// Review the working tree: staged, unstaged, and untracked files.
|
||||
UncommittedChanges,
|
||||
|
||||
/// Review changes between the current branch and the given base branch.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
BaseBranch { branch: String },
|
||||
|
||||
/// Review the changes introduced by a specific commit.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Commit {
|
||||
sha: String,
|
||||
/// Optional human-readable label (e.g., commit subject) for UIs.
|
||||
title: Option<String>,
|
||||
},
|
||||
|
||||
/// Arbitrary instructions, equivalent to the old free-form prompt.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Custom { instructions: String },
|
||||
}
|
||||
316
codex-rs/app-server-protocol/src/protocol/v2/shared.rs
Normal file
316
codex-rs/app-server-protocol/src/protocol/v2/shared.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
|
||||
use codex_protocol::config_types::SandboxMode as CoreSandboxMode;
|
||||
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
|
||||
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
|
||||
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
|
||||
use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::InstanceType;
|
||||
use schemars::schema::Metadata;
|
||||
use schemars::schema::Schema;
|
||||
use schemars::schema::SchemaObject;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use ts_rs::TS;
|
||||
|
||||
// Macro to declare a camelCased API v2 enum mirroring a core enum which
|
||||
// tends to use either snake_case or kebab-case.
|
||||
macro_rules! v2_enum_from_core {
|
||||
(
|
||||
$(#[$enum_meta:meta])*
|
||||
pub enum $Name:ident from $Src:path {
|
||||
$( $(#[$variant_meta:meta])* $Variant:ident ),+ $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
$(#[$enum_meta])*
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum $Name {
|
||||
$( $(#[$variant_meta])* $Variant ),+
|
||||
}
|
||||
|
||||
impl $Name {
|
||||
pub fn to_core(self) -> $Src {
|
||||
match self { $( $Name::$Variant => <$Src>::$Variant ),+ }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$Src> for $Name {
|
||||
fn from(value: $Src) -> Self {
|
||||
match value { $( <$Src>::$Variant => $Name::$Variant ),+ }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) use v2_enum_from_core;
|
||||
|
||||
pub(super) const fn default_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum NonSteerableTurnKind {
|
||||
Review,
|
||||
Compact,
|
||||
}
|
||||
|
||||
/// This translation layer make sure that we expose codex error code in camel case.
|
||||
///
|
||||
/// When an upstream HTTP status is available (for example, from the Responses API or a provider),
|
||||
/// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CodexErrorInfo {
|
||||
ContextWindowExceeded,
|
||||
UsageLimitExceeded,
|
||||
ServerOverloaded,
|
||||
CyberPolicy,
|
||||
HttpConnectionFailed {
|
||||
#[serde(rename = "httpStatusCode")]
|
||||
#[ts(rename = "httpStatusCode")]
|
||||
http_status_code: Option<u16>,
|
||||
},
|
||||
/// Failed to connect to the response SSE stream.
|
||||
ResponseStreamConnectionFailed {
|
||||
#[serde(rename = "httpStatusCode")]
|
||||
#[ts(rename = "httpStatusCode")]
|
||||
http_status_code: Option<u16>,
|
||||
},
|
||||
InternalServerError,
|
||||
Unauthorized,
|
||||
BadRequest,
|
||||
ThreadRollbackFailed,
|
||||
SandboxError,
|
||||
/// The response SSE stream disconnected in the middle of a turn before completion.
|
||||
ResponseStreamDisconnected {
|
||||
#[serde(rename = "httpStatusCode")]
|
||||
#[ts(rename = "httpStatusCode")]
|
||||
http_status_code: Option<u16>,
|
||||
},
|
||||
/// Reached the retry limit for responses.
|
||||
ResponseTooManyFailedAttempts {
|
||||
#[serde(rename = "httpStatusCode")]
|
||||
#[ts(rename = "httpStatusCode")]
|
||||
http_status_code: Option<u16>,
|
||||
},
|
||||
/// Returned when `turn/start` or `turn/steer` is submitted while the current active turn
|
||||
/// cannot accept same-turn steering, for example `/review` or manual `/compact`.
|
||||
ActiveTurnNotSteerable {
|
||||
#[serde(rename = "turnKind")]
|
||||
#[ts(rename = "turnKind")]
|
||||
turn_kind: NonSteerableTurnKind,
|
||||
},
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<CoreCodexErrorInfo> for CodexErrorInfo {
|
||||
fn from(value: CoreCodexErrorInfo) -> Self {
|
||||
match value {
|
||||
CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
|
||||
CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
|
||||
CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
|
||||
CoreCodexErrorInfo::CyberPolicy => CodexErrorInfo::CyberPolicy,
|
||||
CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => {
|
||||
CodexErrorInfo::HttpConnectionFailed { http_status_code }
|
||||
}
|
||||
CoreCodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } => {
|
||||
CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code }
|
||||
}
|
||||
CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError,
|
||||
CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized,
|
||||
CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest,
|
||||
CoreCodexErrorInfo::ThreadRollbackFailed => CodexErrorInfo::ThreadRollbackFailed,
|
||||
CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError,
|
||||
CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => {
|
||||
CodexErrorInfo::ResponseStreamDisconnected { http_status_code }
|
||||
}
|
||||
CoreCodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } => {
|
||||
CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code }
|
||||
}
|
||||
CoreCodexErrorInfo::ActiveTurnNotSteerable { turn_kind } => {
|
||||
CodexErrorInfo::ActiveTurnNotSteerable {
|
||||
turn_kind: turn_kind.into(),
|
||||
}
|
||||
}
|
||||
CoreCodexErrorInfo::Other => CodexErrorInfo::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreNonSteerableTurnKind> for NonSteerableTurnKind {
|
||||
fn from(value: CoreNonSteerableTurnKind) -> Self {
|
||||
match value {
|
||||
CoreNonSteerableTurnKind::Review => Self::Review,
|
||||
CoreNonSteerableTurnKind::Compact => Self::Compact,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum AskForApproval {
|
||||
#[serde(rename = "untrusted")]
|
||||
#[ts(rename = "untrusted")]
|
||||
UnlessTrusted,
|
||||
OnFailure,
|
||||
OnRequest,
|
||||
#[experimental("askForApproval.granular")]
|
||||
Granular {
|
||||
sandbox_approval: bool,
|
||||
rules: bool,
|
||||
#[serde(default)]
|
||||
skill_approval: bool,
|
||||
#[serde(default)]
|
||||
request_permissions: bool,
|
||||
mcp_elicitations: bool,
|
||||
},
|
||||
Never,
|
||||
}
|
||||
|
||||
impl AskForApproval {
|
||||
pub fn to_core(self) -> CoreAskForApproval {
|
||||
match self {
|
||||
AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted,
|
||||
AskForApproval::OnFailure => CoreAskForApproval::OnFailure,
|
||||
AskForApproval::OnRequest => CoreAskForApproval::OnRequest,
|
||||
AskForApproval::Granular {
|
||||
sandbox_approval,
|
||||
rules,
|
||||
skill_approval,
|
||||
request_permissions,
|
||||
mcp_elicitations,
|
||||
} => CoreAskForApproval::Granular(CoreGranularApprovalConfig {
|
||||
sandbox_approval,
|
||||
rules,
|
||||
skill_approval,
|
||||
request_permissions,
|
||||
mcp_elicitations,
|
||||
}),
|
||||
AskForApproval::Never => CoreAskForApproval::Never,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreAskForApproval> for AskForApproval {
|
||||
fn from(value: CoreAskForApproval) -> Self {
|
||||
match value {
|
||||
CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted,
|
||||
CoreAskForApproval::OnFailure => AskForApproval::OnFailure,
|
||||
CoreAskForApproval::OnRequest => AskForApproval::OnRequest,
|
||||
CoreAskForApproval::Granular(granular_config) => AskForApproval::Granular {
|
||||
sandbox_approval: granular_config.sandbox_approval,
|
||||
rules: granular_config.rules,
|
||||
skill_approval: granular_config.skill_approval,
|
||||
request_permissions: granular_config.request_permissions,
|
||||
mcp_elicitations: granular_config.mcp_elicitations,
|
||||
},
|
||||
CoreAskForApproval::Never => AskForApproval::Never,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, TS)]
|
||||
#[ts(
|
||||
type = r#""user" | "auto_review" | "guardian_subagent""#,
|
||||
export_to = "v2/"
|
||||
)]
|
||||
/// Configures who approval requests are routed to for review. Examples
|
||||
/// include sandbox escapes, blocked network access, MCP approval prompts, and
|
||||
/// ARC escalations. Defaults to `user`. `auto_review` uses a carefully
|
||||
/// prompted subagent to gather relevant context and apply a risk-based
|
||||
/// decision framework before approving or denying the request.
|
||||
pub enum ApprovalsReviewer {
|
||||
#[serde(rename = "user")]
|
||||
User,
|
||||
#[serde(rename = "guardian_subagent", alias = "auto_review")]
|
||||
AutoReview,
|
||||
}
|
||||
|
||||
impl JsonSchema for ApprovalsReviewer {
|
||||
fn schema_name() -> String {
|
||||
"ApprovalsReviewer".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
|
||||
string_enum_schema_with_description(
|
||||
&["user", "auto_review", "guardian_subagent"],
|
||||
"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
metadata: Some(Box::new(Metadata {
|
||||
description: Some(description.to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
schema.enum_values = Some(
|
||||
values
|
||||
.iter()
|
||||
.map(|value| JsonValue::String((*value).to_string()))
|
||||
.collect(),
|
||||
);
|
||||
Schema::Object(schema)
|
||||
}
|
||||
|
||||
impl ApprovalsReviewer {
|
||||
pub fn to_core(self) -> CoreApprovalsReviewer {
|
||||
match self {
|
||||
ApprovalsReviewer::User => CoreApprovalsReviewer::User,
|
||||
ApprovalsReviewer::AutoReview => CoreApprovalsReviewer::AutoReview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreApprovalsReviewer> for ApprovalsReviewer {
|
||||
fn from(value: CoreApprovalsReviewer) -> Self {
|
||||
match value {
|
||||
CoreApprovalsReviewer::User => ApprovalsReviewer::User,
|
||||
CoreApprovalsReviewer::AutoReview => ApprovalsReviewer::AutoReview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum SandboxMode {
|
||||
ReadOnly,
|
||||
WorkspaceWrite,
|
||||
DangerFullAccess,
|
||||
}
|
||||
|
||||
impl SandboxMode {
|
||||
pub fn to_core(self) -> CoreSandboxMode {
|
||||
match self {
|
||||
SandboxMode::ReadOnly => CoreSandboxMode::ReadOnly,
|
||||
SandboxMode::WorkspaceWrite => CoreSandboxMode::WorkspaceWrite,
|
||||
SandboxMode::DangerFullAccess => CoreSandboxMode::DangerFullAccess,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreSandboxMode> for SandboxMode {
|
||||
fn from(value: CoreSandboxMode) -> Self {
|
||||
match value {
|
||||
CoreSandboxMode::ReadOnly => SandboxMode::ReadOnly,
|
||||
CoreSandboxMode::WorkspaceWrite => SandboxMode::WorkspaceWrite,
|
||||
CoreSandboxMode::DangerFullAccess => SandboxMode::DangerFullAccess,
|
||||
}
|
||||
}
|
||||
}
|
||||
3630
codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Normal file
3630
codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
1162
codex-rs/app-server-protocol/src/protocol/v2/thread.rs
Normal file
1162
codex-rs/app-server-protocol/src/protocol/v2/thread.rs
Normal file
File diff suppressed because it is too large
Load Diff
194
codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs
Normal file
194
codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use super::CodexErrorInfo;
|
||||
use super::ThreadItem;
|
||||
use super::ThreadStatus;
|
||||
use super::TurnStatus;
|
||||
use codex_protocol::protocol::SessionSource as CoreSessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource as CoreThreadSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase", export_to = "v2/")]
|
||||
#[derive(Default)]
|
||||
pub enum SessionSource {
|
||||
Cli,
|
||||
#[serde(rename = "vscode")]
|
||||
#[ts(rename = "vscode")]
|
||||
#[default]
|
||||
VsCode,
|
||||
Exec,
|
||||
AppServer,
|
||||
Custom(String),
|
||||
SubAgent(CoreSubAgentSource),
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<CoreSessionSource> for SessionSource {
|
||||
fn from(value: CoreSessionSource) -> Self {
|
||||
match value {
|
||||
CoreSessionSource::Cli => SessionSource::Cli,
|
||||
CoreSessionSource::VSCode => SessionSource::VsCode,
|
||||
CoreSessionSource::Exec => SessionSource::Exec,
|
||||
CoreSessionSource::Mcp => SessionSource::AppServer,
|
||||
CoreSessionSource::Custom(source) => SessionSource::Custom(source),
|
||||
// We do not want to render those at the app-server level.
|
||||
CoreSessionSource::Internal(_) => SessionSource::Unknown,
|
||||
CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub),
|
||||
CoreSessionSource::Unknown => SessionSource::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionSource> for CoreSessionSource {
|
||||
fn from(value: SessionSource) -> Self {
|
||||
match value {
|
||||
SessionSource::Cli => CoreSessionSource::Cli,
|
||||
SessionSource::VsCode => CoreSessionSource::VSCode,
|
||||
SessionSource::Exec => CoreSessionSource::Exec,
|
||||
SessionSource::AppServer => CoreSessionSource::Mcp,
|
||||
SessionSource::Custom(source) => CoreSessionSource::Custom(source),
|
||||
SessionSource::SubAgent(sub) => CoreSessionSource::SubAgent(sub),
|
||||
SessionSource::Unknown => CoreSessionSource::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub enum ThreadSource {
|
||||
User,
|
||||
Subagent,
|
||||
MemoryConsolidation,
|
||||
}
|
||||
|
||||
impl From<CoreThreadSource> for ThreadSource {
|
||||
fn from(value: CoreThreadSource) -> Self {
|
||||
match value {
|
||||
CoreThreadSource::User => ThreadSource::User,
|
||||
CoreThreadSource::Subagent => ThreadSource::Subagent,
|
||||
CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThreadSource> for CoreThreadSource {
|
||||
fn from(value: ThreadSource) -> Self {
|
||||
match value {
|
||||
ThreadSource::User => CoreThreadSource::User,
|
||||
ThreadSource::Subagent => CoreThreadSource::Subagent,
|
||||
ThreadSource::MemoryConsolidation => CoreThreadSource::MemoryConsolidation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GitInfo {
|
||||
pub sha: Option<String>,
|
||||
pub branch: Option<String>,
|
||||
pub origin_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Thread {
|
||||
pub id: String,
|
||||
/// Source thread id when this thread was created by forking another thread.
|
||||
pub forked_from_id: Option<String>,
|
||||
/// Usually the first user message in the thread, if available.
|
||||
pub preview: String,
|
||||
/// Whether the thread is ephemeral and should not be materialized on disk.
|
||||
pub ephemeral: bool,
|
||||
/// Model provider used for this thread (for example, 'openai').
|
||||
pub model_provider: String,
|
||||
/// Unix timestamp (in seconds) when the thread was created.
|
||||
#[ts(type = "number")]
|
||||
pub created_at: i64,
|
||||
/// Unix timestamp (in seconds) when the thread was last updated.
|
||||
#[ts(type = "number")]
|
||||
pub updated_at: i64,
|
||||
/// Current runtime status for the thread.
|
||||
pub status: ThreadStatus,
|
||||
/// [UNSTABLE] Path to the thread on disk.
|
||||
pub path: Option<PathBuf>,
|
||||
/// Working directory captured for the thread.
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Version of the CLI that created the thread.
|
||||
pub cli_version: String,
|
||||
/// Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).
|
||||
pub source: SessionSource,
|
||||
/// Optional analytics source classification for this thread.
|
||||
pub thread_source: Option<ThreadSource>,
|
||||
/// Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
|
||||
pub agent_nickname: Option<String>,
|
||||
/// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.
|
||||
pub agent_role: Option<String>,
|
||||
/// Optional Git metadata captured when the thread was created.
|
||||
pub git_info: Option<GitInfo>,
|
||||
/// Optional user-facing thread title.
|
||||
pub name: Option<String>,
|
||||
/// Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read`
|
||||
/// (when `includeTurns` is true) responses.
|
||||
/// For all other responses and notifications returning a Thread,
|
||||
/// the turns field will be an empty list.
|
||||
pub turns: Vec<Turn>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Turn {
|
||||
pub id: String,
|
||||
/// Thread items currently included in this turn payload.
|
||||
pub items: Vec<ThreadItem>,
|
||||
/// Describes how much of `items` has been loaded for this turn.
|
||||
#[serde(default)]
|
||||
pub items_view: TurnItemsView,
|
||||
pub status: TurnStatus,
|
||||
/// Only populated when the Turn's status is failed.
|
||||
pub error: Option<TurnError>,
|
||||
/// Unix timestamp (in seconds) when the turn started.
|
||||
#[ts(type = "number | null")]
|
||||
pub started_at: Option<i64>,
|
||||
/// Unix timestamp (in seconds) when the turn completed.
|
||||
#[ts(type = "number | null")]
|
||||
pub completed_at: Option<i64>,
|
||||
/// Duration between turn start and completion in milliseconds, if known.
|
||||
#[ts(type = "number | null")]
|
||||
pub duration_ms: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum TurnItemsView {
|
||||
/// `items` was not loaded for this turn. The field is intentionally empty.
|
||||
NotLoaded,
|
||||
/// `items` contains only a display summary for this turn.
|
||||
Summary,
|
||||
/// `items` contains every ThreadItem available from persisted app-server history for this turn.
|
||||
#[default]
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
#[error("{message}")]
|
||||
pub struct TurnError {
|
||||
pub message: String,
|
||||
pub codex_error_info: Option<CodexErrorInfo>,
|
||||
#[serde(default)]
|
||||
pub additional_details: Option<String>,
|
||||
}
|
||||
390
codex-rs/app-server-protocol/src/protocol/v2/turn.rs
Normal file
390
codex-rs/app-server-protocol/src/protocol/v2/turn.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
use super::ApprovalsReviewer;
|
||||
use super::AskForApproval;
|
||||
use super::PermissionProfileSelectionParams;
|
||||
use super::SandboxPolicy;
|
||||
use super::Turn;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg;
|
||||
use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus;
|
||||
use codex_protocol::user_input::ByteRange as CoreByteRange;
|
||||
use codex_protocol::user_input::TextElement as CoreTextElement;
|
||||
use codex_protocol::user_input::UserInput as CoreUserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum TurnStatus {
|
||||
Completed,
|
||||
Interrupted,
|
||||
Failed,
|
||||
InProgress,
|
||||
}
|
||||
|
||||
// Turn APIs
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnEnvironmentParams {
|
||||
pub environment_id: String,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnStartParams {
|
||||
pub thread_id: String,
|
||||
pub input: Vec<UserInput>,
|
||||
/// Optional turn-scoped Responses API client metadata.
|
||||
#[experimental("turn/start.responsesapiClientMetadata")]
|
||||
#[ts(optional = nullable)]
|
||||
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
|
||||
/// Optional turn-scoped environments.
|
||||
///
|
||||
/// Omitted uses the thread sticky environments. Empty disables
|
||||
/// environment access for this turn. Non-empty selects the first
|
||||
/// environment as the current turn environment for this turn.
|
||||
#[experimental("turn/start.environments")]
|
||||
#[ts(optional = nullable)]
|
||||
pub environments: Option<Vec<TurnEnvironmentParams>>,
|
||||
/// Override the working directory for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Override the approval policy for this turn and subsequent turns.
|
||||
#[experimental(nested)]
|
||||
#[ts(optional = nullable)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
/// Override where approval requests are routed for review on this turn and
|
||||
/// subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
/// Override the sandbox policy for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
/// 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 permissions: Option<PermissionProfileSelectionParams>,
|
||||
/// Override the model for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub model: Option<String>,
|
||||
/// Override the service tier for this turn and subsequent turns.
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
|
||||
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[ts(optional = nullable)]
|
||||
pub service_tier: Option<Option<ServiceTier>>,
|
||||
/// Override the reasoning effort for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub effort: Option<ReasoningEffort>,
|
||||
/// Override the reasoning summary for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub summary: Option<ReasoningSummary>,
|
||||
/// Override the personality for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub personality: Option<Personality>,
|
||||
/// Optional JSON Schema used to constrain the final assistant message for
|
||||
/// this turn.
|
||||
#[ts(optional = nullable)]
|
||||
pub output_schema: Option<JsonValue>,
|
||||
|
||||
/// EXPERIMENTAL - Set a pre-set collaboration mode.
|
||||
/// Takes precedence over model, reasoning_effort, and developer instructions if set.
|
||||
///
|
||||
/// For `collaboration_mode.settings.developer_instructions`, `null` means
|
||||
/// "use the built-in instructions for the selected mode".
|
||||
#[experimental("turn/start.collaborationMode")]
|
||||
#[ts(optional = nullable)]
|
||||
pub collaboration_mode: Option<CollaborationMode>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnStartResponse {
|
||||
pub turn: Turn,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnSteerParams {
|
||||
pub thread_id: String,
|
||||
pub input: Vec<UserInput>,
|
||||
/// Optional turn-scoped Responses API client metadata.
|
||||
#[experimental("turn/steer.responsesapiClientMetadata")]
|
||||
#[ts(optional = nullable)]
|
||||
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
|
||||
/// Required active turn id precondition. The request fails when it does not
|
||||
/// match the currently active turn.
|
||||
pub expected_turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnSteerResponse {
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnInterruptParams {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnInterruptResponse {}
|
||||
|
||||
// User input types
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ByteRange {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl From<CoreByteRange> for ByteRange {
|
||||
fn from(value: CoreByteRange) -> Self {
|
||||
Self {
|
||||
start: value.start,
|
||||
end: value.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ByteRange> for CoreByteRange {
|
||||
fn from(value: ByteRange) -> Self {
|
||||
Self {
|
||||
start: value.start,
|
||||
end: value.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TextElement {
|
||||
/// Byte range in the parent `text` buffer that this element occupies.
|
||||
pub byte_range: ByteRange,
|
||||
/// Optional human-readable placeholder for the element, displayed in the UI.
|
||||
placeholder: Option<String>,
|
||||
}
|
||||
|
||||
impl TextElement {
|
||||
pub fn new(byte_range: ByteRange, placeholder: Option<String>) -> Self {
|
||||
Self {
|
||||
byte_range,
|
||||
placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_placeholder(&mut self, placeholder: Option<String>) {
|
||||
self.placeholder = placeholder;
|
||||
}
|
||||
|
||||
pub fn placeholder(&self) -> Option<&str> {
|
||||
self.placeholder.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreTextElement> for TextElement {
|
||||
fn from(value: CoreTextElement) -> Self {
|
||||
Self::new(
|
||||
value.byte_range.into(),
|
||||
value._placeholder_for_conversion_only().map(str::to_string),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextElement> for CoreTextElement {
|
||||
fn from(value: TextElement) -> Self {
|
||||
Self::new(value.byte_range.into(), value.placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum UserInput {
|
||||
Text {
|
||||
text: String,
|
||||
/// UI-defined spans within `text` used to render or persist special elements.
|
||||
#[serde(default)]
|
||||
text_elements: Vec<TextElement>,
|
||||
},
|
||||
Image {
|
||||
url: String,
|
||||
},
|
||||
LocalImage {
|
||||
path: PathBuf,
|
||||
},
|
||||
Skill {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
},
|
||||
Mention {
|
||||
name: String,
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl UserInput {
|
||||
pub fn into_core(self) -> CoreUserInput {
|
||||
match self {
|
||||
UserInput::Text {
|
||||
text,
|
||||
text_elements,
|
||||
} => CoreUserInput::Text {
|
||||
text,
|
||||
text_elements: text_elements.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
UserInput::Image { url } => CoreUserInput::Image { image_url: url },
|
||||
UserInput::LocalImage { path } => CoreUserInput::LocalImage { path },
|
||||
UserInput::Skill { name, path } => CoreUserInput::Skill { name, path },
|
||||
UserInput::Mention { name, path } => CoreUserInput::Mention { name, path },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreUserInput> for UserInput {
|
||||
fn from(value: CoreUserInput) -> Self {
|
||||
match value {
|
||||
CoreUserInput::Text {
|
||||
text,
|
||||
text_elements,
|
||||
} => UserInput::Text {
|
||||
text,
|
||||
text_elements: text_elements.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
CoreUserInput::Image { image_url } => UserInput::Image { url: image_url },
|
||||
CoreUserInput::LocalImage { path } => UserInput::LocalImage { path },
|
||||
CoreUserInput::Skill { name, path } => UserInput::Skill { name, path },
|
||||
CoreUserInput::Mention { name, path } => UserInput::Mention { name, path },
|
||||
_ => unreachable!("unsupported user input variant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserInput {
|
||||
pub fn text_char_count(&self) -> usize {
|
||||
match self {
|
||||
UserInput::Text { text, .. } => text.chars().count(),
|
||||
UserInput::Image { .. }
|
||||
| UserInput::LocalImage { .. }
|
||||
| UserInput::Skill { .. }
|
||||
| UserInput::Mention { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnStartedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn: Turn,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Usage {
|
||||
pub input_tokens: i32,
|
||||
pub cached_input_tokens: i32,
|
||||
pub output_tokens: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnCompletedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn: Turn,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// Notification that the turn-level unified diff has changed.
|
||||
/// Contains the latest aggregated diff across all file changes in the turn.
|
||||
pub struct TurnDiffUpdatedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub diff: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnPlanUpdatedNotification {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub explanation: Option<String>,
|
||||
pub plan: Vec<TurnPlanStep>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnPlanStep {
|
||||
pub step: String,
|
||||
pub status: TurnPlanStepStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum TurnPlanStepStatus {
|
||||
Pending,
|
||||
InProgress,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl From<CorePlanItemArg> for TurnPlanStep {
|
||||
fn from(value: CorePlanItemArg) -> Self {
|
||||
Self {
|
||||
step: value.step,
|
||||
status: value.status.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CorePlanStepStatus> for TurnPlanStepStatus {
|
||||
fn from(value: CorePlanStepStatus) -> Self {
|
||||
match value {
|
||||
CorePlanStepStatus::Pending => Self::Pending,
|
||||
CorePlanStepStatus::InProgress => Self::InProgress,
|
||||
CorePlanStepStatus::Completed => Self::Completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsWorldWritableWarningNotification {
|
||||
pub sample_paths: Vec<String>,
|
||||
pub extra_count: usize,
|
||||
pub failed_scan: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum WindowsSandboxSetupMode {
|
||||
Elevated,
|
||||
Unelevated,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum WindowsSandboxReadiness {
|
||||
Ready,
|
||||
NotConfigured,
|
||||
UpdateRequired,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsSandboxSetupStartParams {
|
||||
pub mode: WindowsSandboxSetupMode,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsSandboxSetupStartResponse {
|
||||
pub started: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsSandboxReadinessResponse {
|
||||
pub status: WindowsSandboxReadiness,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsSandboxSetupCompletedNotification {
|
||||
pub mode: WindowsSandboxSetupMode,
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
@@ -14,5 +14,8 @@ codex_rust_crate(
|
||||
"app-server-all-test": 16,
|
||||
"app-server-unit-tests": 8,
|
||||
},
|
||||
extra_binaries = [
|
||||
"//codex-rs/bwrap:bwrap",
|
||||
],
|
||||
test_tags = ["no-sandbox"],
|
||||
)
|
||||
|
||||
@@ -303,11 +303,11 @@ Example:
|
||||
{ "id": 12, "result": { "thread": { "id": "thr_123", "turns": [], … } } }
|
||||
```
|
||||
|
||||
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
|
||||
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The response includes the forked thread's `sessionId`, so clients do not need to infer it from the new thread id. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
|
||||
|
||||
```json
|
||||
{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "ephemeral": true } }
|
||||
{ "id": 12, "result": { "thread": { "id": "thr_456", … } } }
|
||||
{ "id": 12, "result": { "sessionId": "thr_456", "thread": { "id": "thr_456", … } } }
|
||||
{ "method": "thread/started", "params": { "thread": { … } } }
|
||||
```
|
||||
|
||||
@@ -1393,6 +1393,12 @@ If the session approval policy uses `Granular` with `request_permissions: false`
|
||||
|
||||
`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`.
|
||||
|
||||
Dynamic tool identifiers follow the same constraints as Responses function tools:
|
||||
|
||||
- `name` must match `^[a-zA-Z0-9_-]+$` and be between 1 and 128 characters.
|
||||
- `namespace`, when present, must match `^[a-zA-Z0-9_-]+$` and be between 1 and 64 characters.
|
||||
- `namespace` must not collide with reserved Responses runtime namespaces such as `functions`, `multi_tool_use`, `file_search`, `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`, `python`, `python_user_visible`, `api_tool`, `tool_search`, or `submodel_delegator`.
|
||||
|
||||
Each dynamic tool may set `deferLoading`. When omitted, it defaults to `false`. Set it to `true` to keep the tool registered and callable by runtime features such as `code_mode`, while excluding it from the model-facing tool list sent on ordinary turns. When `tool_search` is available, deferred dynamic tools are searchable and can be exposed by a matching search result.
|
||||
|
||||
When a dynamic tool is invoked during a turn, the server sends an `item/tool/call` JSON-RPC request to the client:
|
||||
|
||||
@@ -53,7 +53,6 @@ use codex_app_server_protocol::ServerRequestPayload;
|
||||
use codex_app_server_protocol::SkillsChangedNotification;
|
||||
use codex_app_server_protocol::ThreadGoalUpdatedNotification;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadNameUpdatedNotification;
|
||||
use codex_app_server_protocol::ThreadRealtimeClosedNotification;
|
||||
use codex_app_server_protocol::ThreadRealtimeErrorNotification;
|
||||
use codex_app_server_protocol::ThreadRealtimeItemAddedNotification;
|
||||
@@ -1207,17 +1206,6 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
}
|
||||
EventMsg::ThreadNameUpdated(thread_name_event) => {
|
||||
let notification = ThreadNameUpdatedNotification {
|
||||
thread_id: thread_name_event.thread_id.to_string(),
|
||||
thread_name: thread_name_event.thread_name,
|
||||
};
|
||||
outgoing
|
||||
.send_global_server_notification(ServerNotification::ThreadNameUpdated(
|
||||
notification,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
EventMsg::ThreadGoalUpdated(thread_goal_event) => {
|
||||
let notification = ThreadGoalUpdatedNotification {
|
||||
thread_id: thread_goal_event.thread_id.to_string(),
|
||||
@@ -2194,6 +2182,7 @@ mod tests {
|
||||
cwd: test_path_buf("/tmp").abs().into(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: SessionSource::Cli,
|
||||
thread_source: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
agent_path: None,
|
||||
@@ -2629,7 +2618,8 @@ mod tests {
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
let codex_core::NewThread {
|
||||
thread_id: conversation_id,
|
||||
@@ -3214,7 +3204,8 @@ mod tests {
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
let codex_core::NewThread {
|
||||
thread_id: conversation_id,
|
||||
|
||||
@@ -140,6 +140,21 @@ impl ConfigManager {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn load_latest_config_for_thread(
|
||||
&self,
|
||||
thread_config: &Config,
|
||||
) -> std::io::Result<Config> {
|
||||
let refreshed_config = self
|
||||
.load_latest_config(Some(thread_config.cwd.to_path_buf()))
|
||||
.await?;
|
||||
let mut config = thread_config
|
||||
.rebuild_preserving_session_layers(&refreshed_config)
|
||||
.await?;
|
||||
self.apply_runtime_feature_enablement(&mut config);
|
||||
self.apply_arg0_paths(&mut config);
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_default_config(&self) -> std::io::Result<Config> {
|
||||
let mut config = Config::load_default_with_cli_overrides_for_codex_home(
|
||||
self.codex_home.clone(),
|
||||
|
||||
@@ -82,11 +82,12 @@ use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::init_state_db_from_config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::AuthManager;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
pub use codex_rollout::StateDbHandle;
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
pub use codex_state::log_db::LogDbLayer;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -127,7 +128,7 @@ pub struct InProcessStartArgs {
|
||||
pub feedback: CodexFeedback,
|
||||
/// SQLite tracing layer used to flush recently emitted logs before feedback upload.
|
||||
pub log_db: Option<LogDbLayer>,
|
||||
/// Process-wide SQLite state handle shared with embedded app-server consumers.
|
||||
/// Optional state DB handle to use for the in-process runtime.
|
||||
pub state_db: Option<StateDbHandle>,
|
||||
/// Environment manager used by core execution and filesystem operations.
|
||||
pub environment_manager: Arc<EnvironmentManager>,
|
||||
@@ -344,7 +345,7 @@ impl InProcessClientHandle {
|
||||
/// the runtime is shut down and an `InvalidData` error is returned.
|
||||
pub async fn start(args: InProcessStartArgs) -> IoResult<InProcessClientHandle> {
|
||||
let initialize = args.initialize.clone();
|
||||
let client = start_uninitialized(args);
|
||||
let client = start_uninitialized(args).await;
|
||||
|
||||
let initialize_response = client
|
||||
.request(ClientRequest::Initialize {
|
||||
@@ -364,8 +365,12 @@ pub async fn start(args: InProcessStartArgs) -> IoResult<InProcessClientHandle>
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
async fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
let channel_capacity = args.channel_capacity.max(1);
|
||||
let state_db = match args.state_db.clone() {
|
||||
Some(state_db) => Some(state_db),
|
||||
None => init_state_db_from_config(args.config.as_ref()).await,
|
||||
};
|
||||
let (client_tx, mut client_rx) = mpsc::channel::<InProcessClientMessage>(channel_capacity);
|
||||
let (event_tx, event_rx) = mpsc::channel::<InProcessServerEvent>(channel_capacity);
|
||||
|
||||
@@ -414,6 +419,12 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
);
|
||||
let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity);
|
||||
let mut processor_handle = tokio::spawn(async move {
|
||||
let Some(state_db) = state_db else {
|
||||
warn!(
|
||||
"in-process app-server state db initialization failed; shutting down processor task"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
|
||||
outgoing: Arc::clone(&processor_outgoing),
|
||||
analytics_events_client,
|
||||
@@ -423,7 +434,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
|
||||
environment_manager: args.environment_manager,
|
||||
feedback: args.feedback,
|
||||
log_db: args.log_db,
|
||||
state_db: args.state_db,
|
||||
state_db,
|
||||
config_warnings: args.config_warnings,
|
||||
session_source: args.session_source,
|
||||
auth_manager,
|
||||
@@ -761,7 +772,7 @@ mod tests {
|
||||
) -> InProcessClientHandle {
|
||||
let codex_home = TempDir::new().expect("temp dir");
|
||||
let config = Arc::new(build_test_config(codex_home.path()).await);
|
||||
let state_db = codex_rollout::state_db::try_init(config.as_ref())
|
||||
let state_db = init_state_db_from_config(config.as_ref())
|
||||
.await
|
||||
.expect("state db should initialize for in-process test");
|
||||
let args = InProcessStartArgs {
|
||||
@@ -819,7 +830,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_allows_device_key_requests_to_reach_device_key_processor() {
|
||||
async fn in_process_allows_device_key_requests_to_reach_device_key_api() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
const MALFORMED_KEY_ID_MESSAGE: &str = concat!(
|
||||
"invalid device key payload: keyId must be dk_hse_, dk_tpm_, or dk_osn_ ",
|
||||
|
||||
@@ -50,11 +50,11 @@ use codex_config::TextRange as CoreTextRange;
|
||||
use codex_core::ExecPolicyError;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::init_state_db_from_config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_rollout::state_db as rollout_state_db;
|
||||
use codex_state::log_db;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -85,6 +85,7 @@ mod filters;
|
||||
mod fs_watch;
|
||||
mod fuzzy_file_search;
|
||||
pub mod in_process;
|
||||
mod mcp_refresh;
|
||||
mod message_processor;
|
||||
mod models;
|
||||
mod outgoing_message;
|
||||
@@ -487,9 +488,9 @@ pub async fn run_main_with_transport_options(
|
||||
}
|
||||
};
|
||||
|
||||
let state_db_result = rollout_state_db::try_init(&config).await;
|
||||
let state_db_init_error = state_db_result.as_ref().err().map(ToString::to_string);
|
||||
let state_db = state_db_result.ok();
|
||||
let state_db = init_state_db_from_config(&config)
|
||||
.await
|
||||
.ok_or_else(|| std::io::Error::other("failed to initialize sqlite state db"))?;
|
||||
|
||||
if should_run_personality_migration {
|
||||
let effective_toml = config.config_layer_stack.effective_config();
|
||||
@@ -598,10 +599,12 @@ pub async fn run_main_with_transport_options(
|
||||
|
||||
let feedback_layer = feedback.logger_layer();
|
||||
let feedback_metadata_layer = feedback.metadata_layer();
|
||||
let log_db = state_db.clone().map(log_db::start);
|
||||
let log_db_layer = log_db
|
||||
.clone()
|
||||
.map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE)));
|
||||
let log_db = log_db::start(state_db.clone());
|
||||
let log_db_layer = Some(
|
||||
log_db
|
||||
.clone()
|
||||
.with_filter(Targets::new().with_default(Level::TRACE)),
|
||||
);
|
||||
let otel_logger_layer = otel.as_ref().and_then(|o| o.logger_layer());
|
||||
let otel_tracing_layer = otel.as_ref().and_then(|o| o.tracing_layer());
|
||||
let _ = tracing_subscriber::registry()
|
||||
@@ -618,10 +621,6 @@ pub async fn run_main_with_transport_options(
|
||||
None => error!("{}", warning.summary),
|
||||
}
|
||||
}
|
||||
if let Some(err) = &state_db_init_error {
|
||||
error!("failed to initialize sqlite state db: {err}");
|
||||
}
|
||||
|
||||
let transport_shutdown_token = CancellationToken::new();
|
||||
let mut transport_accept_handles = Vec::<JoinHandle<()>>::new();
|
||||
|
||||
@@ -666,25 +665,17 @@ pub async fn run_main_with_transport_options(
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
|
||||
|
||||
let remote_control_config_enabled = config.features.enabled(Feature::RemoteControl);
|
||||
let remote_control_enabled = remote_control_config_enabled && state_db.is_some();
|
||||
if remote_control_config_enabled && state_db.is_none() {
|
||||
error!("remote control disabled because sqlite state db is unavailable");
|
||||
}
|
||||
let remote_control_enabled = config.features.enabled(Feature::RemoteControl);
|
||||
if transport_accept_handles.is_empty() && !remote_control_enabled {
|
||||
return Err(std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
if remote_control_config_enabled && state_db.is_none() {
|
||||
"no transport configured; remote control disabled because sqlite state db is unavailable"
|
||||
} else {
|
||||
"no transport configured; use --listen or enable remote control"
|
||||
},
|
||||
"no transport configured; use --listen or enable remote control",
|
||||
));
|
||||
}
|
||||
|
||||
let (remote_control_accept_handle, remote_control_handle) = start_remote_control(
|
||||
config.chatgpt_base_url.clone(),
|
||||
state_db.clone(),
|
||||
Some(state_db.clone()),
|
||||
auth_manager.clone(),
|
||||
transport_event_tx.clone(),
|
||||
transport_shutdown_token.clone(),
|
||||
@@ -768,7 +759,7 @@ pub async fn run_main_with_transport_options(
|
||||
config_manager,
|
||||
environment_manager,
|
||||
feedback: feedback.clone(),
|
||||
log_db,
|
||||
log_db: Some(log_db),
|
||||
state_db: state_db.clone(),
|
||||
config_warnings,
|
||||
session_source,
|
||||
|
||||
241
codex-rs/app-server/src/mcp_refresh.rs
Normal file
241
codex-rs/app-server/src/mcp_refresh.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use crate::config_manager::ConfigManager;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::McpServerRefreshConfig;
|
||||
use codex_protocol::protocol::Op;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) async fn queue_strict_refresh(
|
||||
thread_manager: &Arc<ThreadManager>,
|
||||
config_manager: &ConfigManager,
|
||||
) -> io::Result<()> {
|
||||
config_manager
|
||||
.load_latest_config(/*fallback_cwd*/ None)
|
||||
.await?;
|
||||
let mut refreshes = Vec::new();
|
||||
for thread_id in thread_manager.list_thread_ids().await {
|
||||
let thread = thread_manager
|
||||
.get_thread(thread_id)
|
||||
.await
|
||||
.map_err(|err| io::Error::other(format!("failed to load thread {thread_id}: {err}")))?;
|
||||
let config =
|
||||
build_refresh_config(thread_manager, config_manager, thread.config().await).await?;
|
||||
refreshes.push((thread_id, thread, config));
|
||||
}
|
||||
for (thread_id, thread, config) in refreshes {
|
||||
queue_refresh(thread_id, thread, config).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn queue_best_effort_refresh(
|
||||
thread_manager: &Arc<ThreadManager>,
|
||||
config_manager: &ConfigManager,
|
||||
) {
|
||||
for thread_id in thread_manager.list_thread_ids().await {
|
||||
let thread = match thread_manager.get_thread(thread_id).await {
|
||||
Ok(thread) => thread,
|
||||
Err(err) => {
|
||||
warn!("failed to load thread {thread_id} for MCP refresh: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let config =
|
||||
match build_refresh_config(thread_manager, config_manager, thread.config().await).await
|
||||
{
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
warn!("failed to build MCP refresh config for thread {thread_id}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = queue_refresh(thread_id, thread, config).await {
|
||||
warn!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_refresh_config(
|
||||
thread_manager: &ThreadManager,
|
||||
config_manager: &ConfigManager,
|
||||
thread_config: Arc<Config>,
|
||||
) -> io::Result<McpServerRefreshConfig> {
|
||||
let config = config_manager
|
||||
.load_latest_config_for_thread(thread_config.as_ref())
|
||||
.await?;
|
||||
let mcp_servers = thread_manager
|
||||
.mcp_manager()
|
||||
.configured_servers(&config)
|
||||
.await;
|
||||
Ok(McpServerRefreshConfig {
|
||||
mcp_servers: serde_json::to_value(mcp_servers).map_err(io::Error::other)?,
|
||||
mcp_oauth_credentials_store_mode: serde_json::to_value(
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
)
|
||||
.map_err(io::Error::other)?,
|
||||
})
|
||||
}
|
||||
|
||||
async fn queue_refresh(
|
||||
thread_id: ThreadId,
|
||||
thread: Arc<CodexThread>,
|
||||
config: McpServerRefreshConfig,
|
||||
) -> io::Result<()> {
|
||||
thread
|
||||
.submit(Op::RefreshMcpServers { config })
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| {
|
||||
io::Error::other(format!(
|
||||
"failed to queue MCP refresh for thread {thread_id}: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::ThreadConfigContext;
|
||||
use codex_config::ThreadConfigLoadError;
|
||||
use codex_config::ThreadConfigLoadErrorCode;
|
||||
use codex_config::ThreadConfigLoader;
|
||||
use codex_config::ThreadConfigSource;
|
||||
use codex_core::agent_graph_store_from_state_db;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::init_state_db_from_config;
|
||||
use codex_core::thread_store_from_config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_refresh_reports_thread_planning_failures() -> anyhow::Result<()> {
|
||||
let (_temp_dir, thread_manager, config_manager, _loader) = refresh_test_state().await?;
|
||||
|
||||
let err = queue_strict_refresh(&thread_manager, &config_manager)
|
||||
.await
|
||||
.expect_err("strict refresh should fail");
|
||||
|
||||
assert_eq!(err.to_string(), "failed to load refresh config");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn best_effort_refresh_attempts_every_loaded_thread() -> anyhow::Result<()> {
|
||||
let (_temp_dir, thread_manager, config_manager, loader) = refresh_test_state().await?;
|
||||
|
||||
queue_best_effort_refresh(&thread_manager, &config_manager).await;
|
||||
|
||||
assert_eq!(loader.good_loads.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(loader.bad_loads.load(Ordering::Relaxed), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn refresh_test_state() -> anyhow::Result<(
|
||||
TempDir,
|
||||
Arc<ThreadManager>,
|
||||
ConfigManager,
|
||||
Arc<CountingThreadConfigLoader>,
|
||||
)> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let good_cwd = temp_dir.path().join("good");
|
||||
let bad_cwd = temp_dir.path().join("bad");
|
||||
std::fs::create_dir_all(&good_cwd)?;
|
||||
std::fs::create_dir_all(&bad_cwd)?;
|
||||
|
||||
let initial_config_manager =
|
||||
ConfigManager::without_managed_config_for_tests(temp_dir.path().to_path_buf());
|
||||
let good_config = initial_config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
ConfigOverrides::default(),
|
||||
Some(good_cwd.clone()),
|
||||
)
|
||||
.await?;
|
||||
let bad_config = initial_config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
ConfigOverrides::default(),
|
||||
Some(bad_cwd.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
|
||||
let state_db = init_state_db_from_config(&good_config)
|
||||
.await
|
||||
.expect("refresh tests require state db");
|
||||
let thread_store = thread_store_from_config(&good_config, state_db.clone());
|
||||
let agent_graph_store = agent_graph_store_from_state_db(state_db.clone());
|
||||
let thread_manager = Arc::new(ThreadManager::new(
|
||||
&good_config,
|
||||
auth_manager,
|
||||
SessionSource::Exec,
|
||||
Arc::new(EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
state_db,
|
||||
thread_store,
|
||||
agent_graph_store,
|
||||
));
|
||||
thread_manager.start_thread(good_config).await?;
|
||||
thread_manager.start_thread(bad_config).await?;
|
||||
|
||||
let loader = Arc::new(CountingThreadConfigLoader {
|
||||
good_cwd: AbsolutePathBuf::try_from(good_cwd)?,
|
||||
bad_cwd: AbsolutePathBuf::try_from(bad_cwd)?,
|
||||
good_loads: AtomicUsize::new(0),
|
||||
bad_loads: AtomicUsize::new(0),
|
||||
});
|
||||
let config_manager = ConfigManager::new(
|
||||
temp_dir.path().to_path_buf(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
CloudRequirementsLoader::default(),
|
||||
Arg0DispatchPaths::default(),
|
||||
loader.clone(),
|
||||
);
|
||||
|
||||
Ok((temp_dir, thread_manager, config_manager, loader))
|
||||
}
|
||||
|
||||
struct CountingThreadConfigLoader {
|
||||
good_cwd: AbsolutePathBuf,
|
||||
bad_cwd: AbsolutePathBuf,
|
||||
good_loads: AtomicUsize,
|
||||
bad_loads: AtomicUsize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ThreadConfigLoader for CountingThreadConfigLoader {
|
||||
async fn load(
|
||||
&self,
|
||||
context: ThreadConfigContext,
|
||||
) -> Result<Vec<ThreadConfigSource>, ThreadConfigLoadError> {
|
||||
if context.cwd.as_ref() == Some(&self.good_cwd) {
|
||||
self.good_loads.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
if context.cwd.as_ref() == Some(&self.bad_cwd) {
|
||||
self.bad_loads.fetch_add(1, Ordering::Relaxed);
|
||||
return Err(ThreadConfigLoadError::new(
|
||||
ThreadConfigLoadErrorCode::Internal,
|
||||
/*status_code*/ None,
|
||||
"failed to load refresh config",
|
||||
));
|
||||
}
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ use codex_app_server_protocol::experimental_required_message;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_chatgpt::workspace_settings;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::agent_graph_store_from_state_db;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::thread_store_from_config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
@@ -254,7 +255,7 @@ pub(crate) struct MessageProcessorArgs {
|
||||
pub(crate) environment_manager: Arc<EnvironmentManager>,
|
||||
pub(crate) feedback: CodexFeedback,
|
||||
pub(crate) log_db: Option<LogDbLayer>,
|
||||
pub(crate) state_db: Option<StateDbHandle>,
|
||||
pub(crate) state_db: StateDbHandle,
|
||||
pub(crate) config_warnings: Vec<ConfigWarningNotification>,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) auth_manager: Arc<AuthManager>,
|
||||
@@ -291,14 +292,16 @@ impl MessageProcessor {
|
||||
// affect per-thread behavior, but they must not move newly started,
|
||||
// resumed, or forked threads to a different persistence backend/root.
|
||||
let thread_store = thread_store_from_config(config.as_ref(), state_db.clone());
|
||||
let agent_graph_store = agent_graph_store_from_state_db(state_db.clone());
|
||||
let thread_manager = Arc::new(ThreadManager::new(
|
||||
config.as_ref(),
|
||||
auth_manager.clone(),
|
||||
session_source,
|
||||
environment_manager,
|
||||
Some(analytics_events_client.clone()),
|
||||
Arc::clone(&thread_store),
|
||||
state_db.clone(),
|
||||
Arc::clone(&thread_store),
|
||||
agent_graph_store.clone(),
|
||||
));
|
||||
thread_manager
|
||||
.plugins_manager()
|
||||
@@ -344,7 +347,7 @@ impl MessageProcessor {
|
||||
Arc::clone(&config),
|
||||
feedback,
|
||||
log_db,
|
||||
state_db.clone(),
|
||||
Some(state_db.clone()),
|
||||
);
|
||||
let git_processor = GitRequestProcessor::new();
|
||||
let initialize_processor = InitializeRequestProcessor::new(
|
||||
@@ -395,7 +398,7 @@ impl MessageProcessor {
|
||||
thread_watch_manager.clone(),
|
||||
Arc::clone(&thread_list_state_permit),
|
||||
thread_goal_processor.clone(),
|
||||
state_db.clone(),
|
||||
Some(state_db.clone()),
|
||||
);
|
||||
let turn_processor = TurnRequestProcessor::new(
|
||||
auth_manager.clone(),
|
||||
@@ -413,7 +416,7 @@ impl MessageProcessor {
|
||||
if matches!(plugin_startup_tasks, crate::PluginStartupTasks::Start) {
|
||||
// Keep plugin startup warmups aligned at app-server startup.
|
||||
let on_effective_plugins_changed =
|
||||
plugin_processor.effective_plugins_changed_callback((*config).clone());
|
||||
plugin_processor.effective_plugins_changed_callback();
|
||||
thread_manager
|
||||
.plugins_manager()
|
||||
.maybe_start_plugin_startup_tasks_for_config(
|
||||
@@ -795,9 +798,9 @@ impl MessageProcessor {
|
||||
);
|
||||
|
||||
if let Some(scope) = serialization_scope {
|
||||
let key = RequestSerializationQueueKey::from_scope(connection_id, scope);
|
||||
let (key, access) = RequestSerializationQueueKey::from_scope(connection_id, scope);
|
||||
self.request_serialization_queues
|
||||
.enqueue(key, request)
|
||||
.enqueue(key, access, request)
|
||||
.await;
|
||||
} else {
|
||||
tokio::spawn(async move {
|
||||
@@ -1084,6 +1087,11 @@ impl MessageProcessor {
|
||||
ClientRequest::PluginShareSave { params, .. } => {
|
||||
self.plugin_processor.plugin_share_save(params).await
|
||||
}
|
||||
ClientRequest::PluginShareUpdateTargets { params, .. } => {
|
||||
self.plugin_processor
|
||||
.plugin_share_update_targets(params)
|
||||
.await
|
||||
}
|
||||
ClientRequest::PluginShareList { params, .. } => {
|
||||
self.plugin_processor.plugin_share_list(params).await
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::init_state_db_from_config;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::AuthManager;
|
||||
@@ -281,6 +282,9 @@ async fn build_test_processor(
|
||||
outgoing_tx,
|
||||
analytics_events_client.clone(),
|
||||
));
|
||||
let state_db = init_state_db_from_config(config.as_ref())
|
||||
.await
|
||||
.expect("tracing test processor requires state db");
|
||||
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
|
||||
outgoing,
|
||||
analytics_events_client,
|
||||
@@ -290,7 +294,7 @@ async fn build_test_processor(
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
state_db,
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::VSCode,
|
||||
auth_manager,
|
||||
|
||||
@@ -115,11 +115,17 @@ use codex_app_server_protocol::PluginReadParams;
|
||||
use codex_app_server_protocol::PluginReadResponse;
|
||||
use codex_app_server_protocol::PluginShareDeleteParams;
|
||||
use codex_app_server_protocol::PluginShareDeleteResponse;
|
||||
use codex_app_server_protocol::PluginShareDiscoverability;
|
||||
use codex_app_server_protocol::PluginShareListItem;
|
||||
use codex_app_server_protocol::PluginShareListParams;
|
||||
use codex_app_server_protocol::PluginShareListResponse;
|
||||
use codex_app_server_protocol::PluginSharePrincipal;
|
||||
use codex_app_server_protocol::PluginSharePrincipalType;
|
||||
use codex_app_server_protocol::PluginShareSaveParams;
|
||||
use codex_app_server_protocol::PluginShareSaveResponse;
|
||||
use codex_app_server_protocol::PluginShareTarget;
|
||||
use codex_app_server_protocol::PluginShareUpdateTargetsParams;
|
||||
use codex_app_server_protocol::PluginShareUpdateTargetsResponse;
|
||||
use codex_app_server_protocol::PluginSkillReadParams;
|
||||
use codex_app_server_protocol::PluginSkillReadResponse;
|
||||
use codex_app_server_protocol::PluginSource;
|
||||
@@ -355,7 +361,6 @@ use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GitInfo as CoreGitInfo;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::McpAuthStatus as CoreMcpAuthStatus;
|
||||
use codex_protocol::protocol::McpServerRefreshConfig;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
||||
use codex_protocol::protocol::RealtimeVoicesList;
|
||||
|
||||
@@ -168,7 +168,7 @@ impl AccountRequestProcessor {
|
||||
{
|
||||
Ok(config) => {
|
||||
let refresh_thread_manager = Arc::clone(thread_manager);
|
||||
let refresh_config = config.clone();
|
||||
let refresh_config_manager = config_manager.clone();
|
||||
thread_manager
|
||||
.plugins_manager()
|
||||
.maybe_start_remote_installed_plugins_cache_refresh(
|
||||
@@ -177,7 +177,7 @@ impl AccountRequestProcessor {
|
||||
Some(Arc::new(move || {
|
||||
Self::spawn_effective_plugins_changed_task(
|
||||
Arc::clone(&refresh_thread_manager),
|
||||
refresh_config.clone(),
|
||||
refresh_config_manager.clone(),
|
||||
);
|
||||
})),
|
||||
);
|
||||
@@ -190,19 +190,17 @@ impl AccountRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_effective_plugins_changed_task(thread_manager: Arc<ThreadManager>, config: Config) {
|
||||
fn spawn_effective_plugins_changed_task(
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
config_manager: ConfigManager,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
thread_manager.plugins_manager().clear_cache();
|
||||
thread_manager.skills_manager().clear_cache();
|
||||
if thread_manager.list_thread_ids().await.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Err(err) =
|
||||
McpRequestProcessor::queue_mcp_server_refresh_for_config(&thread_manager, &config)
|
||||
.await
|
||||
{
|
||||
warn!("failed to queue MCP refresh after effective plugins changed: {err:?}");
|
||||
}
|
||||
crate::mcp_refresh::queue_best_effort_refresh(&thread_manager, &config_manager).await;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CatalogRequestProcessor {
|
||||
@@ -9,6 +10,8 @@ pub(crate) struct CatalogRequestProcessor {
|
||||
pub(super) workspace_settings_cache: Arc<workspace_settings::WorkspaceSettingsCache>,
|
||||
}
|
||||
|
||||
const SKILLS_LIST_CWD_CONCURRENCY: usize = 8;
|
||||
|
||||
fn skills_to_info(
|
||||
skills: &[codex_core::skills::SkillMetadata],
|
||||
disabled_paths: &HashSet<AbsolutePathBuf>,
|
||||
@@ -379,6 +382,7 @@ impl CatalogRequestProcessor {
|
||||
&self,
|
||||
params: SkillsListParams,
|
||||
) -> Result<SkillsListResponse, JSONRPCErrorError> {
|
||||
let total_started_at = Instant::now();
|
||||
let SkillsListParams {
|
||||
cwds,
|
||||
force_reload,
|
||||
@@ -390,7 +394,9 @@ impl CatalogRequestProcessor {
|
||||
cwds
|
||||
};
|
||||
let cwd_set: HashSet<PathBuf> = cwds.iter().cloned().collect();
|
||||
let cwd_count = cwds.len();
|
||||
|
||||
let extra_roots_started_at = Instant::now();
|
||||
let mut extra_roots_by_cwd: HashMap<PathBuf, Vec<AbsolutePathBuf>> = HashMap::new();
|
||||
for entry in per_cwd_extra_user_roots.unwrap_or_default() {
|
||||
if !cwd_set.contains(&entry.cwd) {
|
||||
@@ -417,12 +423,20 @@ impl CatalogRequestProcessor {
|
||||
.or_default()
|
||||
.extend(valid_extra_roots);
|
||||
}
|
||||
let extra_roots_ms = extra_roots_started_at.elapsed().as_millis();
|
||||
let extra_root_count = extra_roots_by_cwd.values().map(Vec::len).sum::<usize>();
|
||||
|
||||
let load_config_started_at = Instant::now();
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
let load_config_ms = load_config_started_at.elapsed().as_millis();
|
||||
let auth_started_at = Instant::now();
|
||||
let auth = self.auth_manager.auth().await;
|
||||
let auth_ms = auth_started_at.elapsed().as_millis();
|
||||
let workspace_setting_started_at = Instant::now();
|
||||
let workspace_codex_plugins_enabled = self
|
||||
.workspace_codex_plugins_enabled(&config, auth.as_ref())
|
||||
.await;
|
||||
let workspace_setting_ms = workspace_setting_started_at.elapsed().as_millis();
|
||||
let skills_manager = self.thread_manager.skills_manager();
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
let fs = self
|
||||
@@ -430,56 +444,124 @@ impl CatalogRequestProcessor {
|
||||
.environment_manager()
|
||||
.default_environment()
|
||||
.map(|environment| environment.get_filesystem());
|
||||
let mut data = Vec::new();
|
||||
for cwd in cwds {
|
||||
let (cwd_abs, config_layer_stack) = match self.resolve_cwd_config(&cwd).await {
|
||||
Ok(resolved) => resolved,
|
||||
Err(message) => {
|
||||
let error_path = cwd.clone();
|
||||
data.push(codex_app_server_protocol::SkillsListEntry {
|
||||
cwd,
|
||||
skills: Vec::new(),
|
||||
errors: vec![codex_app_server_protocol::SkillErrorInfo {
|
||||
path: error_path,
|
||||
message,
|
||||
}],
|
||||
});
|
||||
continue;
|
||||
let mut data = futures::stream::iter(cwds.into_iter().enumerate())
|
||||
.map(|(index, cwd)| {
|
||||
let config = &config;
|
||||
let extra_roots_by_cwd = &extra_roots_by_cwd;
|
||||
let fs = fs.clone();
|
||||
let plugins_manager = &plugins_manager;
|
||||
let skills_manager = &skills_manager;
|
||||
async move {
|
||||
let cwd_started_at = Instant::now();
|
||||
let resolve_cwd_config_started_at = Instant::now();
|
||||
let (cwd_abs, config_layer_stack) =
|
||||
match self.resolve_cwd_config(&cwd).await {
|
||||
Ok(resolved) => resolved,
|
||||
Err(message) => {
|
||||
warn!(
|
||||
cwd = %cwd.display(),
|
||||
total_ms = cwd_started_at.elapsed().as_millis(),
|
||||
resolve_cwd_config_ms = resolve_cwd_config_started_at.elapsed().as_millis(),
|
||||
"skills/list cwd timing failed to resolve cwd config"
|
||||
);
|
||||
let error_path = cwd.clone();
|
||||
return (
|
||||
index,
|
||||
codex_app_server_protocol::SkillsListEntry {
|
||||
cwd,
|
||||
skills: Vec::new(),
|
||||
errors: vec![
|
||||
codex_app_server_protocol::SkillErrorInfo {
|
||||
path: error_path,
|
||||
message,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
let resolve_cwd_config_ms =
|
||||
resolve_cwd_config_started_at.elapsed().as_millis();
|
||||
let extra_roots = extra_roots_by_cwd
|
||||
.get(&cwd)
|
||||
.map_or(&[][..], std::vec::Vec::as_slice);
|
||||
let effective_skill_roots_started_at = Instant::now();
|
||||
let effective_skill_roots = if workspace_codex_plugins_enabled {
|
||||
let plugins_input = config.plugins_config_input();
|
||||
plugins_manager
|
||||
.effective_skill_roots_for_layer_stack(
|
||||
&config_layer_stack,
|
||||
&plugins_input,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let effective_skill_roots_ms =
|
||||
effective_skill_roots_started_at.elapsed().as_millis();
|
||||
let effective_skill_root_count = effective_skill_roots.len();
|
||||
let skills_input = codex_core::skills::SkillsLoadInput::new(
|
||||
cwd_abs.clone(),
|
||||
effective_skill_roots,
|
||||
config_layer_stack,
|
||||
config.bundled_skills_enabled(),
|
||||
);
|
||||
let load_skills_started_at = Instant::now();
|
||||
let outcome = skills_manager
|
||||
.skills_for_cwd_with_extra_user_roots(
|
||||
&skills_input,
|
||||
force_reload,
|
||||
extra_roots,
|
||||
fs,
|
||||
)
|
||||
.await;
|
||||
let load_skills_ms = load_skills_started_at.elapsed().as_millis();
|
||||
let errors = errors_to_info(&outcome.errors);
|
||||
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
|
||||
warn!(
|
||||
cwd = %cwd.display(),
|
||||
total_ms = cwd_started_at.elapsed().as_millis(),
|
||||
resolve_cwd_config_ms,
|
||||
effective_skill_roots_ms,
|
||||
load_skills_ms,
|
||||
extra_root_count = extra_roots.len(),
|
||||
effective_skill_root_count,
|
||||
skill_count = skills.len(),
|
||||
error_count = errors.len(),
|
||||
"skills/list cwd timing"
|
||||
);
|
||||
(
|
||||
index,
|
||||
codex_app_server_protocol::SkillsListEntry {
|
||||
cwd,
|
||||
skills,
|
||||
errors,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
let extra_roots = extra_roots_by_cwd
|
||||
.get(&cwd)
|
||||
.map_or(&[][..], std::vec::Vec::as_slice);
|
||||
let effective_skill_roots = if workspace_codex_plugins_enabled {
|
||||
let plugins_input = config.plugins_config_input();
|
||||
plugins_manager
|
||||
.effective_skill_roots_for_layer_stack(&config_layer_stack, &plugins_input)
|
||||
.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let skills_input = codex_core::skills::SkillsLoadInput::new(
|
||||
cwd_abs.clone(),
|
||||
effective_skill_roots,
|
||||
config_layer_stack,
|
||||
config.bundled_skills_enabled(),
|
||||
);
|
||||
let outcome = skills_manager
|
||||
.skills_for_cwd_with_extra_user_roots(
|
||||
&skills_input,
|
||||
force_reload,
|
||||
extra_roots,
|
||||
fs.clone(),
|
||||
)
|
||||
.await;
|
||||
let errors = errors_to_info(&outcome.errors);
|
||||
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
|
||||
data.push(codex_app_server_protocol::SkillsListEntry {
|
||||
cwd,
|
||||
skills,
|
||||
errors,
|
||||
});
|
||||
}
|
||||
})
|
||||
.buffer_unordered(SKILLS_LIST_CWD_CONCURRENCY)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
data.sort_unstable_by_key(|(index, _)| *index);
|
||||
let data = data.into_iter().map(|(_, entry)| entry).collect::<Vec<_>>();
|
||||
let skill_count = data.iter().map(|entry| entry.skills.len()).sum::<usize>();
|
||||
let error_count = data.iter().map(|entry| entry.errors.len()).sum::<usize>();
|
||||
warn!(
|
||||
cwd_count,
|
||||
total_ms = total_started_at.elapsed().as_millis(),
|
||||
force_reload,
|
||||
extra_roots_ms,
|
||||
extra_root_count,
|
||||
load_config_ms,
|
||||
auth_ms,
|
||||
workspace_setting_ms,
|
||||
workspace_codex_plugins_enabled,
|
||||
has_remote_fs = fs.is_some(),
|
||||
skill_count,
|
||||
error_count,
|
||||
"skills/list timing"
|
||||
);
|
||||
Ok(SkillsListResponse { data })
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user