Skip to main content

systemprompt_models/bridge/
manifest.rs

1//! Signed manifest wire format.
2//!
3//! [`SignedManifest`] is the JSON document the gateway returns from
4//! `GET /v1/bridge/manifest` and the bridge consumes to drive its
5//! plugin / skill / agent / managed-MCP sync. Every public type in
6//! this module is part of that wire contract — the gateway server
7//! (in `crates/entry/api`) emits these structs and the bridge
8//! deserialises them, so any change here is a wire-format change.
9//!
10//! Signing, signature verification, and manifest construction live in
11//! the bridge crate (`bin/bridge/src/gateway/manifest.rs`) alongside
12//! the gateway client. Those layers pull in `ed25519-dalek` and
13//! `serde_jcs` which are not appropriate dependencies for this
14//! foundation crate.
15
16use std::collections::BTreeMap;
17
18use serde::{Deserialize, Serialize};
19
20pub use crate::bridge::ids::ManifestSignature;
21use crate::bridge::ids::{
22    LibraryArtifactId, ManagedMcpServerName, PluginId, Sha256Digest, SkillId, SkillName, ToolName,
23    ToolPolicy,
24};
25use crate::bridge::manifest_version::ManifestVersion;
26use crate::services::hooks::{HookCategory, HookEvent};
27use crate::services::plugin::PluginComponentRef;
28use systemprompt_identifiers::{AgentId, AgentName, HookId, TenantId, UserId, ValidatedUrl};
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct SignedManifest {
32    pub manifest_version: ManifestVersion,
33    pub issued_at: String,
34    pub not_before: String,
35    pub user_id: UserId,
36    pub tenant_id: Option<TenantId>,
37    #[serde(default)]
38    pub user: Option<UserInfo>,
39    pub plugins: Vec<PluginEntry>,
40    #[serde(default)]
41    pub skills: Vec<SkillEntry>,
42    #[serde(default)]
43    pub agents: Vec<AgentEntry>,
44    #[serde(default)]
45    pub hooks: Vec<HookEntry>,
46    pub managed_mcp_servers: Vec<ManagedMcpServer>,
47    pub revocations: Vec<String>,
48    #[serde(default)]
49    pub enabled_hosts: Vec<String>,
50    /// Optional per-host wire-protocol filter, keyed by host id. A present
51    /// entry overrides the host's built-in default `accepted_protocols`; an
52    /// empty value means "all models" (no restriction). An absent entry leaves
53    /// the host on its default.
54    #[serde(default)]
55    pub host_model_protocols: BTreeMap<String, Vec<String>>,
56    /// Cowork global-library HTML documents — distinct from the in-chat MCP
57    /// artifacts in [`crate::artifacts`].
58    #[serde(default)]
59    pub artifacts: Vec<ArtifactEntry>,
60    /// Detached ed25519 signature of the canonicalised payload (every
61    /// field above this one). Always present on the wire even for
62    /// unsigned manifests, where it is the empty string.
63    pub signature: ManifestSignature,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct UserInfo {
68    pub id: UserId,
69    pub name: String,
70    pub email: String,
71    #[serde(default)]
72    pub display_name: Option<String>,
73    #[serde(default)]
74    pub roles: Vec<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PluginEntry {
79    pub id: PluginId,
80    pub version: String,
81    pub sha256: Sha256Digest,
82    pub files: Vec<PluginFile>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PluginFile {
87    pub path: String,
88    pub sha256: Sha256Digest,
89    pub size: u64,
90}
91
92/// A Cowork-native library document (raw HTML in the desktop app's Artifacts
93/// library) — not one of the in-chat MCP artifacts in [`crate::artifacts`].
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ArtifactEntry {
96    pub id: LibraryArtifactId,
97    pub name: String,
98    pub description: String,
99    pub version: String,
100    pub plugin_id: PluginId,
101    pub mcp_tools: Vec<String>,
102    pub content: String,
103    pub starred: bool,
104    pub sha256: Sha256Digest,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct SkillEntry {
109    pub id: SkillId,
110    pub name: SkillName,
111    pub description: String,
112    pub file_path: String,
113    #[serde(default)]
114    pub tags: Vec<String>,
115    pub sha256: Sha256Digest,
116    pub instructions: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct AgentEntry {
121    pub id: AgentId,
122    pub name: AgentName,
123    pub display_name: String,
124    pub description: String,
125    pub version: String,
126    pub endpoint: String,
127    pub enabled: bool,
128    pub is_default: bool,
129    pub is_primary: bool,
130    #[serde(default)]
131    pub provider: Option<String>,
132    #[serde(default)]
133    pub model: Option<String>,
134    #[serde(default)]
135    pub mcp_servers: PluginComponentRef,
136    #[serde(default)]
137    pub skills: PluginComponentRef,
138    #[serde(default)]
139    pub tags: Vec<String>,
140    #[serde(default)]
141    pub system_prompt: Option<String>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct HookEntry {
146    pub id: HookId,
147    pub name: String,
148    pub description: String,
149    pub version: String,
150    pub event: HookEvent,
151    pub matcher: String,
152    pub command: String,
153    #[serde(default)]
154    pub is_async: bool,
155    pub category: HookCategory,
156    #[serde(default)]
157    pub tags: Vec<String>,
158    pub sha256: Sha256Digest,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ManagedMcpServer {
163    pub name: ManagedMcpServerName,
164    pub url: ValidatedUrl,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub transport: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub headers: Option<BTreeMap<String, String>>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub oauth: Option<bool>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
173}