Skip to content

Commit b1adbc0

Browse files
committed
Reimplement skills loading using SkillsManager + skills/list op.
1 parent 190fa9e commit b1adbc0

File tree

30 files changed

+544
-111
lines changed

30 files changed

+544
-111
lines changed

codex-rs/app-server-protocol/src/protocol/common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ client_request_definitions! {
121121
params: v2::ThreadCompactParams,
122122
response: v2::ThreadCompactResponse,
123123
},
124+
SkillsList => "skills/list" {
125+
params: v2::SkillsListParams,
126+
response: v2::SkillsListResponse,
127+
},
124128
TurnStart => "turn/start" {
125129
params: v2::TurnStartParams,
126130
response: v2::TurnStartResponse,

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
2121
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
2222
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
2323
use codex_protocol::protocol::SessionSource as CoreSessionSource;
24+
use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo;
25+
use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata;
26+
use codex_protocol::protocol::SkillScope as CoreSkillScope;
2427
use codex_protocol::protocol::TokenUsage as CoreTokenUsage;
2528
use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo;
2629
use codex_protocol::user_input::UserInput as CoreUserInput;
@@ -966,6 +969,86 @@ pub struct ThreadCompactParams {
966969
#[ts(export_to = "v2/")]
967970
pub struct ThreadCompactResponse {}
968971

972+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
973+
#[serde(rename_all = "camelCase")]
974+
#[ts(export_to = "v2/")]
975+
pub struct SkillsListParams {
976+
#[serde(skip_serializing_if = "Option::is_none")]
977+
pub cwds: Option<Vec<PathBuf>>,
978+
}
979+
980+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
981+
#[serde(rename_all = "camelCase")]
982+
#[ts(export_to = "v2/")]
983+
pub struct SkillsListResponse {
984+
pub data: Vec<SkillsListEntry>,
985+
}
986+
987+
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
988+
#[serde(rename_all = "snake_case")]
989+
#[ts(rename_all = "snake_case")]
990+
#[ts(export_to = "v2/")]
991+
pub enum SkillScope {
992+
User,
993+
Repo,
994+
}
995+
996+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
997+
#[serde(rename_all = "camelCase")]
998+
#[ts(export_to = "v2/")]
999+
pub struct SkillMetadata {
1000+
pub name: String,
1001+
pub description: String,
1002+
pub path: PathBuf,
1003+
pub scope: SkillScope,
1004+
}
1005+
1006+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1007+
#[serde(rename_all = "camelCase")]
1008+
#[ts(export_to = "v2/")]
1009+
pub struct SkillErrorInfo {
1010+
pub path: PathBuf,
1011+
pub message: String,
1012+
}
1013+
1014+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1015+
#[serde(rename_all = "camelCase")]
1016+
#[ts(export_to = "v2/")]
1017+
pub struct SkillsListEntry {
1018+
pub cwd: PathBuf,
1019+
pub skills: Vec<SkillMetadata>,
1020+
pub errors: Vec<SkillErrorInfo>,
1021+
}
1022+
1023+
impl From<CoreSkillMetadata> for SkillMetadata {
1024+
fn from(value: CoreSkillMetadata) -> Self {
1025+
Self {
1026+
name: value.name,
1027+
description: value.description,
1028+
path: value.path,
1029+
scope: value.scope.into(),
1030+
}
1031+
}
1032+
}
1033+
1034+
impl From<CoreSkillScope> for SkillScope {
1035+
fn from(value: CoreSkillScope) -> Self {
1036+
match value {
1037+
CoreSkillScope::User => Self::User,
1038+
CoreSkillScope::Repo => Self::Repo,
1039+
}
1040+
}
1041+
}
1042+
1043+
impl From<CoreSkillErrorInfo> for SkillErrorInfo {
1044+
fn from(value: CoreSkillErrorInfo) -> Self {
1045+
Self {
1046+
path: value.path,
1047+
message: value.message,
1048+
}
1049+
}
1050+
}
1051+
9691052
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
9701053
#[serde(rename_all = "camelCase")]
9711054
#[ts(export_to = "v2/")]

codex-rs/app-server/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Example (from OpenAI's official VSCode extension):
6565
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
6666
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
6767
- `model/list` — list available models (with reasoning effort options).
68+
- `skills/list` — list skills for one or more `cwd` values; each skill includes a `scope` of `user` or `repo` (not thread-scoped).
6869
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
6970
- `mcpServers/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
7071
- `feedback/upload` — submit a feedback report (classification + optional reason/logs and conversation_id); returns the tracking thread id.

codex-rs/app-server/src/codex_message_processor.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ use codex_app_server_protocol::ServerNotification;
8181
use codex_app_server_protocol::SessionConfiguredNotification;
8282
use codex_app_server_protocol::SetDefaultModelParams;
8383
use codex_app_server_protocol::SetDefaultModelResponse;
84+
use codex_app_server_protocol::SkillsListParams;
85+
use codex_app_server_protocol::SkillsListResponse;
8486
use codex_app_server_protocol::Thread;
8587
use codex_app_server_protocol::ThreadArchiveParams;
8688
use codex_app_server_protocol::ThreadArchiveResponse;
@@ -374,6 +376,9 @@ impl CodexMessageProcessor {
374376
self.send_unimplemented_error(request_id, "thread/compact")
375377
.await;
376378
}
379+
ClientRequest::SkillsList { request_id, params } => {
380+
self.skills_list(request_id, params).await;
381+
}
377382
ClientRequest::TurnStart { request_id, params } => {
378383
self.turn_start(request_id, params).await;
379384
}
@@ -2630,6 +2635,40 @@ impl CodexMessageProcessor {
26302635
.await;
26312636
}
26322637

2638+
async fn skills_list(&self, request_id: RequestId, params: SkillsListParams) {
2639+
let SkillsListParams { cwds } = params;
2640+
let cwds = match cwds {
2641+
Some(cwds) if !cwds.is_empty() => cwds,
2642+
_ => vec![self.config.cwd.clone()],
2643+
};
2644+
let data = if self.config.features.enabled(Feature::Skills) {
2645+
let skills_manager = self.conversation_manager.skills_manager();
2646+
cwds.into_iter()
2647+
.map(|cwd| {
2648+
let outcome = skills_manager.skills_for_cwd(&cwd);
2649+
let errors = errors_to_info(&outcome.errors);
2650+
let skills = skills_to_info(&outcome.skills);
2651+
codex_app_server_protocol::SkillsListEntry {
2652+
cwd,
2653+
skills,
2654+
errors,
2655+
}
2656+
})
2657+
.collect()
2658+
} else {
2659+
cwds.into_iter()
2660+
.map(|cwd| codex_app_server_protocol::SkillsListEntry {
2661+
cwd,
2662+
skills: Vec::new(),
2663+
errors: Vec::new(),
2664+
})
2665+
.collect()
2666+
};
2667+
self.outgoing
2668+
.send_response(request_id, SkillsListResponse { data })
2669+
.await;
2670+
}
2671+
26332672
async fn interrupt_conversation(
26342673
&mut self,
26352674
request_id: RequestId,
@@ -3275,6 +3314,32 @@ impl CodexMessageProcessor {
32753314
}
32763315
}
32773316

3317+
fn skills_to_info(
3318+
skills: &[codex_core::skills::SkillMetadata],
3319+
) -> Vec<codex_app_server_protocol::SkillMetadata> {
3320+
skills
3321+
.iter()
3322+
.map(|skill| codex_app_server_protocol::SkillMetadata {
3323+
name: skill.name.clone(),
3324+
description: skill.description.clone(),
3325+
path: skill.path.clone(),
3326+
scope: skill.scope.into(),
3327+
})
3328+
.collect()
3329+
}
3330+
3331+
fn errors_to_info(
3332+
errors: &[codex_core::skills::SkillError],
3333+
) -> Vec<codex_app_server_protocol::SkillErrorInfo> {
3334+
errors
3335+
.iter()
3336+
.map(|err| codex_app_server_protocol::SkillErrorInfo {
3337+
path: err.path.clone(),
3338+
message: err.message.clone(),
3339+
})
3340+
.collect()
3341+
}
3342+
32783343
async fn derive_config_from_params(
32793344
overrides: ConfigOverrides,
32803345
cli_overrides: Option<std::collections::HashMap<String, serde_json::Value>>,

codex-rs/core/src/auth.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,18 @@ impl AuthManager {
11111111
})
11121112
}
11131113

1114+
#[cfg(any(test, feature = "test-support"))]
1115+
/// Create an AuthManager with a specific CodexAuth and codex home, for testing only.
1116+
pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<Self> {
1117+
let cached = CachedAuth { auth: Some(auth) };
1118+
Arc::new(Self {
1119+
codex_home,
1120+
inner: RwLock::new(cached),
1121+
enable_codex_api_key_env: false,
1122+
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
1123+
})
1124+
}
1125+
11141126
/// Current cached auth (clone). May be `None` if not logged in or load failed.
11151127
pub fn auth(&self) -> Option<CodexAuth> {
11161128
self.inner.read().ok().and_then(|c| c.auth.clone())

0 commit comments

Comments
 (0)