Skip to main content

greentic_ext_runtime/
types.rs

1/// Host-side mirror of WIT `greentic:extension-design/tools@0.2.0::tool-definition`.
2#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3pub struct ToolDefinition {
4    pub name: String,
5    pub description: String,
6    pub input_schema_json: String,
7    #[serde(default, skip_serializing_if = "Option::is_none")]
8    pub output_schema_json: Option<String>,
9    /// Runtime contexts the tool supports (`"flow"`, `"agentic_worker"`).
10    /// Legacy extensions return `None`; consumers must default to `["flow"]`.
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub capabilities: Option<Vec<String>>,
13    /// JSON-encoded `AgenticWorkerMetadata` blob. Decode via
14    /// `greentic_extension_sdk_contract::AgenticWorkerMetadata::decode`.
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub agentic_worker_metadata: Option<String>,
17    /// Per-tool secret/credential requirements (v2 declarative tools).
18    /// Legacy v1 WIT tools have none; defaults to empty.
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub secret_requirements: Vec<greentic_types::secrets::SecretRequirement>,
21}
22
23/// Host-side mirror of WIT `greentic:extension-design/prompting@0.2.0::prompt-fragment`.
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25pub struct PromptFragment {
26    pub section: String,
27    pub content_markdown: String,
28    pub priority: u32,
29}
30
31/// Host-side mirror of WIT `greentic:extension-design/knowledge@0.2.0::entry-summary`.
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct KnowledgeEntrySummary {
34    pub id: String,
35    pub title: String,
36    pub category: String,
37    pub tags: Vec<String>,
38}
39
40/// Host-side mirror of WIT `greentic:extension-design/knowledge@0.2.0::entry`.
41#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
42pub struct KnowledgeEntry {
43    pub id: String,
44    pub title: String,
45    pub category: String,
46    pub tags: Vec<String>,
47    pub content_json: String,
48}
49
50/// Host-side mirror of WIT `greentic:extension-base/types@0.1.0::severity`.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
52#[serde(rename_all = "lowercase")]
53pub enum Severity {
54    Error,
55    Warning,
56    Info,
57    Hint,
58}
59
60/// Host-side mirror of WIT `greentic:extension-base/types@0.1.0::diagnostic`.
61#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
62pub struct Diagnostic {
63    pub severity: Severity,
64    pub code: String,
65    pub message: String,
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub path: Option<String>,
68}
69
70/// Host-side mirror of WIT `greentic:extension-deploy/targets@0.1.0::target-summary`.
71///
72/// Returned by `ExtensionRuntime::list_targets` for each deploy target a
73/// loaded extension declares.
74#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
75pub struct TargetSummary {
76    pub id: String,
77    pub display_name: String,
78    pub description: String,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub icon_path: Option<String>,
81    pub supports_rollback: bool,
82}
83
84/// Host-side mirror of WIT `greentic:extension-deploy/deployment@0.1.0::deploy-request`.
85///
86/// Note: no `serde` derives — `artifact_bytes` is a raw binary blob handed
87/// to the WASM guest verbatim; JSON-encoding it would be wasteful and
88/// incorrect.
89#[derive(Debug, Clone)]
90pub struct DeployRequest {
91    pub target_id: String,
92    pub artifact_bytes: Vec<u8>,
93    pub credentials_json: String,
94    pub config_json: String,
95    pub deployment_name: String,
96}
97
98/// Host-side mirror of WIT `greentic:extension-deploy/deployment@0.1.0::deploy-status`.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
100#[serde(rename_all = "kebab-case")]
101pub enum DeployStatus {
102    Pending,
103    Provisioning,
104    Configuring,
105    Starting,
106    Running,
107    Failed,
108    RolledBack,
109}
110
111/// Host-side mirror of WIT `greentic:extension-deploy/deployment@0.1.0::deploy-job`.
112#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
113pub struct DeployJob {
114    pub id: String,
115    pub status: DeployStatus,
116    pub message: String,
117    pub endpoints: Vec<String>,
118}
119
120/// Typed extension-level error surfaced by `deploy`/`poll`/`rollback`.
121///
122/// Variant choice is a host-visible contract: the designer treats
123/// `Internal` from `deploy()` as "not implemented in WASM" (Mode A stub)
124/// and falls back to the greentic-deployer binary. Mode B extensions
125/// must use the other variants for expected failures.
126///
127/// Structurally mirrors [`HostExtensionError`] by design; kept separate
128/// because the two types participate in different dispatch paths and may
129/// diverge as the deploy surface grows.
130#[derive(Debug, Clone, thiserror::Error)]
131pub enum DeployExtensionError {
132    #[error("invalid input: {0}")]
133    InvalidInput(String),
134    #[error("missing capability: {0}")]
135    MissingCapability(String),
136    #[error("permission denied: {0}")]
137    PermissionDenied(String),
138    #[error("not found: {0}")]
139    NotFound(String),
140    #[error("schema invalid: {0}")]
141    SchemaInvalid(String),
142    #[error("internal: {0}")]
143    Internal(String),
144}
145
146/// Host-side mirror of WIT `greentic:extension-design/validation@0.2.0::validate-result`.
147#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
148pub struct ValidateResult {
149    pub valid: bool,
150    pub diagnostics: Vec<Diagnostic>,
151}
152
153/// Host-side mirror of WIT
154/// `greentic:extension-bundle/bundling@0.1.0::designer-session`.
155///
156/// The full payload the host hands to a bundle extension to render.
157/// `flows_json` and `contents_json` are pre-serialised JSON blobs from
158/// the designer; `assets` carries auxiliary file bytes (images, fonts,
159/// vendored resources) keyed by their relative path inside the bundle.
160#[derive(Debug, Clone, Default)]
161pub struct BundleSession {
162    pub flows_json: String,
163    pub contents_json: String,
164    pub assets: Vec<(String, Vec<u8>)>,
165    pub capabilities_used: Vec<String>,
166}
167
168/// Host-side mirror of WIT
169/// `greentic:extension-bundle/bundling@0.1.0::bundle-artifact`.
170///
171/// What `bundling.render` returns on success — the rendered artefact
172/// bytes (typically a `.gtpack` zip) plus its filename and sha256 for
173/// integrity checks. The host writes the bytes to disk verbatim.
174#[derive(Debug, Clone)]
175pub struct BundleArtifact {
176    pub filename: String,
177    pub bytes: Vec<u8>,
178    pub sha256: String,
179}
180
181/// Host-side mirror of WIT
182/// `greentic:extension-design/roles@0.2.0::target-kind`.
183///
184/// Output channel a compiled role targets. Closed enum: a new target
185/// requires a WIT minor bump on the design package.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
187#[serde(rename_all = "kebab-case")]
188pub enum TargetKind {
189    AdaptiveCard,
190    SlackBlockKit,
191    TeamsCard,
192    PlainText,
193}
194
195/// Host-side mirror of WIT
196/// `greentic:extension-design/roles@0.2.0::role-spec`.
197///
198/// One role advertised by an extension. Aggregated across every loaded
199/// design extension into a single registry keyed by `name`.
200#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
201pub struct RoleSpec {
202    pub name: String,
203    pub description: String,
204    pub json_schema: String,
205    pub target: TargetKind,
206    pub schema_version: u32,
207    pub context_aware: bool,
208}
209
210/// Host-side mirror of WIT
211/// `greentic:extension-design/roles@0.2.0::compile-context`.
212///
213/// Flow-level context handed to context-aware compilers. Empty for pure
214/// roles. Designer fills this from the in-progress DSL document before
215/// dispatch.
216#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
217pub struct CompileContext {
218    pub flow_entries_json: String,
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub flow_id: Option<String>,
221    pub locale: String,
222}
223
224/// Host-side mirror of WIT
225/// `greentic:extension-base/types@0.1.0::extension-error`.
226///
227/// Host-level failure that a role compiler may surface. Mirrored here
228/// so [`RoleError::Host`] can carry the variant without dragging in
229/// the bindgen-generated type at the public API boundary.
230///
231/// The `code()` method returns a stable kebab-case string that matches
232/// the WIT `extension-error` variant name — used as the wire contract
233/// for the designer's `{ok, data, error}` response envelope. Never
234/// rename existing codes without a wire-breaking version bump.
235#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
236#[serde(rename_all = "kebab-case", tag = "kind", content = "message")]
237pub enum HostExtensionError {
238    #[error("invalid input: {0}")]
239    InvalidInput(String),
240    #[error("missing capability: {0}")]
241    MissingCapability(String),
242    #[error("permission denied: {0}")]
243    PermissionDenied(String),
244    #[error("not found: {0}")]
245    NotFound(String),
246    #[error("schema invalid: {0}")]
247    SchemaInvalid(String),
248    #[error("internal: {0}")]
249    Internal(String),
250}
251
252impl HostExtensionError {
253    /// Stable kebab-case code matching the WIT `extension-error` variant
254    /// name. This string is the wire contract for the designer's
255    /// `{ok, data, error}` envelope — never rename existing codes.
256    #[must_use]
257    pub fn code(&self) -> &'static str {
258        match self {
259            Self::InvalidInput(_) => "invalid-input",
260            Self::MissingCapability(_) => "missing-capability",
261            Self::PermissionDenied(_) => "permission-denied",
262            Self::NotFound(_) => "not-found",
263            Self::SchemaInvalid(_) => "schema-invalid",
264            Self::Internal(_) => "internal",
265        }
266    }
267}
268
269/// Host-side mirror of WIT
270/// `greentic:extension-design/roles@0.2.0::role-error`.
271///
272/// Why a `compile-role` call failed. Distinct from
273/// [`crate::error::RuntimeError`]: `RuntimeError` represents host /
274/// runtime failures (extension-not-found, signature, IO), while
275/// `RoleError` represents domain-level outcomes the LLM and designer
276/// can act on (unknown role, invalid input, target not supported).
277#[derive(Debug, Clone, thiserror::Error)]
278pub enum RoleError {
279    #[error("unknown role: {0}")]
280    UnknownRole(String),
281    #[error("invalid input: {0:?}")]
282    InvalidInput(Vec<Diagnostic>),
283    #[error("compile failed: {0}")]
284    CompileFailed(String),
285    #[error("target not supported: {0:?}")]
286    TargetNotSupported(TargetKind),
287    #[error("schema version not supported: {0}")]
288    VersionNotSupported(u32),
289    #[error("host: {0}")]
290    Host(#[from] HostExtensionError),
291}
292
293#[cfg(test)]
294mod host_extension_error_tests {
295    #[test]
296    fn host_extension_error_codes_are_stable_kebab() {
297        use super::HostExtensionError as E;
298        let cases = [
299            (E::InvalidInput("x".into()), "invalid-input"),
300            (E::MissingCapability("x".into()), "missing-capability"),
301            (E::PermissionDenied("x".into()), "permission-denied"),
302            (E::NotFound("x".into()), "not-found"),
303            (E::SchemaInvalid("x".into()), "schema-invalid"),
304            (E::Internal("x".into()), "internal"),
305        ];
306        for (e, code) in cases {
307            assert_eq!(e.code(), code);
308        }
309    }
310}
311
312#[cfg(test)]
313mod target_summary_tests {
314    use super::*;
315
316    #[test]
317    fn target_summary_serializes_and_deserializes() {
318        let t = TargetSummary {
319            id: "aws-ecs-fargate-local".into(),
320            display_name: "AWS ECS Fargate (local creds)".into(),
321            description: "Deploy to AWS ECS Fargate using ambient credentials.".into(),
322            icon_path: Some("icons/aws.svg".into()),
323            supports_rollback: true,
324        };
325        let json = serde_json::to_string(&t).unwrap();
326        let back: TargetSummary = serde_json::from_str(&json).unwrap();
327        assert_eq!(back.id, t.id);
328        assert!(back.supports_rollback);
329    }
330}