mirror of
https://github.com/openai/codex.git
synced 2026-05-09 22:02:32 +00:00
Compare commits
9 Commits
xli-codex/
...
codex/viya
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
935cfa3949 | ||
|
|
ae15343243 | ||
|
|
9cbd4c0371 | ||
|
|
f48d18d229 | ||
|
|
1efc5b0681 | ||
|
|
212e89c0f5 | ||
|
|
8357e97075 | ||
|
|
0026ec9e54 | ||
|
|
86f31a2ef9 |
11
MODULE.bazel.lock
generated
11
MODULE.bazel.lock
generated
File diff suppressed because one or more lines are too long
146
codex-rs/Cargo.lock
generated
146
codex-rs/Cargo.lock
generated
@@ -757,6 +757,7 @@ checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-sdk-signin",
|
||||
"aws-sdk-sso",
|
||||
"aws-sdk-ssooidc",
|
||||
"aws-sdk-sts",
|
||||
@@ -767,15 +768,20 @@ dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"base64-simd",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"hex",
|
||||
"http 1.4.0",
|
||||
"p256",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"sha2",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -838,6 +844,28 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-signin"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c084bd63941916e1348cb8d9e05ac2e49bdd40a380e9167702683184c6c6be53"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes",
|
||||
"fastrand",
|
||||
"http 0.2.12",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.91.0"
|
||||
@@ -1180,6 +1208,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
@@ -4415,6 +4449,18 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -5128,6 +5174,20 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
@@ -5161,6 +5221,26 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.3"
|
||||
@@ -5414,6 +5494,16 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
@@ -6808,6 +6898,17 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gzip-header"
|
||||
version = "1.0.0"
|
||||
@@ -9307,6 +9408,18 @@ dependencies = [
|
||||
"supports-color 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
@@ -9736,6 +9849,15 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
@@ -10686,6 +10808,16 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
@@ -11145,6 +11277,20 @@ version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array",
|
||||
"pkcs8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seccompiler"
|
||||
version = "0.5.0"
|
||||
|
||||
@@ -2091,8 +2091,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2104,6 +2114,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
@@ -6177,4 +6188,4 @@
|
||||
}
|
||||
],
|
||||
"title": "ClientRequest"
|
||||
}
|
||||
}
|
||||
@@ -12414,9 +12414,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/v2/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -12428,6 +12438,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
@@ -12437,6 +12448,9 @@
|
||||
"PluginShareUpdateTargetsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/v2/PluginShareDiscoverability"
|
||||
},
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/PluginSharePrincipal"
|
||||
@@ -12445,6 +12459,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
@@ -18396,4 +18411,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocol",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -9007,9 +9007,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginShareUpdateTargetsParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9021,6 +9031,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
@@ -9030,6 +9041,9 @@
|
||||
"PluginShareUpdateTargetsResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
@@ -9038,6 +9052,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
@@ -16263,4 +16278,4 @@
|
||||
},
|
||||
"title": "CodexAppServerProtocolV2",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,19 @@
|
||||
"principalType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginShareUpdateDiscoverability": {
|
||||
"enum": [
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareUpdateDiscoverability"
|
||||
},
|
||||
"remotePluginId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -37,6 +47,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"remotePluginId",
|
||||
"shareTargets"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginShareDiscoverability": {
|
||||
"enum": [
|
||||
"LISTED",
|
||||
"UNLISTED",
|
||||
"PRIVATE"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginSharePrincipal": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -30,6 +38,9 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"discoverability": {
|
||||
"$ref": "#/definitions/PluginShareDiscoverability"
|
||||
},
|
||||
"principals": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSharePrincipal"
|
||||
@@ -38,6 +49,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"discoverability",
|
||||
"principals"
|
||||
],
|
||||
"title": "PluginShareUpdateTargetsResponse",
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateDiscoverability.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/PluginShareUpdateDiscoverability.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 PluginShareUpdateDiscoverability = "UNLISTED" | "PRIVATE";
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
// 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";
|
||||
import type { PluginShareUpdateDiscoverability } from "./PluginShareUpdateDiscoverability";
|
||||
|
||||
export type PluginShareUpdateTargetsParams = { remotePluginId: string, shareTargets: Array<PluginShareTarget>, };
|
||||
export type PluginShareUpdateTargetsParams = { remotePluginId: string, discoverability: PluginShareUpdateDiscoverability, shareTargets: Array<PluginShareTarget>, };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginShareDiscoverability } from "./PluginShareDiscoverability";
|
||||
import type { PluginSharePrincipal } from "./PluginSharePrincipal";
|
||||
|
||||
export type PluginShareUpdateTargetsResponse = { principals: Array<PluginSharePrincipal>, };
|
||||
export type PluginShareUpdateTargetsResponse = { principals: Array<PluginSharePrincipal>, discoverability: PluginShareDiscoverability, };
|
||||
|
||||
@@ -287,6 +287,7 @@ export type { PluginSharePrincipalType } from "./PluginSharePrincipalType";
|
||||
export type { PluginShareSaveParams } from "./PluginShareSaveParams";
|
||||
export type { PluginShareSaveResponse } from "./PluginShareSaveResponse";
|
||||
export type { PluginShareTarget } from "./PluginShareTarget";
|
||||
export type { PluginShareUpdateDiscoverability } from "./PluginShareUpdateDiscoverability";
|
||||
export type { PluginShareUpdateTargetsParams } from "./PluginShareUpdateTargetsParams";
|
||||
export type { PluginShareUpdateTargetsResponse } from "./PluginShareUpdateTargetsResponse";
|
||||
export type { PluginSkillReadParams } from "./PluginSkillReadParams";
|
||||
|
||||
@@ -218,6 +218,7 @@ pub struct PluginShareSaveResponse {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareUpdateTargetsParams {
|
||||
pub remote_plugin_id: String,
|
||||
pub discoverability: PluginShareUpdateDiscoverability,
|
||||
pub share_targets: Vec<PluginShareTarget>,
|
||||
}
|
||||
|
||||
@@ -226,6 +227,7 @@ pub struct PluginShareUpdateTargetsParams {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginShareUpdateTargetsResponse {
|
||||
pub principals: Vec<PluginSharePrincipal>,
|
||||
pub discoverability: PluginShareDiscoverability,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -275,6 +277,17 @@ pub enum PluginShareDiscoverability {
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginShareUpdateDiscoverability {
|
||||
#[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 {
|
||||
|
||||
@@ -2936,6 +2936,7 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(PluginShareUpdateTargetsParams {
|
||||
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
|
||||
discoverability: PluginShareUpdateDiscoverability::Unlisted,
|
||||
share_targets: vec![PluginShareTarget {
|
||||
principal_type: PluginSharePrincipalType::Group,
|
||||
principal_id: "group-1".to_string(),
|
||||
@@ -2944,6 +2945,7 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
|
||||
.unwrap(),
|
||||
json!({
|
||||
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
|
||||
"discoverability": "UNLISTED",
|
||||
"shareTargets": [{
|
||||
"principalType": "group",
|
||||
"principalId": "group-1",
|
||||
@@ -2958,6 +2960,7 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
|
||||
principal_id: "user-1".to_string(),
|
||||
name: "Gavin".to_string(),
|
||||
}],
|
||||
discoverability: PluginShareDiscoverability::Unlisted,
|
||||
})
|
||||
.unwrap(),
|
||||
json!({
|
||||
@@ -2966,6 +2969,7 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
|
||||
"principalId": "user-1",
|
||||
"name": "Gavin",
|
||||
}],
|
||||
"discoverability": "UNLISTED",
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ 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::PluginShareUpdateDiscoverability;
|
||||
use codex_app_server_protocol::PluginShareUpdateTargetsParams;
|
||||
use codex_app_server_protocol::PluginShareUpdateTargetsResponse;
|
||||
use codex_app_server_protocol::PluginSkillReadParams;
|
||||
|
||||
@@ -133,6 +133,33 @@ fn remote_plugin_share_discoverability(
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_plugin_share_update_discoverability(
|
||||
discoverability: PluginShareUpdateDiscoverability,
|
||||
) -> codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability {
|
||||
match discoverability {
|
||||
PluginShareUpdateDiscoverability::Unlisted => {
|
||||
codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability::Unlisted
|
||||
}
|
||||
PluginShareUpdateDiscoverability::Private => {
|
||||
codex_core_plugins::remote::RemotePluginShareUpdateDiscoverability::Private
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_client_plugin_share_targets(
|
||||
targets: &[PluginShareTarget],
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
if targets
|
||||
.iter()
|
||||
.any(|target| target.principal_type == PluginSharePrincipalType::Workspace)
|
||||
{
|
||||
return Err(invalid_request(
|
||||
"shareTargets cannot include workspace principals; use discoverability UNLISTED for workspace link access",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remote_plugin_share_targets(
|
||||
targets: Vec<PluginShareTarget>,
|
||||
) -> Vec<codex_core_plugins::remote::RemotePluginShareTarget> {
|
||||
@@ -729,9 +756,17 @@ impl PluginRequestProcessor {
|
||||
}
|
||||
if remote_plugin_id.is_some() && (discoverability.is_some() || share_targets.is_some()) {
|
||||
return Err(invalid_request(
|
||||
"discoverability and shareTargets are only supported when creating a plugin share; use plugin/share/updateTargets to update share targets",
|
||||
"discoverability and shareTargets are only supported when creating a plugin share; use plugin/share/updateTargets to update share settings",
|
||||
));
|
||||
}
|
||||
if discoverability == Some(PluginShareDiscoverability::Listed) {
|
||||
return Err(invalid_request(
|
||||
"discoverability LISTED is not supported for plugin/share/save; use UNLISTED or PRIVATE",
|
||||
));
|
||||
}
|
||||
if let Some(share_targets) = share_targets.as_ref() {
|
||||
validate_client_plugin_share_targets(share_targets)?;
|
||||
}
|
||||
|
||||
let remote_plugin_service_config = RemotePluginServiceConfig {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
@@ -765,11 +800,14 @@ impl PluginRequestProcessor {
|
||||
let (config, auth) = self.load_plugin_share_config_and_auth().await?;
|
||||
let PluginShareUpdateTargetsParams {
|
||||
remote_plugin_id,
|
||||
discoverability,
|
||||
share_targets,
|
||||
} = params;
|
||||
if remote_plugin_id.is_empty() || !is_valid_remote_plugin_id(&remote_plugin_id) {
|
||||
return Err(invalid_request("invalid remote plugin id"));
|
||||
}
|
||||
validate_client_plugin_share_targets(&share_targets)?;
|
||||
let requested_share_targets = share_targets.clone();
|
||||
|
||||
let remote_plugin_service_config = RemotePluginServiceConfig {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
@@ -779,6 +817,7 @@ impl PluginRequestProcessor {
|
||||
auth.as_ref(),
|
||||
&remote_plugin_id,
|
||||
remote_plugin_share_targets(share_targets),
|
||||
remote_plugin_share_update_discoverability(discoverability),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -790,7 +829,14 @@ impl PluginRequestProcessor {
|
||||
.principals
|
||||
.into_iter()
|
||||
.map(plugin_share_principal_from_remote)
|
||||
.filter(|principal| {
|
||||
requested_share_targets.iter().any(|target| {
|
||||
target.principal_type == principal.principal_type
|
||||
&& target.principal_id == principal.principal_id
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
discoverability: remote_plugin_share_discoverability_to_info(result.discoverability),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1487,6 +1533,22 @@ fn remote_plugin_share_context_to_info(
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_plugin_share_discoverability_to_info(
|
||||
discoverability: codex_core_plugins::remote::RemotePluginShareDiscoverability,
|
||||
) -> PluginShareDiscoverability {
|
||||
match discoverability {
|
||||
codex_core_plugins::remote::RemotePluginShareDiscoverability::Listed => {
|
||||
PluginShareDiscoverability::Listed
|
||||
}
|
||||
codex_core_plugins::remote::RemotePluginShareDiscoverability::Unlisted => {
|
||||
PluginShareDiscoverability::Unlisted
|
||||
}
|
||||
codex_core_plugins::remote::RemotePluginShareDiscoverability::Private => {
|
||||
PluginShareDiscoverability::Private
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_plugin_detail_to_info(
|
||||
detail: RemoteCatalogPluginDetail,
|
||||
apps: Vec<AppSummary>,
|
||||
|
||||
@@ -219,7 +219,7 @@ async fn plugin_share_save_forwards_access_policy() -> Result<()> {
|
||||
.and(body_json(json!({
|
||||
"file_id": "file_123",
|
||||
"etag": "\"upload_etag_123\"",
|
||||
"discoverability": "PRIVATE",
|
||||
"discoverability": "UNLISTED",
|
||||
"share_targets": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
@@ -227,7 +227,7 @@ async fn plugin_share_save_forwards_access_policy() -> Result<()> {
|
||||
},
|
||||
{
|
||||
"principal_type": "workspace",
|
||||
"principal_id": "workspace-1",
|
||||
"principal_id": "account-123",
|
||||
},
|
||||
],
|
||||
})))
|
||||
@@ -247,16 +247,12 @@ async fn plugin_share_save_forwards_access_policy() -> Result<()> {
|
||||
"plugin/share/save",
|
||||
Some(json!({
|
||||
"pluginPath": expected_plugin_path,
|
||||
"discoverability": "PRIVATE",
|
||||
"discoverability": "UNLISTED",
|
||||
"shareTargets": [
|
||||
{
|
||||
"principalType": "user",
|
||||
"principalId": "user-1",
|
||||
},
|
||||
{
|
||||
"principalType": "workspace",
|
||||
"principalId": "workspace-1",
|
||||
},
|
||||
],
|
||||
})),
|
||||
)
|
||||
@@ -279,6 +275,124 @@ async fn plugin_share_save_forwards_access_policy() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_share_save_rejects_listed_discoverability() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let plugin_root = TempDir::new()?;
|
||||
let plugin_path = write_test_plugin(plugin_root.path(), "demo-plugin")?;
|
||||
let server = MockServer::start().await;
|
||||
write_remote_plugin_config(codex_home.path(), &format!("{}/backend-api", server.uri()))?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/share/save",
|
||||
Some(json!({
|
||||
"pluginPath": AbsolutePathBuf::try_from(plugin_path)?,
|
||||
"discoverability": "LISTED",
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.error.code, -32600);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"discoverability LISTED is not supported for plugin/share/save; use UNLISTED or PRIVATE"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_share_rejects_workspace_targets_from_client() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let plugin_root = TempDir::new()?;
|
||||
let plugin_path = write_test_plugin(plugin_root.path(), "demo-plugin")?;
|
||||
let server = MockServer::start().await;
|
||||
write_remote_plugin_config(codex_home.path(), &format!("{}/backend-api", server.uri()))?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/share/save",
|
||||
Some(json!({
|
||||
"pluginPath": AbsolutePathBuf::try_from(plugin_path)?,
|
||||
"discoverability": "UNLISTED",
|
||||
"shareTargets": [
|
||||
{
|
||||
"principalType": "workspace",
|
||||
"principalId": "account-123",
|
||||
},
|
||||
],
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.error.code, -32600);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"shareTargets cannot include workspace principals; use discoverability UNLISTED for workspace link access"
|
||||
);
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/share/updateTargets",
|
||||
Some(json!({
|
||||
"remotePluginId": "plugins_123",
|
||||
"discoverability": "UNLISTED",
|
||||
"shareTargets": [
|
||||
{
|
||||
"principalType": "workspace",
|
||||
"principalId": "account-123",
|
||||
},
|
||||
],
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.error.code, -32600);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"shareTargets cannot include workspace principals; use discoverability UNLISTED for workspace link access"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_share_save_rejects_access_policy_for_existing_plugin() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -323,7 +437,7 @@ async fn plugin_share_save_rejects_access_policy_for_existing_plugin() -> Result
|
||||
assert_eq!(error.error.code, -32600);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"discoverability and shareTargets are only supported when creating a plugin share; use plugin/share/updateTargets to update share targets"
|
||||
"discoverability and shareTargets are only supported when creating a plugin share; use plugin/share/updateTargets to update share settings"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -420,24 +534,39 @@ async fn plugin_share_update_targets_updates_share_targets() -> Result<()> {
|
||||
)?;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path("/backend-api/public/plugins/plugins_123/shares"))
|
||||
.and(path("/backend-api/ps/plugins/plugins_123/shares"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.and(body_json(json!({
|
||||
"discoverability": "UNLISTED",
|
||||
"targets": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
"principal_id": "user-1",
|
||||
},
|
||||
{
|
||||
"principal_type": "workspace",
|
||||
"principal_id": "account-123",
|
||||
},
|
||||
],
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"principals": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
"principal_id": "owner-1",
|
||||
"name": "Owner",
|
||||
},
|
||||
{
|
||||
"principal_type": "user",
|
||||
"principal_id": "user-1",
|
||||
"name": "Gavin",
|
||||
},
|
||||
{
|
||||
"principal_type": "workspace",
|
||||
"principal_id": "account-123",
|
||||
"name": "Workspace",
|
||||
},
|
||||
],
|
||||
})))
|
||||
.expect(1)
|
||||
@@ -451,6 +580,7 @@ async fn plugin_share_update_targets_updates_share_targets() -> Result<()> {
|
||||
"plugin/share/updateTargets",
|
||||
Some(json!({
|
||||
"remotePluginId": "plugins_123",
|
||||
"discoverability": "UNLISTED",
|
||||
"shareTargets": [
|
||||
{
|
||||
"principalType": "user",
|
||||
@@ -476,6 +606,7 @@ async fn plugin_share_update_targets_updates_share_targets() -> Result<()> {
|
||||
principal_id: "user-1".to_string(),
|
||||
name: "Gavin".to_string(),
|
||||
}],
|
||||
discoverability: codex_app_server_protocol::PluginShareDiscoverability::Unlisted,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
|
||||
@@ -13,7 +13,7 @@ path = "src/lib.rs"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
aws-config = { workspace = true }
|
||||
aws-config = { workspace = true, features = ["credentials-login"] }
|
||||
aws-credential-types = { workspace = true }
|
||||
aws-sigv4 = { workspace = true }
|
||||
aws-types = { workspace = true }
|
||||
|
||||
@@ -35,6 +35,7 @@ pub use share::RemotePluginSharePrincipal;
|
||||
pub use share::RemotePluginSharePrincipalType;
|
||||
pub use share::RemotePluginShareSaveResult;
|
||||
pub use share::RemotePluginShareTarget;
|
||||
pub use share::RemotePluginShareUpdateDiscoverability;
|
||||
pub use share::RemotePluginShareUpdateTargetsResult;
|
||||
pub use share::delete_remote_plugin_share;
|
||||
pub use share::list_remote_plugin_shares;
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct RemotePluginShareAccessPolicy {
|
||||
pub share_targets: Option<Vec<RemotePluginShareTarget>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RemotePluginShareDiscoverability {
|
||||
Listed,
|
||||
@@ -40,6 +40,13 @@ pub enum RemotePluginShareDiscoverability {
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RemotePluginShareUpdateDiscoverability {
|
||||
Unlisted,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum RemotePluginSharePrincipalType {
|
||||
@@ -64,6 +71,7 @@ pub struct RemotePluginSharePrincipal {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RemotePluginShareUpdateTargetsResult {
|
||||
pub principals: Vec<RemotePluginSharePrincipal>,
|
||||
pub discoverability: RemotePluginShareDiscoverability,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
@@ -100,12 +108,14 @@ struct RemoteWorkspacePluginCreateResponse {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
struct RemotePluginShareUpdateTargetsRequest {
|
||||
discoverability: RemotePluginShareUpdateDiscoverability,
|
||||
targets: Vec<RemotePluginShareTarget>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
struct RemotePluginShareUpdateTargetsResponse {
|
||||
principals: Vec<RemotePluginSharePrincipal>,
|
||||
discoverability: Option<RemotePluginShareDiscoverability>,
|
||||
}
|
||||
|
||||
pub async fn save_remote_plugin_share(
|
||||
@@ -137,6 +147,9 @@ pub async fn save_remote_plugin_share(
|
||||
.etag
|
||||
.ok_or(RemotePluginCatalogError::MissingUploadEtag)?;
|
||||
put_workspace_plugin_upload(&upload.upload_url, archive_bytes).await?;
|
||||
let share_targets = access_policy.share_targets;
|
||||
let share_targets =
|
||||
ensure_unlisted_workspace_target(auth, access_policy.discoverability, share_targets)?;
|
||||
let response = finalize_workspace_plugin_upload(
|
||||
config,
|
||||
auth,
|
||||
@@ -145,7 +158,7 @@ pub async fn save_remote_plugin_share(
|
||||
file_id: upload.file_id,
|
||||
etag,
|
||||
discoverability: access_policy.discoverability,
|
||||
share_targets: access_policy.share_targets,
|
||||
share_targets,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -245,19 +258,64 @@ pub async fn update_remote_plugin_share_targets(
|
||||
auth: Option<&CodexAuth>,
|
||||
remote_plugin_id: &str,
|
||||
targets: Vec<RemotePluginShareTarget>,
|
||||
discoverability: RemotePluginShareUpdateDiscoverability,
|
||||
) -> Result<RemotePluginShareUpdateTargetsResult, RemotePluginCatalogError> {
|
||||
let auth = ensure_chatgpt_auth(auth)?;
|
||||
let target_discoverability = match discoverability {
|
||||
RemotePluginShareUpdateDiscoverability::Unlisted => {
|
||||
RemotePluginShareDiscoverability::Unlisted
|
||||
}
|
||||
RemotePluginShareUpdateDiscoverability::Private => {
|
||||
RemotePluginShareDiscoverability::Private
|
||||
}
|
||||
};
|
||||
let targets =
|
||||
ensure_unlisted_workspace_target(auth, Some(target_discoverability), Some(targets))?
|
||||
.unwrap_or_default();
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/public/plugins/{remote_plugin_id}/shares");
|
||||
let url = format!("{base_url}/ps/plugins/{remote_plugin_id}/shares");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.put(&url), auth)?
|
||||
.json(&RemotePluginShareUpdateTargetsRequest { targets });
|
||||
let request = authenticated_request(client.put(&url), auth)?.json(
|
||||
&RemotePluginShareUpdateTargetsRequest {
|
||||
discoverability,
|
||||
targets,
|
||||
},
|
||||
);
|
||||
let response: RemotePluginShareUpdateTargetsResponse = send_and_decode(request, &url).await?;
|
||||
Ok(RemotePluginShareUpdateTargetsResult {
|
||||
principals: response.principals,
|
||||
// TODO: Remove this fallback once deployed plugin-service responses always include
|
||||
// discoverability per the API schema.
|
||||
discoverability: response.discoverability.unwrap_or(target_discoverability),
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_unlisted_workspace_target(
|
||||
auth: &CodexAuth,
|
||||
discoverability: Option<RemotePluginShareDiscoverability>,
|
||||
targets: Option<Vec<RemotePluginShareTarget>>,
|
||||
) -> Result<Option<Vec<RemotePluginShareTarget>>, RemotePluginCatalogError> {
|
||||
if discoverability != Some(RemotePluginShareDiscoverability::Unlisted) {
|
||||
return Ok(targets);
|
||||
}
|
||||
let account_id = auth.get_account_id().ok_or_else(|| {
|
||||
RemotePluginCatalogError::UnexpectedResponse(
|
||||
"workspace plugin share requires an account id".to_string(),
|
||||
)
|
||||
})?;
|
||||
let mut targets = targets.unwrap_or_default();
|
||||
if !targets.iter().any(|target| {
|
||||
target.principal_type == RemotePluginSharePrincipalType::Workspace
|
||||
&& target.principal_id == account_id
|
||||
}) {
|
||||
targets.push(RemotePluginShareTarget {
|
||||
principal_type: RemotePluginSharePrincipalType::Workspace,
|
||||
principal_id: account_id,
|
||||
});
|
||||
}
|
||||
Ok(Some(targets))
|
||||
}
|
||||
|
||||
async fn fetch_created_workspace_plugins(
|
||||
config: &RemotePluginServiceConfig,
|
||||
auth: &CodexAuth,
|
||||
|
||||
@@ -204,7 +204,7 @@ async fn save_remote_plugin_share_creates_workspace_plugin() {
|
||||
.and(body_json(json!({
|
||||
"file_id": "file_123",
|
||||
"etag": "\"upload_etag_123\"",
|
||||
"discoverability": "PRIVATE",
|
||||
"discoverability": "UNLISTED",
|
||||
"share_targets": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
@@ -212,7 +212,7 @@ async fn save_remote_plugin_share_creates_workspace_plugin() {
|
||||
},
|
||||
{
|
||||
"principal_type": "workspace",
|
||||
"principal_id": "workspace-1",
|
||||
"principal_id": "account_id",
|
||||
},
|
||||
],
|
||||
})))
|
||||
@@ -231,17 +231,11 @@ async fn save_remote_plugin_share_creates_workspace_plugin() {
|
||||
&plugin_path,
|
||||
/*remote_plugin_id*/ None,
|
||||
RemotePluginShareAccessPolicy {
|
||||
discoverability: Some(RemotePluginShareDiscoverability::Private),
|
||||
share_targets: Some(vec![
|
||||
RemotePluginShareTarget {
|
||||
principal_type: RemotePluginSharePrincipalType::User,
|
||||
principal_id: "user-1".to_string(),
|
||||
},
|
||||
RemotePluginShareTarget {
|
||||
principal_type: RemotePluginSharePrincipalType::Workspace,
|
||||
principal_id: "workspace-1".to_string(),
|
||||
},
|
||||
]),
|
||||
discoverability: Some(RemotePluginShareDiscoverability::Unlisted),
|
||||
share_targets: Some(vec![RemotePluginShareTarget {
|
||||
principal_type: RemotePluginSharePrincipalType::User,
|
||||
principal_id: "user-1".to_string(),
|
||||
}]),
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -401,10 +395,11 @@ async fn update_remote_plugin_share_targets_updates_targets() {
|
||||
let auth = test_auth();
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path("/backend-api/public/plugins/plugins_123/shares"))
|
||||
.and(path("/backend-api/ps/plugins/plugins_123/shares"))
|
||||
.and(header("authorization", "Bearer Access Token"))
|
||||
.and(header("chatgpt-account-id", "account_id"))
|
||||
.and(body_json(json!({
|
||||
"discoverability": "UNLISTED",
|
||||
"targets": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
@@ -414,6 +409,10 @@ async fn update_remote_plugin_share_targets_updates_targets() {
|
||||
"principal_type": "group",
|
||||
"principal_id": "group-1",
|
||||
},
|
||||
{
|
||||
"principal_type": "workspace",
|
||||
"principal_id": "account_id",
|
||||
},
|
||||
],
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
@@ -429,6 +428,7 @@ async fn update_remote_plugin_share_targets_updates_targets() {
|
||||
"name": "Engineering",
|
||||
},
|
||||
],
|
||||
"discoverability": "UNLISTED",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
@@ -448,6 +448,7 @@ async fn update_remote_plugin_share_targets_updates_targets() {
|
||||
principal_id: "group-1".to_string(),
|
||||
},
|
||||
],
|
||||
RemotePluginShareUpdateDiscoverability::Unlisted,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -467,6 +468,65 @@ async fn update_remote_plugin_share_targets_updates_targets() {
|
||||
name: "Engineering".to_string(),
|
||||
},
|
||||
],
|
||||
discoverability: RemotePluginShareDiscoverability::Unlisted,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_remote_plugin_share_targets_falls_back_to_requested_discoverability() {
|
||||
let server = MockServer::start().await;
|
||||
let config = test_config(&server);
|
||||
let auth = test_auth();
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path("/backend-api/ps/plugins/plugins_123/shares"))
|
||||
.and(header("authorization", "Bearer Access Token"))
|
||||
.and(header("chatgpt-account-id", "account_id"))
|
||||
.and(body_json(json!({
|
||||
"discoverability": "PRIVATE",
|
||||
"targets": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
"principal_id": "user-1",
|
||||
},
|
||||
],
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"principals": [
|
||||
{
|
||||
"principal_type": "user",
|
||||
"principal_id": "user-1",
|
||||
"name": "Gavin",
|
||||
},
|
||||
],
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = update_remote_plugin_share_targets(
|
||||
&config,
|
||||
Some(&auth),
|
||||
"plugins_123",
|
||||
vec![RemotePluginShareTarget {
|
||||
principal_type: RemotePluginSharePrincipalType::User,
|
||||
principal_id: "user-1".to_string(),
|
||||
}],
|
||||
RemotePluginShareUpdateDiscoverability::Private,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
RemotePluginShareUpdateTargetsResult {
|
||||
principals: vec![RemotePluginSharePrincipal {
|
||||
principal_type: RemotePluginSharePrincipalType::User,
|
||||
principal_id: "user-1".to_string(),
|
||||
name: "Gavin".to_string(),
|
||||
}],
|
||||
discoverability: RemotePluginShareDiscoverability::Private,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1705,8 +1705,12 @@ async fn proposed_execpolicy_amendment_is_present_when_heuristics_allow() {
|
||||
async fn proposed_execpolicy_amendment_is_suppressed_when_policy_matches_allow() {
|
||||
assert_exec_approval_requirement_for_command(
|
||||
ExecApprovalRequirementScenario {
|
||||
policy_src: Some(r#"prefix_rule(pattern=["echo"], decision="allow")"#.to_string()),
|
||||
command: vec!["echo".to_string(), "safe".to_string()],
|
||||
policy_src: Some(r#"prefix_rule(pattern=["python3"], decision="allow")"#.to_string()),
|
||||
command: vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(1)".to_string(),
|
||||
],
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
file_system_sandbox_policy: read_only_file_system_sandbox_policy(),
|
||||
|
||||
@@ -26,6 +26,7 @@ use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::default_exec_approval_requirement;
|
||||
use crate::tools::sandboxing::sandbox_override_for_first_attempt;
|
||||
use codex_hooks::PermissionRequestDecision;
|
||||
use codex_otel::ToolDecisionSource;
|
||||
use codex_protocol::error::CodexErr;
|
||||
@@ -148,6 +149,11 @@ impl ToolOrchestrator {
|
||||
let requirement = tool.exec_approval_requirement(req).unwrap_or_else(|| {
|
||||
default_exec_approval_requirement(approval_policy, &file_system_sandbox_policy)
|
||||
});
|
||||
let sandbox_override = sandbox_override_for_first_attempt(
|
||||
tool.sandbox_permissions(req),
|
||||
&requirement,
|
||||
&file_system_sandbox_policy,
|
||||
);
|
||||
match requirement {
|
||||
ExecApprovalRequirement::Skip { .. } => {
|
||||
if strict_auto_review {
|
||||
@@ -214,7 +220,7 @@ impl ToolOrchestrator {
|
||||
|
||||
// 2) First attempt under the selected sandbox.
|
||||
let managed_network_active = turn_ctx.network.is_some();
|
||||
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
|
||||
let initial_sandbox = match sandbox_override {
|
||||
SandboxOverride::BypassSandboxFirstAttempt => SandboxType::None,
|
||||
SandboxOverride::NoOverride => self.sandbox.select_initial(
|
||||
&file_system_sandbox_policy,
|
||||
|
||||
@@ -27,13 +27,11 @@ use crate::tools::sandboxing::ApprovalCtx;
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
use crate::tools::sandboxing::PermissionRequestPayload;
|
||||
use crate::tools::sandboxing::SandboxAttempt;
|
||||
use crate::tools::sandboxing::SandboxOverride;
|
||||
use crate::tools::sandboxing::Sandboxable;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::managed_network_for_sandbox_permissions;
|
||||
use crate::tools::sandboxing::sandbox_override_for_first_attempt;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
@@ -209,8 +207,8 @@ impl Approvable<ShellRequest> for ShellRuntime {
|
||||
))
|
||||
}
|
||||
|
||||
fn sandbox_mode_for_first_attempt(&self, req: &ShellRequest) -> SandboxOverride {
|
||||
sandbox_override_for_first_attempt(req.sandbox_permissions, &req.exec_approval_requirement)
|
||||
fn sandbox_permissions(&self, req: &ShellRequest) -> SandboxPermissions {
|
||||
req.sandbox_permissions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,11 @@ use crate::tools::sandboxing::ApprovalCtx;
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
use crate::tools::sandboxing::PermissionRequestPayload;
|
||||
use crate::tools::sandboxing::SandboxAttempt;
|
||||
use crate::tools::sandboxing::SandboxOverride;
|
||||
use crate::tools::sandboxing::Sandboxable;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::managed_network_for_sandbox_permissions;
|
||||
use crate::tools::sandboxing::sandbox_override_for_first_attempt;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use crate::unified_exec::NoopSpawnLifecycle;
|
||||
use crate::unified_exec::UnifiedExecError;
|
||||
@@ -211,8 +209,8 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
|
||||
))
|
||||
}
|
||||
|
||||
fn sandbox_mode_for_first_attempt(&self, req: &UnifiedExecRequest) -> SandboxOverride {
|
||||
sandbox_override_for_first_attempt(req.sandbox_permissions, &req.exec_approval_requirement)
|
||||
fn sandbox_permissions(&self, req: &UnifiedExecRequest) -> SandboxPermissions {
|
||||
req.sandbox_permissions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -247,18 +247,27 @@ pub(crate) enum SandboxOverride {
|
||||
pub(crate) fn sandbox_override_for_first_attempt(
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
exec_approval_requirement: &ExecApprovalRequirement,
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
) -> SandboxOverride {
|
||||
// ExecPolicy `Allow` can intentionally imply full trust (Skip + bypass_sandbox=true),
|
||||
// which supersedes `with_additional_permissions` sandboxed execution hints.
|
||||
if sandbox_permissions.requires_escalated_permissions()
|
||||
|| matches!(
|
||||
exec_approval_requirement,
|
||||
ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: true,
|
||||
..
|
||||
}
|
||||
)
|
||||
{
|
||||
if matches!(
|
||||
exec_approval_requirement,
|
||||
ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: true,
|
||||
..
|
||||
}
|
||||
) {
|
||||
return SandboxOverride::BypassSandboxFirstAttempt;
|
||||
}
|
||||
|
||||
// Deny-read restrictions suppress explicit escalation because that path
|
||||
// would otherwise discard the filesystem policy entirely.
|
||||
if file_system_sandbox_policy.has_denied_read_restrictions() {
|
||||
return SandboxOverride::NoOverride;
|
||||
}
|
||||
|
||||
if sandbox_permissions.requires_escalated_permissions() {
|
||||
SandboxOverride::BypassSandboxFirstAttempt
|
||||
} else {
|
||||
SandboxOverride::NoOverride
|
||||
@@ -288,11 +297,10 @@ pub(crate) trait Approvable<Req> {
|
||||
// requests touching a subset can be auto-approved.
|
||||
fn approval_keys(&self, req: &Req) -> Vec<Self::ApprovalKey>;
|
||||
|
||||
/// Some tools may request to skip the sandbox on the first attempt
|
||||
/// (e.g., when the request explicitly asks for escalated permissions).
|
||||
/// Defaults to `NoOverride`.
|
||||
fn sandbox_mode_for_first_attempt(&self, _req: &Req) -> SandboxOverride {
|
||||
SandboxOverride::NoOverride
|
||||
/// Return per-request sandbox permissions for first-attempt sandbox
|
||||
/// selection. Most tools use the ambient sandbox policy unchanged.
|
||||
fn sandbox_permissions(&self, _req: &Req) -> SandboxPermissions {
|
||||
SandboxPermissions::UseDefault
|
||||
}
|
||||
|
||||
fn should_bypass_approval(&self, policy: AskForApproval, already_approved: bool) -> bool {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use super::*;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -120,6 +124,7 @@ fn additional_permissions_allow_bypass_sandbox_first_attempt_when_execpolicy_ski
|
||||
bypass_sandbox: true,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
&FileSystemSandboxPolicy::default(),
|
||||
),
|
||||
SandboxOverride::BypassSandboxFirstAttempt
|
||||
);
|
||||
@@ -134,7 +139,43 @@ fn guardian_bypasses_sandbox_for_explicit_escalation_on_first_attempt() {
|
||||
bypass_sandbox: false,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
&FileSystemSandboxPolicy::default(),
|
||||
),
|
||||
SandboxOverride::BypassSandboxFirstAttempt
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deny_read_blocks_explicit_escalation_but_preserves_policy_bypass() {
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
}]);
|
||||
|
||||
assert_eq!(
|
||||
sandbox_override_for_first_attempt(
|
||||
SandboxPermissions::RequireEscalated,
|
||||
&ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: false,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
&file_system_policy,
|
||||
),
|
||||
SandboxOverride::NoOverride,
|
||||
"explicit escalation would drop deny-read filesystem policy, so keep the first attempt sandboxed",
|
||||
);
|
||||
assert_eq!(
|
||||
sandbox_override_for_first_attempt(
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
&ExecApprovalRequirement::Skip {
|
||||
bypass_sandbox: true,
|
||||
proposed_execpolicy_amendment: None,
|
||||
},
|
||||
&file_system_policy,
|
||||
),
|
||||
SandboxOverride::BypassSandboxFirstAttempt,
|
||||
"exec-policy allow rules intentionally bypass sandbox even when deny-read entries exist",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use super::mantle::aws_auth_config;
|
||||
use super::mantle::region_from_config;
|
||||
|
||||
const AWS_BEARER_TOKEN_BEDROCK_ENV_VAR: &str = "AWS_BEARER_TOKEN_BEDROCK";
|
||||
const LEGACY_SESSION_ID_HEADER: &str = "session_id";
|
||||
|
||||
pub(super) enum BedrockAuthMethod {
|
||||
EnvBearerToken { token: String, region: String },
|
||||
@@ -87,10 +86,18 @@ fn aws_auth_error_to_auth_error(error: AwsAuthError) -> AuthError {
|
||||
}
|
||||
|
||||
fn remove_headers_not_preserved_by_bedrock_mantle(headers: &mut HeaderMap) {
|
||||
// The Bedrock Mantle front door does not preserve this legacy OpenAI header
|
||||
// for SigV4 verification. Signing it makes the richer Codex agent request
|
||||
// fail even though raw Responses requests work.
|
||||
headers.remove(LEGACY_SESSION_ID_HEADER);
|
||||
// The Bedrock Mantle front door does not preserve legacy OpenAI
|
||||
// compatibility headers that use snake_case, such as `session_id` and
|
||||
// `thread_id`, before SigV4 verification. Signing that header class makes
|
||||
// richer Codex agent requests fail even though raw Responses requests work.
|
||||
let headers_to_remove = headers
|
||||
.keys()
|
||||
.filter(|name| name.as_str().contains('_'))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for name in headers_to_remove {
|
||||
headers.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// AWS SigV4 auth provider for Bedrock Mantle OpenAI-compatible requests.
|
||||
@@ -182,10 +189,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bedrock_mantle_sigv4_strips_legacy_session_id_header() {
|
||||
fn bedrock_mantle_sigv4_strips_headers_not_preserved_by_mantle() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
LEGACY_SESSION_ID_HEADER,
|
||||
"session_id",
|
||||
HeaderValue::from_static("019dae79-15c3-70c3-8736-3219b8602b37"),
|
||||
);
|
||||
headers.insert(
|
||||
"thread_id",
|
||||
HeaderValue::from_static("019dae79-15c3-70c3-8736-3219b8602b37"),
|
||||
);
|
||||
headers.insert(
|
||||
"future_identity_header",
|
||||
HeaderValue::from_static("019dae79-15c3-70c3-8736-3219b8602b37"),
|
||||
);
|
||||
headers.insert(
|
||||
@@ -195,7 +210,9 @@ mod tests {
|
||||
|
||||
remove_headers_not_preserved_by_bedrock_mantle(&mut headers);
|
||||
|
||||
assert!(!headers.contains_key(LEGACY_SESSION_ID_HEADER));
|
||||
assert!(!headers.contains_key("session_id"));
|
||||
assert!(!headers.contains_key("thread_id"));
|
||||
assert!(!headers.contains_key("future_identity_header"));
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("x-client-request-id")
|
||||
|
||||
Reference in New Issue
Block a user