[apps] Expose more fields from apps listing endpoints. (#11706)

- [x] Expose app_metadata, branding, and labels in AppInfo.
This commit is contained in:
Matthew Zeng 2026-02-17 11:45:04 -08:00 committed by GitHub
parent 41800fc876
commit 16fa195fce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1174 additions and 2 deletions

View file

@ -165,9 +165,71 @@
}
]
},
"AppBranding": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"isDiscoverableApp": {
"type": "boolean"
},
"privacyPolicy": {
"type": [
"string",
"null"
]
},
"termsOfService": {
"type": [
"string",
"null"
]
},
"website": {
"type": [
"string",
"null"
]
}
},
"required": [
"isDiscoverableApp"
],
"type": "object"
},
"AppInfo": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"appMetadata": {
"anyOf": [
{
"$ref": "#/definitions/AppMetadata"
},
{
"type": "null"
}
]
},
"branding": {
"anyOf": [
{
"$ref": "#/definitions/AppBranding"
},
{
"type": "null"
}
]
},
"description": {
"type": [
"string",
@ -198,6 +260,15 @@
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": [
"object",
"null"
]
},
"logoUrl": {
"type": [
"string",
@ -235,6 +306,130 @@
],
"type": "object"
},
"AppMetadata": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"firstPartyRequiresInstall": {
"type": [
"boolean",
"null"
]
},
"firstPartyType": {
"type": [
"string",
"null"
]
},
"review": {
"anyOf": [
{
"$ref": "#/definitions/AppReview"
},
{
"type": "null"
}
]
},
"screenshots": {
"items": {
"$ref": "#/definitions/AppScreenshot"
},
"type": [
"array",
"null"
]
},
"seoDescription": {
"type": [
"string",
"null"
]
},
"showInComposerWhenUnlinked": {
"type": [
"boolean",
"null"
]
},
"subCategories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"version": {
"type": [
"string",
"null"
]
},
"versionId": {
"type": [
"string",
"null"
]
},
"versionNotes": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"AppReview": {
"properties": {
"status": {
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
},
"AppScreenshot": {
"properties": {
"fileId": {
"type": [
"string",
"null"
]
},
"url": {
"type": [
"string",
"null"
]
},
"userPrompt": {
"type": "string"
}
},
"required": [
"userPrompt"
],
"type": "object"
},
"AskForApproval": {
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
"oneOf": [

View file

@ -10271,6 +10271,48 @@
},
"type": "object"
},
"AppBranding": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"isDiscoverableApp": {
"type": "boolean"
},
"privacyPolicy": {
"type": [
"string",
"null"
]
},
"termsOfService": {
"type": [
"string",
"null"
]
},
"website": {
"type": [
"string",
"null"
]
}
},
"required": [
"isDiscoverableApp"
],
"type": "object"
},
"AppConfig": {
"properties": {
"disabled_reason": {
@ -10300,6 +10342,26 @@
"AppInfo": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"appMetadata": {
"anyOf": [
{
"$ref": "#/definitions/v2/AppMetadata"
},
{
"type": "null"
}
]
},
"branding": {
"anyOf": [
{
"$ref": "#/definitions/v2/AppBranding"
},
{
"type": "null"
}
]
},
"description": {
"type": [
"string",
@ -10330,6 +10392,15 @@
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": [
"object",
"null"
]
},
"logoUrl": {
"type": [
"string",
@ -10369,6 +10440,130 @@
"title": "AppListUpdatedNotification",
"type": "object"
},
"AppMetadata": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"firstPartyRequiresInstall": {
"type": [
"boolean",
"null"
]
},
"firstPartyType": {
"type": [
"string",
"null"
]
},
"review": {
"anyOf": [
{
"$ref": "#/definitions/v2/AppReview"
},
{
"type": "null"
}
]
},
"screenshots": {
"items": {
"$ref": "#/definitions/v2/AppScreenshot"
},
"type": [
"array",
"null"
]
},
"seoDescription": {
"type": [
"string",
"null"
]
},
"showInComposerWhenUnlinked": {
"type": [
"boolean",
"null"
]
},
"subCategories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"version": {
"type": [
"string",
"null"
]
},
"versionId": {
"type": [
"string",
"null"
]
},
"versionNotes": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"AppReview": {
"properties": {
"status": {
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
},
"AppScreenshot": {
"properties": {
"fileId": {
"type": [
"string",
"null"
]
},
"url": {
"type": [
"string",
"null"
]
},
"userPrompt": {
"type": "string"
}
},
"required": [
"userPrompt"
],
"type": "object"
},
"AppsConfig": {
"type": "object"
},

View file

@ -1,9 +1,71 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AppBranding": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"isDiscoverableApp": {
"type": "boolean"
},
"privacyPolicy": {
"type": [
"string",
"null"
]
},
"termsOfService": {
"type": [
"string",
"null"
]
},
"website": {
"type": [
"string",
"null"
]
}
},
"required": [
"isDiscoverableApp"
],
"type": "object"
},
"AppInfo": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"appMetadata": {
"anyOf": [
{
"$ref": "#/definitions/AppMetadata"
},
{
"type": "null"
}
]
},
"branding": {
"anyOf": [
{
"$ref": "#/definitions/AppBranding"
},
{
"type": "null"
}
]
},
"description": {
"type": [
"string",
@ -34,6 +96,15 @@
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": [
"object",
"null"
]
},
"logoUrl": {
"type": [
"string",
@ -55,6 +126,130 @@
"name"
],
"type": "object"
},
"AppMetadata": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"firstPartyRequiresInstall": {
"type": [
"boolean",
"null"
]
},
"firstPartyType": {
"type": [
"string",
"null"
]
},
"review": {
"anyOf": [
{
"$ref": "#/definitions/AppReview"
},
{
"type": "null"
}
]
},
"screenshots": {
"items": {
"$ref": "#/definitions/AppScreenshot"
},
"type": [
"array",
"null"
]
},
"seoDescription": {
"type": [
"string",
"null"
]
},
"showInComposerWhenUnlinked": {
"type": [
"boolean",
"null"
]
},
"subCategories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"version": {
"type": [
"string",
"null"
]
},
"versionId": {
"type": [
"string",
"null"
]
},
"versionNotes": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"AppReview": {
"properties": {
"status": {
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
},
"AppScreenshot": {
"properties": {
"fileId": {
"type": [
"string",
"null"
]
},
"url": {
"type": [
"string",
"null"
]
},
"userPrompt": {
"type": "string"
}
},
"required": [
"userPrompt"
],
"type": "object"
}
},
"description": "EXPERIMENTAL - notification emitted when the app list changes.",

View file

@ -1,9 +1,71 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AppBranding": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"category": {
"type": [
"string",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"isDiscoverableApp": {
"type": "boolean"
},
"privacyPolicy": {
"type": [
"string",
"null"
]
},
"termsOfService": {
"type": [
"string",
"null"
]
},
"website": {
"type": [
"string",
"null"
]
}
},
"required": [
"isDiscoverableApp"
],
"type": "object"
},
"AppInfo": {
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
"properties": {
"appMetadata": {
"anyOf": [
{
"$ref": "#/definitions/AppMetadata"
},
{
"type": "null"
}
]
},
"branding": {
"anyOf": [
{
"$ref": "#/definitions/AppBranding"
},
{
"type": "null"
}
]
},
"description": {
"type": [
"string",
@ -34,6 +96,15 @@
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": [
"object",
"null"
]
},
"logoUrl": {
"type": [
"string",
@ -55,6 +126,130 @@
"name"
],
"type": "object"
},
"AppMetadata": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developer": {
"type": [
"string",
"null"
]
},
"firstPartyRequiresInstall": {
"type": [
"boolean",
"null"
]
},
"firstPartyType": {
"type": [
"string",
"null"
]
},
"review": {
"anyOf": [
{
"$ref": "#/definitions/AppReview"
},
{
"type": "null"
}
]
},
"screenshots": {
"items": {
"$ref": "#/definitions/AppScreenshot"
},
"type": [
"array",
"null"
]
},
"seoDescription": {
"type": [
"string",
"null"
]
},
"showInComposerWhenUnlinked": {
"type": [
"boolean",
"null"
]
},
"subCategories": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"version": {
"type": [
"string",
"null"
]
},
"versionId": {
"type": [
"string",
"null"
]
},
"versionNotes": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"AppReview": {
"properties": {
"status": {
"type": "string"
}
},
"required": [
"status"
],
"type": "object"
},
"AppScreenshot": {
"properties": {
"fileId": {
"type": [
"string",
"null"
]
},
"url": {
"type": [
"string",
"null"
]
},
"userPrompt": {
"type": "string"
}
},
"required": [
"userPrompt"
],
"type": "object"
}
},
"description": "EXPERIMENTAL - app list response.",

View file

@ -0,0 +1,8 @@
// 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.
/**
* EXPERIMENTAL - app metadata returned by app-list APIs.
*/
export type AppBranding = { category: string | null, developer: string | null, website: string | null, privacyPolicy: string | null, termsOfService: string | null, isDiscoverableApp: boolean, };

View file

@ -1,11 +1,13 @@
// 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 { AppBranding } from "./AppBranding";
import type { AppMetadata } from "./AppMetadata";
/**
* EXPERIMENTAL - app metadata returned by app-list APIs.
*/
export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, installUrl: string | null, isAccessible: boolean,
export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, branding: AppBranding | null, appMetadata: AppMetadata | null, labels: { [key in string]?: string } | null, installUrl: string | null, isAccessible: boolean,
/**
* Whether this app is enabled in config.toml.
* Example:

View file

@ -0,0 +1,7 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AppReview } from "./AppReview";
import type { AppScreenshot } from "./AppScreenshot";
export type AppMetadata = { review: AppReview | null, categories: Array<string> | null, subCategories: Array<string> | null, seoDescription: string | null, screenshots: Array<AppScreenshot> | null, developer: string | null, version: string | null, versionId: string | null, versionNotes: string | null, firstPartyType: string | null, firstPartyRequiresInstall: boolean | null, showInComposerWhenUnlinked: boolean | null, };

View 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 AppReview = { status: string, };

View 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 AppScreenshot = { url: string | null, fileId: string | null, userPrompt: string, };

View file

@ -6,9 +6,13 @@ export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUp
export type { AccountUpdatedNotification } from "./AccountUpdatedNotification";
export type { AgentMessageDeltaNotification } from "./AgentMessageDeltaNotification";
export type { AnalyticsConfig } from "./AnalyticsConfig";
export type { AppBranding } from "./AppBranding";
export type { AppDisabledReason } from "./AppDisabledReason";
export type { AppInfo } from "./AppInfo";
export type { AppListUpdatedNotification } from "./AppListUpdatedNotification";
export type { AppMetadata } from "./AppMetadata";
export type { AppReview } from "./AppReview";
export type { AppScreenshot } from "./AppScreenshot";
export type { AppsConfig } from "./AppsConfig";
export type { AppsListParams } from "./AppsListParams";
export type { AppsListResponse } from "./AppsListResponse";

View file

@ -1287,6 +1287,55 @@ pub struct AppsListParams {
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/")]
@ -1298,6 +1347,9 @@ pub struct AppInfo {
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,

View file

@ -772,7 +772,7 @@ To enable or disable a skill by path:
## Apps
Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, whether it is currently accessible, and whether it is enabled in config.
Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, `branding`, `appMetadata`, `labels`, whether it is currently accessible, and whether it is enabled in config.
```json
{ "method": "app/list", "id": 50, "params": {
@ -790,6 +790,9 @@ Use `app/list` to fetch available apps (connectors). Each entry includes metadat
"logoUrl": "https://example.com/demo-app.png",
"logoUrlDark": null,
"distributionChannel": null,
"branding": null,
"appMetadata": null,
"labels": null,
"installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
"isAccessible": true,
"isEnabled": true
@ -817,6 +820,9 @@ The server also emits `app/list/updated` notifications whenever either source (a
"logoUrl": "https://example.com/demo-app.png",
"logoUrlDark": null,
"distributionChannel": null,
"branding": null,
"appMetadata": null,
"labels": null,
"installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
"isAccessible": true,
"isEnabled": true

View file

@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
@ -16,8 +17,12 @@ use axum::http::HeaderMap;
use axum::http::StatusCode;
use axum::http::header::AUTHORIZATION;
use axum::routing::get;
use codex_app_server_protocol::AppBranding;
use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::AppListUpdatedNotification;
use codex_app_server_protocol::AppMetadata;
use codex_app_server_protocol::AppReview;
use codex_app_server_protocol::AppScreenshot;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::JSONRPCError;
@ -85,6 +90,9 @@ async fn list_apps_uses_thread_feature_flag_when_thread_id_is_provided() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -184,6 +192,9 @@ async fn list_apps_reports_is_enabled_from_config() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -249,6 +260,39 @@ enabled = false
#[tokio::test]
async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<()> {
let alpha_branding = Some(AppBranding {
category: Some("PRODUCTIVITY".to_string()),
developer: Some("Acme".to_string()),
website: Some("https://acme.example".to_string()),
privacy_policy: Some("https://acme.example/privacy".to_string()),
terms_of_service: Some("https://acme.example/terms".to_string()),
is_discoverable_app: true,
});
let alpha_app_metadata = Some(AppMetadata {
review: Some(AppReview {
status: "APPROVED".to_string(),
}),
categories: Some(vec!["PRODUCTIVITY".to_string()]),
sub_categories: Some(vec!["WRITING".to_string()]),
seo_description: Some("Alpha connector".to_string()),
screenshots: Some(vec![AppScreenshot {
url: Some("https://example.com/alpha-screenshot.png".to_string()),
file_id: Some("file_123".to_string()),
user_prompt: "Summarize this draft".to_string(),
}]),
developer: Some("Acme".to_string()),
version: Some("1.2.3".to_string()),
version_id: Some("version_123".to_string()),
version_notes: Some("Fixes and improvements".to_string()),
first_party_type: Some("internal".to_string()),
first_party_requires_install: Some(true),
show_in_composer_when_unlinked: Some(true),
});
let alpha_labels = Some(HashMap::from([
("feature".to_string(), "beta".to_string()),
("source".to_string(), "directory".to_string()),
]));
let connectors = vec![
AppInfo {
id: "alpha".to_string(),
@ -257,6 +301,9 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
branding: alpha_branding.clone(),
app_metadata: alpha_app_metadata.clone(),
labels: alpha_labels.clone(),
install_url: None,
is_accessible: false,
is_enabled: true,
@ -268,6 +315,9 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -313,6 +363,9 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -329,6 +382,9 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -340,6 +396,9 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
branding: alpha_branding,
app_metadata: alpha_app_metadata,
labels: alpha_labels,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -377,6 +436,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -388,6 +450,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -437,6 +502,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -448,6 +516,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: false,
is_enabled: true,
@ -463,6 +534,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -474,6 +548,9 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -506,6 +583,9 @@ async fn list_apps_paginates_results() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -517,6 +597,9 @@ async fn list_apps_paginates_results() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -571,6 +654,9 @@ async fn list_apps_paginates_results() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -611,6 +697,9 @@ async fn list_apps_paginates_results() -> Result<()> {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -632,6 +721,9 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -733,6 +825,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -744,6 +839,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -790,6 +888,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -807,6 +908,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
is_accessible: true,
is_enabled: true,
@ -818,6 +922,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -844,6 +951,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -870,6 +980,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
@ -881,6 +994,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
is_accessible: false,
is_enabled: true,
@ -895,6 +1011,9 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,

View file

@ -14,7 +14,9 @@ use crate::chatgpt_client::chatgpt_get_request_with_timeout;
use crate::chatgpt_token::get_chatgpt_token_data;
use crate::chatgpt_token::init_chatgpt_token_from_auth;
use codex_core::connectors::AppBranding;
pub use codex_core::connectors::AppInfo;
use codex_core::connectors::AppMetadata;
use codex_core::connectors::CONNECTORS_CACHE_TTL;
pub use codex_core::connectors::connector_display_label;
use codex_core::connectors::connector_install_url;
@ -36,6 +38,10 @@ struct DirectoryApp {
id: String,
name: String,
description: Option<String>,
#[serde(alias = "appMetadata")]
app_metadata: Option<AppMetadata>,
branding: Option<AppBranding>,
labels: Option<HashMap<String, String>>,
#[serde(alias = "logoUrl")]
logo_url: Option<String>,
#[serde(alias = "logoUrlDark")]
@ -269,6 +275,9 @@ fn merge_directory_app(existing: &mut DirectoryApp, incoming: DirectoryApp) {
id: _,
name,
description,
app_metadata,
branding,
labels,
logo_url,
logo_url_dark,
distribution_channel,
@ -302,6 +311,108 @@ fn merge_directory_app(existing: &mut DirectoryApp, incoming: DirectoryApp) {
if existing.distribution_channel.is_none() && distribution_channel.is_some() {
existing.distribution_channel = distribution_channel;
}
if let Some(incoming_branding) = branding {
if let Some(existing_branding) = existing.branding.as_mut() {
if existing_branding.category.is_none() && incoming_branding.category.is_some() {
existing_branding.category = incoming_branding.category;
}
if existing_branding.developer.is_none() && incoming_branding.developer.is_some() {
existing_branding.developer = incoming_branding.developer;
}
if existing_branding.website.is_none() && incoming_branding.website.is_some() {
existing_branding.website = incoming_branding.website;
}
if existing_branding.privacy_policy.is_none()
&& incoming_branding.privacy_policy.is_some()
{
existing_branding.privacy_policy = incoming_branding.privacy_policy;
}
if existing_branding.terms_of_service.is_none()
&& incoming_branding.terms_of_service.is_some()
{
existing_branding.terms_of_service = incoming_branding.terms_of_service;
}
if !existing_branding.is_discoverable_app && incoming_branding.is_discoverable_app {
existing_branding.is_discoverable_app = true;
}
} else {
existing.branding = Some(incoming_branding);
}
}
if let Some(incoming_app_metadata) = app_metadata {
if let Some(existing_app_metadata) = existing.app_metadata.as_mut() {
if existing_app_metadata.review.is_none() && incoming_app_metadata.review.is_some() {
existing_app_metadata.review = incoming_app_metadata.review;
}
if existing_app_metadata.categories.is_none()
&& incoming_app_metadata.categories.is_some()
{
existing_app_metadata.categories = incoming_app_metadata.categories;
}
if existing_app_metadata.sub_categories.is_none()
&& incoming_app_metadata.sub_categories.is_some()
{
existing_app_metadata.sub_categories = incoming_app_metadata.sub_categories;
}
if existing_app_metadata.seo_description.is_none()
&& incoming_app_metadata.seo_description.is_some()
{
existing_app_metadata.seo_description = incoming_app_metadata.seo_description;
}
if existing_app_metadata.screenshots.is_none()
&& incoming_app_metadata.screenshots.is_some()
{
existing_app_metadata.screenshots = incoming_app_metadata.screenshots;
}
if existing_app_metadata.developer.is_none()
&& incoming_app_metadata.developer.is_some()
{
existing_app_metadata.developer = incoming_app_metadata.developer;
}
if existing_app_metadata.version.is_none() && incoming_app_metadata.version.is_some() {
existing_app_metadata.version = incoming_app_metadata.version;
}
if existing_app_metadata.version_id.is_none()
&& incoming_app_metadata.version_id.is_some()
{
existing_app_metadata.version_id = incoming_app_metadata.version_id;
}
if existing_app_metadata.version_notes.is_none()
&& incoming_app_metadata.version_notes.is_some()
{
existing_app_metadata.version_notes = incoming_app_metadata.version_notes;
}
if existing_app_metadata.first_party_type.is_none()
&& incoming_app_metadata.first_party_type.is_some()
{
existing_app_metadata.first_party_type = incoming_app_metadata.first_party_type;
}
if existing_app_metadata.first_party_requires_install.is_none()
&& incoming_app_metadata.first_party_requires_install.is_some()
{
existing_app_metadata.first_party_requires_install =
incoming_app_metadata.first_party_requires_install;
}
if existing_app_metadata
.show_in_composer_when_unlinked
.is_none()
&& incoming_app_metadata
.show_in_composer_when_unlinked
.is_some()
{
existing_app_metadata.show_in_composer_when_unlinked =
incoming_app_metadata.show_in_composer_when_unlinked;
}
} else {
existing.app_metadata = Some(incoming_app_metadata);
}
}
if existing.labels.is_none() && labels.is_some() {
existing.labels = labels;
}
}
fn is_hidden_directory_app(app: &DirectoryApp) -> bool {
@ -316,6 +427,9 @@ fn directory_app_to_app_info(app: DirectoryApp) -> AppInfo {
logo_url: app.logo_url,
logo_url_dark: app.logo_url_dark,
distribution_channel: app.distribution_channel,
branding: app.branding,
app_metadata: app.app_metadata,
labels: app.labels,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -342,6 +456,8 @@ const DISALLOWED_CONNECTOR_IDS: &[&str] = &[
"asdk_app_6938a94a61d881918ef32cb999ff937c",
"connector_2b0a9009c9c64bf9933a3dae3f2b1254",
"connector_68de829bf7648191acd70a907364c67c",
"connector_68e004f14af881919eb50893d3d9f523",
"connector_69272cb413a081919685ec3c88d1744e",
];
const DISALLOWED_CONNECTOR_PREFIX: &str = "connector_openai_";
@ -375,6 +491,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
@ -429,6 +548,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some(connector_install_url(id, id)),
is_accessible,
is_enabled: true,

View file

@ -5823,6 +5823,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: true,
is_enabled: true,

View file

@ -7,7 +7,9 @@ use std::time::Duration;
use std::time::Instant;
use async_channel::unbounded;
pub use codex_app_server_protocol::AppBranding;
pub use codex_app_server_protocol::AppInfo;
pub use codex_app_server_protocol::AppMetadata;
use codex_protocol::protocol::SandboxPolicy;
use serde::Deserialize;
use tokio_util::sync::CancellationToken;
@ -320,6 +322,9 @@ where
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some(connector_install_url(&connector_name, &connector_id)),
is_accessible: true,
is_enabled: true,

View file

@ -261,6 +261,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: true,
is_enabled: enabled,

View file

@ -4596,6 +4596,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4633,6 +4636,9 @@ mod tests {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: false,

View file

@ -4424,6 +4424,9 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4457,6 +4460,9 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4468,6 +4474,9 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/linear".to_string()),
is_accessible: true,
is_enabled: true,
@ -4504,6 +4513,9 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4515,6 +4527,9 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,
@ -4536,6 +4551,9 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4572,6 +4590,9 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4583,6 +4604,9 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,
@ -4606,6 +4630,9 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4617,6 +4644,9 @@ async fn apps_partial_refresh_uses_same_filtering_as_full_refresh() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/hidden-openai".to_string()),
is_accessible: true,
is_enabled: true,
@ -4657,6 +4687,9 @@ async fn apps_popup_shows_disabled_status_for_installed_but_disabled_apps() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: false,
@ -4704,6 +4737,9 @@ async fn apps_initial_load_applies_enabled_state_from_config() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4738,6 +4774,9 @@ async fn apps_refresh_preserves_toggled_enabled_state() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4756,6 +4795,9 @@ async fn apps_refresh_preserves_toggled_enabled_state() {
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/notion".to_string()),
is_accessible: true,
is_enabled: true,
@ -4797,6 +4839,9 @@ async fn apps_popup_for_not_installed_app_uses_install_only_selected_description
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://example.test/linear".to_string()),
is_accessible: false,
is_enabled: true,