Add an app-server API for reading app metadata (#33651)

## What changed

- Add the experimental `app/read` request for fetching metadata for up to 100
  app IDs, with optional display-only tool summaries.
- Preserve first-request order while deduplicating IDs, and report unknown or
  unauthorized apps as partial misses.
- Cache metadata by backend and ChatGPT account/workspace identity, refetch
  metadata-only entries when tools are requested, and preserve cached records
  when a backend request fails.

## Testing

- Cover request serialization, ordering, deduplication, partial misses, caching,
  tool-summary refetches, backend failures, cache isolation, and the ID limit.

GitOrigin-RevId: 55fc3281d99291e76d6c4a581e8d11569d3cb1ac
This commit is contained in:
stevenlee-oai authored and copyberry committed 2026-07-16 17:46:36 +00:00
1 parent f64233d142
commit 726b6378d2
26 files changed
+1716 -1

No files matched your search

@@ -735,6 +735,11 @@ client_request_definitions! {
serialization: global("config"),
response: v2::PluginShareDeleteResponse,
},
AppsRead => "app/read" {
params: v2::AppsReadParams,
serialization: None,
response: v2::AppsReadResponse,
},
AppsList => "app/list" {
params: v2::AppsListParams,
serialization: None,
@@ -3103,6 +3108,26 @@ mod tests {
Ok(())
}
#[test]
fn serialize_read_apps() -> Result<()> {
let request = ClientRequest::AppsRead {
request_id: RequestId::Integer(9),
params: v2::AppsReadParams {
app_ids: vec!["app-a".to_string(), "app-b".to_string()],
include_tools: true,
},
};
assert_eq!(
json!({
"method": "app/read",
"id": 9,
"params": { "appIds": ["app-a", "app-b"], "includeTools": true }
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_environment_add() -> Result<()> {
let request = ClientRequest::EnvironmentAdd {
@@ -131,6 +131,50 @@ fn non_empty_category(category: Option<&str>) -> Option<String> {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - read metadata for specific apps/connectors.
pub struct AppsReadParams {
/// App ids to read. The server accepts at most 100 ids and deduplicates repeated ids while
/// preserving their first-request order.
pub app_ids: Vec<String>,
/// When true, include display-only public tool summaries in the returned metadata.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub include_tools: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - metadata returned by app/read.
pub struct AppToolSummary {
pub name: String,
pub title: Option<String>,
pub description: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - metadata returned by app/read.
pub struct ConnectorMetadata {
pub id: String,
pub name: String,
pub description: Option<String>,
pub icon_url: Option<String>,
pub tool_summaries: Option<Vec<AppToolSummary>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app/read response.
pub struct AppsReadResponse {
pub apps: Vec<ConnectorMetadata>,
pub missing_app_ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]