1use serde::{Deserialize, Deserializer, Serialize};
3
4use crate::schema::{IsoDateTime, NonEmptyString, RunxSchema};
5use crate::{JsonNumber, JsonObject, Reference, ResolutionRequest};
6
7pub const EXTERNAL_ADAPTER_PROTOCOL_VERSION: &str = "runx.external_adapter.v1";
8
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
12pub enum ExternalAdapterProtocolVersion {
13 #[serde(rename = "runx.external_adapter.v1")]
14 V1,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
18pub enum ExternalAdapterManifestSchema {
19 #[serde(rename = "runx.external_adapter.manifest.v1")]
20 V1,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
24pub enum ExternalAdapterCredentialRequestSchema {
25 #[serde(rename = "runx.external_adapter.credential_request.v1")]
26 V1,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
30pub enum ExternalAdapterInvocationSchema {
31 #[serde(rename = "runx.external_adapter.invocation.v1")]
32 V1,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
36pub enum ExternalAdapterHostResolutionSchema {
37 #[serde(rename = "runx.external_adapter.host_resolution.v1")]
38 V1,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
42pub enum ExternalAdapterCancellationSchema {
43 #[serde(rename = "runx.external_adapter.cancellation.v1")]
44 V1,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
48#[serde(rename_all = "snake_case")]
49pub enum ExternalAdapterTransportKind {
50 Process,
51 Http,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
55#[serde(rename_all = "snake_case")]
56pub enum ExternalAdapterStatus {
57 Completed,
58 Failed,
59 HostResolutionRequested,
60 Cancelled,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
64#[serde(rename_all = "snake_case")]
65pub enum ExternalAdapterCredentialPurpose {
66 ProviderApi,
67 Registry,
68 ArtifactStore,
69 WebhookVerification,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
73#[serde(deny_unknown_fields)]
74pub struct ExternalAdapterTransport {
75 pub kind: ExternalAdapterTransportKind,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub command: Option<NonEmptyString>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub args: Option<Vec<String>>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub endpoint: Option<NonEmptyString>,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
85#[serde(deny_unknown_fields)]
86pub struct ExternalAdapterCredentialNeed {
87 pub purpose: ExternalAdapterCredentialPurpose,
88 pub provider: NonEmptyString,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub scope_refs: Option<Vec<Reference>>,
91 pub required: bool,
92}
93
94#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
95#[serde(deny_unknown_fields)]
96pub struct ExternalAdapterSandboxIntent {
97 pub profile: NonEmptyString,
98 pub network: bool,
99 pub cwd_policy: NonEmptyString,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub writable_paths: Option<Vec<NonEmptyString>>,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
105#[serde(deny_unknown_fields)]
106pub struct ExternalAdapterTimeouts {
107 pub startup_ms: u64,
108 pub invocation_ms: u64,
109}
110
111#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
112#[serde(deny_unknown_fields)]
113#[runx_schema(id = "runx.external_adapter.manifest.v1")]
114pub struct ExternalAdapterManifest {
115 pub schema: ExternalAdapterManifestSchema,
116 pub protocol_version: ExternalAdapterProtocolVersion,
117 pub adapter_id: NonEmptyString,
118 pub name: NonEmptyString,
119 pub version: NonEmptyString,
120 pub supported_source_types: Vec<NonEmptyString>,
121 pub transport: ExternalAdapterTransport,
122 pub timeouts: ExternalAdapterTimeouts,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub credential_needs: Option<Vec<ExternalAdapterCredentialNeed>>,
125 pub sandbox_intent: ExternalAdapterSandboxIntent,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub metadata: Option<JsonObject>,
128}
129
130#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
131#[serde(deny_unknown_fields)]
132pub struct ExternalAdapterCredentialReference {
133 pub credential_ref: Reference,
134 pub provider: NonEmptyString,
135 pub purpose: ExternalAdapterCredentialPurpose,
136}
137
138#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
139#[serde(deny_unknown_fields)]
140#[runx_schema(id = "runx.external_adapter.credential_request.v1")]
141pub struct ExternalAdapterCredentialRequest {
142 pub schema: ExternalAdapterCredentialRequestSchema,
143 pub protocol_version: ExternalAdapterProtocolVersion,
144 pub request_id: NonEmptyString,
145 pub adapter_id: NonEmptyString,
146 pub invocation_id: NonEmptyString,
147 pub credential_refs: Vec<ExternalAdapterCredentialReference>,
148 pub requested_at: IsoDateTime,
149}
150
151#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
152#[serde(deny_unknown_fields)]
153#[runx_schema(id = "runx.external_adapter.invocation.v1")]
154pub struct ExternalAdapterInvocation {
155 pub schema: ExternalAdapterInvocationSchema,
156 pub protocol_version: ExternalAdapterProtocolVersion,
157 pub invocation_id: NonEmptyString,
158 pub adapter_id: NonEmptyString,
159 pub run_id: NonEmptyString,
160 pub step_id: NonEmptyString,
161 pub source_type: NonEmptyString,
162 pub skill_ref: NonEmptyString,
163 pub harness_ref: Reference,
164 pub host_ref: Reference,
165 pub inputs: JsonObject,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub resolved_inputs: Option<JsonObject>,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub cwd: Option<NonEmptyString>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub receipt_dir: Option<NonEmptyString>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub env: Option<JsonObject>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub credential_refs: Option<Vec<ExternalAdapterCredentialReference>>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub metadata: Option<JsonObject>,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
181#[serde(deny_unknown_fields)]
182pub struct ExternalAdapterArtifactObservation {
183 pub artifact_ref: Reference,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub summary: Option<String>,
186}
187
188#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
189#[serde(deny_unknown_fields)]
190pub struct ExternalAdapterErrorObservation {
191 pub code: String,
192 pub message: String,
193 pub retryable: bool,
194}
195
196#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
197#[serde(untagged)]
198pub enum ExternalAdapterTelemetryValue {
199 Number(JsonNumber),
200 String(String),
201 Bool(bool),
202}
203
204#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
205#[serde(deny_unknown_fields)]
206pub struct ExternalAdapterTelemetryObservation {
207 pub name: String,
208 pub value: ExternalAdapterTelemetryValue,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub unit: Option<String>,
211}
212
213#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
214#[serde(deny_unknown_fields)]
215#[runx_schema(id = "runx.external_adapter.response.v1")]
216pub struct ExternalAdapterResponse {
217 pub schema: String,
218 pub protocol_version: String,
219 pub invocation_id: String,
220 pub adapter_id: String,
221 pub status: ExternalAdapterStatus,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub stdout: Option<String>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub stderr: Option<String>,
226 #[serde(
227 default,
228 deserialize_with = "deserialize_optional_nullable_i64",
229 skip_serializing_if = "Option::is_none"
230 )]
231 pub exit_code: Option<Option<i64>>,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub output: Option<JsonObject>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub artifacts: Option<Vec<ExternalAdapterArtifactObservation>>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub errors: Option<Vec<ExternalAdapterErrorObservation>>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub telemetry: Option<Vec<ExternalAdapterTelemetryObservation>>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub metadata: Option<JsonObject>,
242 pub observed_at: String,
243}
244
245#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
246#[serde(deny_unknown_fields)]
247#[runx_schema(id = "runx.external_adapter.host_resolution.v1")]
248pub struct ExternalAdapterHostResolutionFrame {
249 pub schema: ExternalAdapterHostResolutionSchema,
250 pub protocol_version: ExternalAdapterProtocolVersion,
251 pub frame_id: NonEmptyString,
252 pub invocation_id: NonEmptyString,
253 pub adapter_id: NonEmptyString,
254 pub request: ResolutionRequest,
255 pub requested_at: IsoDateTime,
256}
257
258#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
259#[serde(deny_unknown_fields)]
260#[runx_schema(id = "runx.external_adapter.cancellation.v1")]
261pub struct ExternalAdapterCancellationFrame {
262 pub schema: ExternalAdapterCancellationSchema,
263 pub protocol_version: ExternalAdapterProtocolVersion,
264 pub frame_id: NonEmptyString,
265 pub invocation_id: NonEmptyString,
266 pub adapter_id: NonEmptyString,
267 pub reason: NonEmptyString,
268 pub requested_at: IsoDateTime,
269}
270
271fn deserialize_optional_nullable_i64<'de, D>(
272 deserializer: D,
273) -> Result<Option<Option<i64>>, D::Error>
274where
275 D: Deserializer<'de>,
276{
277 Option::<i64>::deserialize(deserializer).map(Some)
278}