1use super::connection::WireAuthBindingRef;
4use super::runtime::WireTurnMetadataOverride;
5use super::session::WireContentInput;
6use super::supervisor_bridge::BridgeBootstrapToken;
7use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
8use meerkat_core::OutputSchema;
9use meerkat_core::{
10 HandlingMode,
11 types::{RenderClass, RenderMetadata, RenderSalience},
12};
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::collections::BTreeMap;
16
17use meerkat_core::{SurfaceMetadata, SurfaceMetadataError};
18
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[serde(rename_all = "snake_case")]
22pub enum WireMobBackendKind {
23 #[default]
24 Session,
25 External,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
35#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
36pub enum WireRuntimeBinding {
37 Session,
38 External {
39 address: String,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 bootstrap_token: Option<BridgeBootstrapToken>,
42 identity: WireTrustedPeerIdentity,
46 },
47}
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
50#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
51#[serde(rename_all = "snake_case")]
52pub enum WireMobRuntimeMode {
53 #[default]
54 AutonomousHost,
55 TurnDriven,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
61#[serde(tag = "mode", rename_all = "snake_case")]
62pub enum WireMemberLaunchMode {
63 Fresh,
64 Resume {
65 bridge_session_id: String,
66 },
67 Fork {
68 source_member_id: String,
69 #[serde(default)]
70 fork_context: WireForkContext,
71 },
72}
73
74#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
76#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
77#[serde(tag = "type", rename_all = "snake_case")]
78pub enum WireForkContext {
79 #[default]
80 FullHistory,
81 LastMessages {
82 count: u32,
83 },
84}
85
86#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
89#[serde(tag = "type", content = "value", rename_all = "snake_case")]
90pub enum WireBudgetSplitPolicy {
91 #[default]
92 Equal,
93 Proportional,
94 Remaining,
95 Fixed(u64),
96}
97
98#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
100#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
101#[serde(tag = "type", content = "value", rename_all = "snake_case")]
102pub enum WireToolAccessPolicy {
103 #[default]
104 Inherit,
105 AllowList(Vec<String>),
106 DenyList(Vec<String>),
107}
108
109#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
111#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
112pub enum WireToolFilter {
113 #[default]
114 All,
115 Allow(Vec<String>),
116 Deny(Vec<String>),
117}
118
119#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
122#[serde(deny_unknown_fields)]
123pub struct WireMobToolConfig {
124 #[serde(default)]
125 pub builtins: bool,
126 #[serde(default)]
127 pub shell: bool,
128 #[serde(default)]
129 pub comms: bool,
130 #[serde(default)]
131 pub memory: bool,
132 #[serde(default)]
133 pub workgraph: bool,
134 #[serde(default)]
135 pub mob: bool,
136 #[serde(default)]
137 pub schedule: bool,
138 #[serde(default)]
139 pub image_generation: bool,
140 #[serde(default)]
141 pub mcp: Vec<String>,
142}
143
144#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
149#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
150#[serde(rename_all = "snake_case")]
151pub enum WireMobResumeOverrideField {
152 Model,
153 Provider,
154 ProviderParams,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
159#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
160#[serde(deny_unknown_fields)]
161pub struct WireMobProfile {
162 pub model: String,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub provider: Option<meerkat_core::Provider>,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub self_hosted_server_id: Option<String>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub image_generation_provider: Option<meerkat_core::Provider>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub auto_compact_threshold: Option<std::num::NonZeroU64>,
176 #[serde(default, skip_serializing_if = "Vec::is_empty")]
178 pub resume_overrides: Vec<WireMobResumeOverrideField>,
179 #[serde(default)]
180 pub skills: Vec<String>,
181 #[serde(default)]
182 pub tools: WireMobToolConfig,
183 #[serde(default)]
184 pub peer_description: String,
185 #[serde(default)]
186 pub external_addressable: bool,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub backend: Option<WireMobBackendKind>,
189 #[serde(default)]
190 pub runtime_mode: WireMobRuntimeMode,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub max_inline_peer_notifications: Option<i32>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub output_schema: Option<Value>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub provider_params: Option<crate::wire::runtime::WireProviderParamsOverride>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
200#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
201#[serde(deny_unknown_fields)]
202pub struct MobOrchestratorInput {
203 pub profile: String,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
207#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
208#[serde(tag = "source", rename_all = "snake_case")]
209pub enum MobSkillSourceInput {
210 Inline { content: String },
211 Path { path: String },
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216#[serde(deny_unknown_fields)]
217pub struct MobRoleWiringRuleInput {
218 pub a: String,
219 pub b: String,
220}
221
222#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
223#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
224#[serde(deny_unknown_fields)]
225pub struct MobWiringRulesInput {
226 #[serde(default)]
227 pub auto_wire_orchestrator: bool,
228 #[serde(default, skip_serializing_if = "Vec::is_empty")]
229 pub role_wiring: Vec<MobRoleWiringRuleInput>,
230}
231
232#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
233#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
234#[serde(deny_unknown_fields)]
235pub struct MobToolConfigInput {
236 #[serde(default)]
237 pub builtins: bool,
238 #[serde(default)]
239 pub shell: bool,
240 #[serde(default)]
241 pub comms: bool,
242 #[serde(default)]
243 pub memory: bool,
244 #[serde(default)]
245 pub workgraph: bool,
246 #[serde(default)]
247 pub mob: bool,
248 #[serde(default)]
249 pub schedule: bool,
250 #[serde(default)]
251 pub image_generation: bool,
252 #[serde(default, skip_serializing_if = "Vec::is_empty")]
253 pub mcp: Vec<String>,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
262#[allow(clippy::large_enum_variant)]
263#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
264#[serde(untagged)]
265pub enum MobProfileBindingInput {
266 RealmRef {
268 realm_profile: String,
270 },
271 Inline(MobProfileInput),
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
276#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
277#[serde(deny_unknown_fields)]
278pub struct MobProfileInput {
279 pub model: String,
280 #[serde(default, skip_serializing_if = "Option::is_none")]
283 pub provider: Option<meerkat_core::Provider>,
284 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub self_hosted_server_id: Option<String>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub image_generation_provider: Option<meerkat_core::Provider>,
290 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub auto_compact_threshold: Option<std::num::NonZeroU64>,
293 #[serde(default, skip_serializing_if = "Vec::is_empty")]
295 pub resume_overrides: Vec<WireMobResumeOverrideField>,
296 #[serde(default, skip_serializing_if = "Vec::is_empty")]
297 pub skills: Vec<String>,
298 #[serde(default)]
299 pub tools: MobToolConfigInput,
300 #[serde(default, skip_serializing_if = "String::is_empty")]
301 pub peer_description: String,
302 #[serde(default)]
303 pub external_addressable: bool,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub backend: Option<WireMobBackendKind>,
306 #[serde(default)]
307 pub runtime_mode: WireMobRuntimeMode,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub max_inline_peer_notifications: Option<i32>,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub output_schema: Option<OutputSchema>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub provider_params: Option<crate::wire::runtime::WireProviderParamsOverride>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
320#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
321#[serde(deny_unknown_fields)]
322pub struct MobExternalBackendConfigInput {
323 pub address_base: String,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub supervisor_bridge: Option<MobSupervisorBridgeEndpointConfigInput>,
326}
327
328#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
329#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
330#[serde(deny_unknown_fields)]
331pub struct MobSupervisorBridgeEndpointConfigInput {
332 #[serde(default, skip_serializing_if = "Option::is_none")]
333 pub bind_address: Option<String>,
334 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub advertised_address: Option<String>,
336}
337
338#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
339#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
340#[serde(deny_unknown_fields)]
341pub struct MobBackendConfigInput {
342 #[serde(default)]
343 pub default: WireMobBackendKind,
344 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub external: Option<MobExternalBackendConfigInput>,
346}
347
348#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
349#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
350#[serde(rename_all = "snake_case")]
351pub enum MobDispatchModeInput {
352 #[default]
353 FanOut,
354 OneToOne,
355 FanIn,
356}
357
358#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
359#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
360#[serde(tag = "type", rename_all = "snake_case")]
361pub enum MobCollectionPolicyInput {
362 #[default]
363 All,
364 Any,
365 Quorum {
366 n: u8,
367 },
368}
369
370#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
371#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
372#[serde(rename_all = "snake_case")]
373pub enum MobDependencyModeInput {
374 #[default]
375 All,
376 Any,
377}
378
379#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
384#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
385#[serde(rename_all = "snake_case")]
386pub enum MobStepOutputFormatInput {
387 Json,
388 Text,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
392#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
393#[serde(tag = "op", rename_all = "snake_case")]
394pub enum MobConditionExprInput {
395 Eq { path: String, value: Value },
396 In { path: String, values: Vec<Value> },
397 Gt { path: String, value: Value },
398 Lt { path: String, value: Value },
399 And { exprs: Vec<MobConditionExprInput> },
400 Or { exprs: Vec<MobConditionExprInput> },
401 Not { expr: Box<MobConditionExprInput> },
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
405#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
406#[serde(deny_unknown_fields)]
407pub struct MobFrameSpecInput {
408 pub nodes: BTreeMap<String, MobFlowNodeInput>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
412#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
413#[serde(tag = "kind", rename_all = "snake_case")]
414pub enum MobFlowNodeInput {
415 Step(MobFrameStepInput),
416 RepeatUntil(MobRepeatUntilInput),
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
420#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
421#[serde(deny_unknown_fields)]
422pub struct MobFrameStepInput {
423 pub step_id: String,
424 #[serde(default, skip_serializing_if = "Vec::is_empty")]
425 pub depends_on: Vec<String>,
426 #[serde(default)]
427 pub depends_on_mode: MobDependencyModeInput,
428 #[serde(default, skip_serializing_if = "Option::is_none")]
429 pub branch: Option<String>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
433#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
434#[serde(deny_unknown_fields)]
435pub struct MobRepeatUntilInput {
436 pub loop_id: String,
437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
438 pub depends_on: Vec<String>,
439 #[serde(default)]
440 pub depends_on_mode: MobDependencyModeInput,
441 pub body: MobFrameSpecInput,
442 pub until: MobConditionExprInput,
443 pub max_iterations: u32,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
447#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
448#[serde(deny_unknown_fields)]
449pub struct MobFlowStepInput {
450 pub role: String,
451 pub message: WireContentInput,
452 #[serde(default, skip_serializing_if = "Vec::is_empty")]
453 pub depends_on: Vec<String>,
454 #[serde(default)]
455 pub dispatch_mode: MobDispatchModeInput,
456 #[serde(default)]
457 pub collection_policy: MobCollectionPolicyInput,
458 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub condition: Option<MobConditionExprInput>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 pub timeout_ms: Option<u64>,
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub expected_schema_ref: Option<String>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub branch: Option<String>,
466 #[serde(default)]
467 pub depends_on_mode: MobDependencyModeInput,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub allowed_tools: Option<Vec<String>>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub blocked_tools: Option<Vec<String>>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub output_format: Option<MobStepOutputFormatInput>,
476}
477
478#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
479#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
480#[serde(deny_unknown_fields)]
481pub struct MobFlowSpecInput {
482 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub description: Option<String>,
484 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
485 pub steps: BTreeMap<String, MobFlowStepInput>,
486 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub root: Option<MobFrameSpecInput>,
488}
489
490#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
491#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
492#[serde(rename_all = "snake_case")]
493pub enum MobPolicyModeInput {
494 #[default]
495 Advisory,
496 Strict,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
500#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
501#[serde(deny_unknown_fields)]
502pub struct MobTopologyRuleInput {
503 pub from_role: String,
504 pub to_role: String,
505 pub allowed: bool,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
509#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
510#[serde(deny_unknown_fields)]
511pub struct MobTopologySpecInput {
512 pub mode: MobPolicyModeInput,
513 pub rules: Vec<MobTopologyRuleInput>,
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
517#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
518#[serde(deny_unknown_fields)]
519pub struct MobSupervisorSpecInput {
520 pub role: String,
521 pub escalation_threshold: u32,
522 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub escalation_turn_timeout_ms: Option<u64>,
526}
527
528#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
530#[serde(deny_unknown_fields)]
531pub struct MobLimitsSpecInput {
532 #[serde(default, skip_serializing_if = "Option::is_none")]
533 pub max_flow_duration_ms: Option<u64>,
534 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub max_step_retries: Option<u32>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
537 pub max_orphaned_turns: Option<u32>,
538 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub cancel_grace_timeout_ms: Option<u64>,
540 #[serde(default, skip_serializing_if = "Option::is_none")]
541 pub max_active_nodes: Option<u64>,
542 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub max_active_frames: Option<u64>,
544 #[serde(default, skip_serializing_if = "Option::is_none")]
545 pub max_frame_depth: Option<u64>,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
549#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
550#[serde(tag = "mode", rename_all = "snake_case")]
551pub enum MobSpawnPolicyInput {
552 None,
553 Auto {
554 profile_map: BTreeMap<String, String>,
555 },
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
559#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
560#[serde(deny_unknown_fields)]
561pub struct MobEventRouterConfigInput {
562 #[serde(default = "default_event_router_buffer_size")]
563 pub buffer_size: usize,
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub include_patterns: Option<Vec<String>>,
566 #[serde(default, skip_serializing_if = "Option::is_none")]
567 pub exclude_patterns: Option<Vec<String>>,
568}
569
570const fn default_event_router_buffer_size() -> usize {
571 256
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
583#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
584#[serde(deny_unknown_fields)]
585pub struct MobDefinitionInput {
586 pub id: String,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
588 pub orchestrator: Option<MobOrchestratorInput>,
589 pub profiles: BTreeMap<String, MobProfileBindingInput>,
590 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
594 pub models: BTreeMap<String, meerkat_core::config::CustomModelConfig>,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
597 pub image_generation_provider: Option<meerkat_core::Provider>,
598 #[serde(default)]
599 pub wiring: MobWiringRulesInput,
600 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
601 pub skills: BTreeMap<String, MobSkillSourceInput>,
602 #[serde(default)]
603 pub backend: MobBackendConfigInput,
604 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
605 pub flows: BTreeMap<String, MobFlowSpecInput>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub topology: Option<MobTopologySpecInput>,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub supervisor: Option<MobSupervisorSpecInput>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub limits: Option<MobLimitsSpecInput>,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub spawn_policy: Option<MobSpawnPolicyInput>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub event_router: Option<MobEventRouterConfigInput>,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
620#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
621#[serde(deny_unknown_fields)]
622pub struct MobCreateParams {
623 pub definition: MobDefinitionInput,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
628#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
629pub struct MobCreateResult {
630 pub mob_id: String,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
635#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
636#[serde(deny_unknown_fields)]
637pub struct MobIdParams {
638 pub mob_id: String,
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
643#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
644#[serde(deny_unknown_fields)]
645pub struct MobMemberParams {
646 pub mob_id: String,
647 pub agent_identity: String,
648}
649
650#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
658#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
659pub enum WireMobLifecycleStatus {
660 Creating,
661 Running,
662 Stopped,
663 Completed,
664 Destroyed,
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
669#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
670pub struct MobStatusResult {
671 pub mob_id: String,
672 pub status: WireMobLifecycleStatus,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
677#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
678pub struct MobListResult {
679 pub mobs: Vec<MobStatusResult>,
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
684#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
685#[serde(deny_unknown_fields)]
686pub struct MobSpawnParams {
687 pub mob_id: String,
688 pub profile: String,
689 pub agent_identity: String,
690 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub initial_message: Option<WireContentInput>,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
693 pub runtime_mode: Option<WireMobRuntimeMode>,
694 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub backend: Option<WireMobBackendKind>,
696 #[serde(default, skip_serializing_if = "Option::is_none")]
697 pub labels: Option<BTreeMap<String, String>>,
698 #[serde(default, skip_serializing_if = "Option::is_none")]
699 pub context: Option<Value>,
700 #[serde(default, skip_serializing_if = "Option::is_none")]
701 pub additional_instructions: Option<Vec<String>>,
702 #[serde(default, skip_serializing_if = "Option::is_none")]
703 pub binding: Option<WireRuntimeBinding>,
704 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub shell_env: Option<BTreeMap<String, String>>,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub auto_wire_parent: Option<bool>,
708 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub launch_mode: Option<WireMemberLaunchMode>,
710 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub tool_access_policy: Option<WireToolAccessPolicy>,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub budget_split_policy: Option<WireBudgetSplitPolicy>,
714 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub inherited_tool_filter: Option<WireToolFilter>,
716 #[serde(default, skip_serializing_if = "Option::is_none")]
717 pub override_profile: Option<WireMobProfile>,
718 #[serde(default, skip_serializing_if = "Option::is_none")]
719 pub auth_binding: Option<WireAuthBindingRef>,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
724#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
725pub struct MobSpawnResult {
726 pub mob_id: String,
727 pub agent_identity: String,
728 pub member_ref: WireMemberRef,
729}
730
731#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
733#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
734#[serde(deny_unknown_fields)]
735pub struct MobSpawnSpecParams {
736 pub profile: String,
737 pub agent_identity: String,
738 #[serde(default, skip_serializing_if = "Option::is_none")]
739 pub initial_message: Option<WireContentInput>,
740 #[serde(default, skip_serializing_if = "Option::is_none")]
741 pub runtime_mode: Option<WireMobRuntimeMode>,
742 #[serde(default, skip_serializing_if = "Option::is_none")]
743 pub backend: Option<WireMobBackendKind>,
744 #[serde(default, skip_serializing_if = "Option::is_none")]
745 pub labels: Option<BTreeMap<String, String>>,
746 #[serde(default, skip_serializing_if = "Option::is_none")]
747 pub context: Option<Value>,
748 #[serde(default, skip_serializing_if = "Option::is_none")]
749 pub additional_instructions: Option<Vec<String>>,
750 #[serde(default, skip_serializing_if = "Option::is_none")]
751 pub auth_binding: Option<WireAuthBindingRef>,
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
756#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
757#[serde(deny_unknown_fields)]
758pub struct MobSpawnManyParams {
759 pub mob_id: String,
760 pub specs: Vec<MobSpawnSpecParams>,
761}
762
763#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
765#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
766#[serde(rename_all = "snake_case")]
767pub enum MobSpawnManyResultStatus {
768 Spawned,
769 Failed,
770}
771
772#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
774#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
775#[serde(deny_unknown_fields)]
776pub struct MobSpawnManySpawnedResult {
777 pub agent_identity: String,
778 pub member_ref: WireMemberRef,
779}
780
781#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
783#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
784#[serde(rename_all = "snake_case")]
785pub enum MobSpawnManyFailureCause {
786 ProfileNotFound,
787 MemberNotFound,
788 MemberAlreadyExists,
789 NotExternallyAddressable,
790 InvalidTransition,
791 WiringError,
792 BridgeCommandRejected,
793 MemberRestoreFailed,
794 KickoffWaitTimedOut,
795 ReadyWaitTimedOut,
796 DefinitionError,
797 FlowNotFound,
798 FlowFailed,
799 RunNotFound,
800 RunCanceled,
801 FlowTurnTimedOut,
802 FrameDepthLimitExceeded,
803 FrameAtomicPersistenceUnavailable,
804 SpecRevisionConflict,
805 SchemaValidation,
806 InsufficientTargets,
807 TopologyViolation,
808 BridgeDeliveryRejected,
809 SupervisorEscalation,
810 UnsupportedForMode,
811 MissingMemberCapability,
812 ResetBarrier,
813 StorageError,
814 SessionError,
815 CommsError,
816 CallbackPending,
817 StaleFenceToken,
818 StaleEventCursor,
819 WorkNotFound,
820 Internal,
821}
822
823#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
825#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
826#[serde(deny_unknown_fields)]
827pub struct MobSpawnManyFailedResult {
828 pub cause: MobSpawnManyFailureCause,
829 pub message: String,
830}
831
832#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
834#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
835#[serde(untagged)]
836pub enum MobSpawnManyResultPayload {
837 Spawned(MobSpawnManySpawnedResult),
838 Failed(MobSpawnManyFailedResult),
839}
840
841#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
843#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
844#[serde(try_from = "MobSpawnManyResultEntryRaw")]
845pub struct MobSpawnManyResultEntry {
846 pub status: MobSpawnManyResultStatus,
847 pub result: MobSpawnManyResultPayload,
848}
849
850#[derive(Debug, Deserialize)]
851#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
852#[serde(deny_unknown_fields)]
853struct MobSpawnManyResultEntryRaw {
854 status: MobSpawnManyResultStatus,
855 result: MobSpawnManyResultPayload,
856}
857
858impl TryFrom<MobSpawnManyResultEntryRaw> for MobSpawnManyResultEntry {
859 type Error = String;
860
861 fn try_from(raw: MobSpawnManyResultEntryRaw) -> Result<Self, Self::Error> {
862 let entry = Self {
863 status: raw.status,
864 result: raw.result,
865 };
866 entry.validate().map_err(str::to_owned)?;
867 Ok(entry)
868 }
869}
870
871impl MobSpawnManyResultEntry {
872 pub fn spawned(agent_identity: impl Into<String>, member_ref: WireMemberRef) -> Self {
873 Self {
874 status: MobSpawnManyResultStatus::Spawned,
875 result: MobSpawnManyResultPayload::Spawned(MobSpawnManySpawnedResult {
876 agent_identity: agent_identity.into(),
877 member_ref,
878 }),
879 }
880 }
881
882 pub fn failed(cause: MobSpawnManyFailureCause, message: impl Into<String>) -> Self {
883 Self {
884 status: MobSpawnManyResultStatus::Failed,
885 result: MobSpawnManyResultPayload::Failed(MobSpawnManyFailedResult {
886 cause,
887 message: message.into(),
888 }),
889 }
890 }
891
892 pub fn validate(&self) -> Result<(), &'static str> {
893 match (&self.status, &self.result) {
894 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Spawned(_))
895 | (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Failed(_)) => Ok(()),
896 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Failed(_)) => {
897 Err("mob spawn_many result status spawned requires spawned result")
898 }
899 (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Spawned(_)) => {
900 Err("mob spawn_many result status failed requires failed result")
901 }
902 }
903 }
904}
905
906#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
908#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
909pub struct MobSpawnManyResult {
910 pub results: Vec<MobSpawnManyResultEntry>,
911}
912
913#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
915#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
916pub struct MobRetireResult {
917 pub retired: bool,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
922#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
923#[serde(deny_unknown_fields)]
924pub struct MobRespawnParams {
925 pub mob_id: String,
926 pub agent_identity: String,
927 #[serde(default, skip_serializing_if = "Option::is_none")]
928 pub initial_message: Option<WireContentInput>,
929}
930
931#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
933#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
934pub struct MobRespawnReceipt {
935 pub identity: String,
936 pub member_ref: WireMemberRef,
937}
938
939#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
944#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
945#[serde(rename_all = "snake_case")]
946pub enum WireMobRespawnOutcome {
947 Completed,
948 TopologyRestoreFailed,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
953#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
954pub struct MobRespawnResult {
955 pub status: WireMobRespawnOutcome,
956 pub receipt: MobRespawnReceipt,
957 #[serde(default, skip_serializing_if = "Vec::is_empty")]
958 pub failed_peer_ids: Vec<String>,
959}
960
961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
963#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
964pub struct MobMembersResult {
965 pub mob_id: String,
966 pub members: Vec<MobMemberListEntryWire>,
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
971#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
972#[serde(deny_unknown_fields)]
973pub struct MobEventsParams {
974 pub mob_id: String,
975 #[serde(default)]
976 pub after_cursor: u64,
977 #[serde(default = "default_mob_events_limit")]
978 pub limit: usize,
979 #[serde(default)]
980 pub strict: bool,
981}
982
983const fn default_mob_events_limit() -> usize {
984 100
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
989#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
990pub struct MobEventsResult {
991 pub events: Vec<Value>,
992}
993
994#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
996#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
997#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
998pub enum WireTrustedPeerIdentity {
999 Ed25519PublicKey { public_key: String },
1001}
1002
1003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1005pub struct ResolvedWireTrustedPeerIdentity {
1006 pub peer_id: meerkat_core::comms::PeerId,
1007 pub pubkey: [u8; 32],
1008}
1009
1010#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
1012pub enum WireTrustedPeerIdentityError {
1013 #[error("external peer identity public_key must start with 'ed25519:'")]
1014 MissingEd25519Prefix,
1015 #[error("external peer identity public_key is not valid base64: {0}")]
1016 InvalidBase64(String),
1017 #[error("external peer identity public_key must decode to 32 bytes, got {actual}")]
1018 InvalidLength { actual: usize },
1019 #[error("external peer identity public_key must be non-zero")]
1020 ZeroPublicKey,
1021}
1022
1023impl WireTrustedPeerIdentity {
1024 pub fn resolve(&self) -> Result<ResolvedWireTrustedPeerIdentity, WireTrustedPeerIdentityError> {
1025 match self {
1026 Self::Ed25519PublicKey { public_key } => {
1027 let pubkey = parse_ed25519_public_key(public_key)?;
1028 if pubkey == [0u8; 32] {
1029 return Err(WireTrustedPeerIdentityError::ZeroPublicKey);
1030 }
1031 Ok(ResolvedWireTrustedPeerIdentity {
1032 peer_id: meerkat_core::comms::PeerId::from_ed25519_pubkey(&pubkey),
1033 pubkey,
1034 })
1035 }
1036 }
1037 }
1038}
1039
1040fn parse_ed25519_public_key(raw: &str) -> Result<[u8; 32], WireTrustedPeerIdentityError> {
1041 const PREFIX: &str = "ed25519:";
1042 let encoded = raw
1043 .strip_prefix(PREFIX)
1044 .ok_or(WireTrustedPeerIdentityError::MissingEd25519Prefix)?;
1045 let bytes = BASE64
1046 .decode(encoded)
1047 .map_err(|err| WireTrustedPeerIdentityError::InvalidBase64(err.to_string()))?;
1048 let actual = bytes.len();
1049 let pubkey: [u8; 32] = bytes
1050 .try_into()
1051 .map_err(|_| WireTrustedPeerIdentityError::InvalidLength { actual })?;
1052 Ok(pubkey)
1053}
1054
1055#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1061#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1062#[serde(deny_unknown_fields)]
1063pub struct WireTrustedPeerSpec {
1064 pub name: String,
1065 pub address: String,
1066 pub identity: WireTrustedPeerIdentity,
1067}
1068
1069#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1071#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1072#[serde(rename_all = "snake_case")]
1073pub enum MobPeerTarget {
1074 Local(String),
1075 External(WireTrustedPeerSpec),
1076}
1077
1078#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1080#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1081#[serde(deny_unknown_fields)]
1082pub struct MobWireParams {
1083 pub mob_id: String,
1084 pub member: String,
1085 pub peer: MobPeerTarget,
1086}
1087
1088#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1090#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1091pub struct MobWireResult {
1092 pub wired: bool,
1093}
1094
1095#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1097#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1098#[serde(deny_unknown_fields)]
1099pub struct MobWireMembersBatchEdge {
1100 pub a: String,
1101 pub b: String,
1102}
1103
1104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1106#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1107#[serde(deny_unknown_fields)]
1108pub struct MobWireMembersBatchParams {
1109 pub mob_id: String,
1110 pub edges: Vec<MobWireMembersBatchEdge>,
1111}
1112
1113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1115#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1116pub struct MobWireMembersBatchResult {
1117 pub requested: usize,
1118 pub wired: Vec<MobWireMembersBatchEdge>,
1119 pub already_wired: Vec<MobWireMembersBatchEdge>,
1120}
1121
1122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1124#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1125#[serde(deny_unknown_fields)]
1126pub struct MobUnwireParams {
1127 pub mob_id: String,
1128 pub member: String,
1129 pub peer: MobPeerTarget,
1130}
1131
1132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1134#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1135pub struct MobUnwireResult {
1136 pub unwired: bool,
1137}
1138
1139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1141#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1142#[serde(deny_unknown_fields)]
1143pub struct MobMemberSendParams {
1144 pub mob_id: String,
1145 pub agent_identity: String,
1146 pub content: WireContentInput,
1147 #[serde(default)]
1148 pub handling_mode: WireHandlingMode,
1149 #[serde(default, skip_serializing_if = "Option::is_none")]
1150 pub render_metadata: Option<WireRenderMetadata>,
1151}
1152
1153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1156pub struct WireAgentRuntimeId {
1157 pub identity: String,
1158 pub generation: u64,
1159}
1160
1161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1163#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1164pub struct MobMemberSendResult {
1165 pub mob_id: String,
1166 pub agent_identity: String,
1168 pub member_ref: WireMemberRef,
1173 pub handling_mode: WireHandlingMode,
1174}
1175
1176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1182#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1183#[serde(deny_unknown_fields)]
1184pub struct MobIngressInteractionParams {
1185 pub mob_id: String,
1186 pub spec: MobMemberSpecWire,
1187 pub content: WireContentInput,
1188 #[serde(default)]
1189 pub handling_mode: WireHandlingMode,
1190 #[serde(default, skip_serializing_if = "Option::is_none")]
1191 pub render_metadata: Option<WireRenderMetadata>,
1192}
1193
1194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1196#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1197pub struct MobIngressInteractionResult {
1198 pub mob_id: String,
1199 pub agent_identity: String,
1200 pub member_ref: WireMemberRef,
1201 pub ensure_outcome: MobEnsureMemberOutcomeWire,
1202 pub delivery: MobMemberSendResult,
1203 pub events_after_cursor: u64,
1205 pub latest_event_cursor: u64,
1207}
1208
1209#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1211#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1212#[serde(rename_all = "snake_case")]
1213pub enum WireHandlingMode {
1214 #[default]
1215 Queue,
1216 Steer,
1217}
1218
1219impl From<WireHandlingMode> for HandlingMode {
1220 fn from(mode: WireHandlingMode) -> Self {
1221 match mode {
1222 WireHandlingMode::Queue => HandlingMode::Queue,
1223 WireHandlingMode::Steer => HandlingMode::Steer,
1224 }
1225 }
1226}
1227
1228impl From<HandlingMode> for WireHandlingMode {
1229 fn from(mode: HandlingMode) -> Self {
1230 match mode {
1231 HandlingMode::Queue => WireHandlingMode::Queue,
1232 HandlingMode::Steer => WireHandlingMode::Steer,
1233 }
1234 }
1235}
1236
1237#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1239#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1240#[serde(rename_all = "snake_case")]
1241pub enum WireRenderClass {
1242 UserPrompt,
1243 PeerMessage,
1244 PeerRequest,
1245 PeerResponse,
1246 ExternalEvent,
1247 FlowStep,
1248 Continuation,
1249 SystemNotice,
1250 ToolScopeNotice,
1251 OpsProgress,
1252}
1253
1254impl From<WireRenderClass> for RenderClass {
1255 fn from(class: WireRenderClass) -> Self {
1256 match class {
1257 WireRenderClass::UserPrompt => RenderClass::UserPrompt,
1258 WireRenderClass::PeerMessage => RenderClass::PeerMessage,
1259 WireRenderClass::PeerRequest => RenderClass::PeerRequest,
1260 WireRenderClass::PeerResponse => RenderClass::PeerResponse,
1261 WireRenderClass::ExternalEvent => RenderClass::ExternalEvent,
1262 WireRenderClass::FlowStep => RenderClass::FlowStep,
1263 WireRenderClass::Continuation => RenderClass::Continuation,
1264 WireRenderClass::SystemNotice => RenderClass::SystemNotice,
1265 WireRenderClass::ToolScopeNotice => RenderClass::ToolScopeNotice,
1266 WireRenderClass::OpsProgress => RenderClass::OpsProgress,
1267 }
1268 }
1269}
1270
1271impl From<RenderClass> for WireRenderClass {
1272 fn from(class: RenderClass) -> Self {
1273 match class {
1274 RenderClass::UserPrompt => WireRenderClass::UserPrompt,
1275 RenderClass::PeerMessage => WireRenderClass::PeerMessage,
1276 RenderClass::PeerRequest => WireRenderClass::PeerRequest,
1277 RenderClass::PeerResponse => WireRenderClass::PeerResponse,
1278 RenderClass::ExternalEvent => WireRenderClass::ExternalEvent,
1279 RenderClass::FlowStep => WireRenderClass::FlowStep,
1280 RenderClass::Continuation => WireRenderClass::Continuation,
1281 RenderClass::SystemNotice => WireRenderClass::SystemNotice,
1282 RenderClass::ToolScopeNotice => WireRenderClass::ToolScopeNotice,
1283 RenderClass::OpsProgress => WireRenderClass::OpsProgress,
1284 }
1285 }
1286}
1287
1288#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1290#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1291#[serde(rename_all = "snake_case")]
1292pub enum WireRenderSalience {
1293 Background,
1294 Normal,
1295 Important,
1296 Urgent,
1297}
1298
1299impl From<WireRenderSalience> for RenderSalience {
1300 fn from(salience: WireRenderSalience) -> Self {
1301 match salience {
1302 WireRenderSalience::Background => RenderSalience::Background,
1303 WireRenderSalience::Normal => RenderSalience::Normal,
1304 WireRenderSalience::Important => RenderSalience::Important,
1305 WireRenderSalience::Urgent => RenderSalience::Urgent,
1306 }
1307 }
1308}
1309
1310impl From<RenderSalience> for WireRenderSalience {
1311 fn from(salience: RenderSalience) -> Self {
1312 match salience {
1313 RenderSalience::Background => WireRenderSalience::Background,
1314 RenderSalience::Normal => WireRenderSalience::Normal,
1315 RenderSalience::Important => WireRenderSalience::Important,
1316 RenderSalience::Urgent => WireRenderSalience::Urgent,
1317 }
1318 }
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1324pub struct WireRenderMetadata {
1325 pub class: WireRenderClass,
1326 #[serde(default, skip_serializing_if = "Option::is_none")]
1327 pub salience: Option<WireRenderSalience>,
1328}
1329
1330impl From<WireRenderMetadata> for RenderMetadata {
1331 fn from(metadata: WireRenderMetadata) -> Self {
1332 Self {
1333 class: metadata.class.into(),
1334 salience: metadata
1335 .salience
1336 .unwrap_or(WireRenderSalience::Normal)
1337 .into(),
1338 }
1339 }
1340}
1341
1342impl From<RenderMetadata> for WireRenderMetadata {
1343 fn from(metadata: RenderMetadata) -> Self {
1344 Self {
1345 class: metadata.class.into(),
1346 salience: Some(metadata.salience.into()),
1347 }
1348 }
1349}
1350
1351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1366#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1367pub struct MobMemberSpecWire {
1368 pub profile: String,
1370 pub agent_identity: String,
1372 #[serde(default, skip_serializing_if = "Option::is_none")]
1373 pub initial_message: Option<WireContentInput>,
1374 #[serde(default, skip_serializing_if = "Option::is_none")]
1375 pub runtime_mode: Option<WireMobRuntimeMode>,
1376 #[serde(default, skip_serializing_if = "Option::is_none")]
1377 pub backend: Option<WireMobBackendKind>,
1378 #[serde(default, skip_serializing_if = "Option::is_none")]
1379 pub binding: Option<WireRuntimeBinding>,
1380 #[serde(default, skip_serializing_if = "Option::is_none")]
1381 pub context: Option<Value>,
1382 #[serde(default, skip_serializing_if = "Option::is_none")]
1383 pub labels: Option<BTreeMap<String, String>>,
1384 #[serde(default, skip_serializing_if = "Option::is_none")]
1385 pub additional_instructions: Option<Vec<String>>,
1386 #[serde(default, skip_serializing_if = "Option::is_none")]
1387 pub auto_wire_parent: Option<bool>,
1388}
1389
1390impl MobMemberSpecWire {
1391 #[must_use]
1394 pub fn surface_metadata(&self) -> SurfaceMetadata {
1395 SurfaceMetadata::from_optional_parts(self.labels.clone(), self.context.clone())
1396 }
1397
1398 pub fn validate_public_surface_metadata(&self) -> Result<(), SurfaceMetadataError> {
1400 self.surface_metadata().validate_public()
1401 }
1402}
1403
1404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1406#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1407#[serde(deny_unknown_fields)]
1408pub struct MobEnsureMemberParams {
1409 pub mob_id: String,
1410 pub spec: MobMemberSpecWire,
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1425#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1426#[serde(transparent)]
1427pub struct WireMemberRef(String);
1428
1429impl WireMemberRef {
1430 #[must_use]
1434 pub fn encode(mob_id: &str, agent_identity: &str) -> Self {
1435 let payload = serde_json::json!({ "m": mob_id, "a": agent_identity });
1439 Self(base64_url_encode(payload.to_string().as_bytes()))
1440 }
1441
1442 #[must_use]
1444 pub fn as_str(&self) -> &str {
1445 &self.0
1446 }
1447
1448 #[must_use]
1451 pub fn from_token(token: impl Into<String>) -> Self {
1452 Self(token.into())
1453 }
1454
1455 pub fn decode(&self) -> Result<(String, String), WireMemberRefError> {
1458 let bytes = base64_url_decode(&self.0).map_err(|_| WireMemberRefError::Malformed)?;
1459 let value: Value =
1460 serde_json::from_slice(&bytes).map_err(|_| WireMemberRefError::Malformed)?;
1461 let mob_id = value
1462 .get("m")
1463 .and_then(Value::as_str)
1464 .ok_or(WireMemberRefError::Malformed)?;
1465 let agent_identity = value
1466 .get("a")
1467 .and_then(Value::as_str)
1468 .ok_or(WireMemberRefError::Malformed)?;
1469 Ok((mob_id.to_string(), agent_identity.to_string()))
1470 }
1471}
1472
1473#[derive(Debug, thiserror::Error)]
1475pub enum WireMemberRefError {
1476 #[error("malformed member ref token")]
1479 Malformed,
1480}
1481
1482fn base64_url_encode(bytes: &[u8]) -> String {
1483 use base64::Engine as _;
1484 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
1485}
1486
1487fn base64_url_decode(input: &str) -> Result<Vec<u8>, base64::DecodeError> {
1488 use base64::Engine as _;
1489 base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)
1490}
1491
1492#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1494#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1495pub struct MobSpawnReceiptWire {
1496 pub agent_identity: String,
1497 pub member_ref: WireMemberRef,
1501}
1502
1503#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1505#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1506#[serde(rename_all = "snake_case")]
1507pub enum WireMobMemberStatus {
1508 Active,
1509 Retiring,
1510 Broken,
1511 Completed,
1512 Unknown,
1513}
1514
1515#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1520#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1521pub struct MobMemberListEntryWire {
1522 pub agent_identity: String,
1523 pub member_ref: WireMemberRef,
1524 pub role: String,
1525 pub runtime_mode: WireMobRuntimeMode,
1526 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1527 pub wired_to: Vec<String>,
1528 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1529 pub labels: BTreeMap<String, String>,
1530 pub status: WireMobMemberStatus,
1531 #[serde(default, skip_serializing_if = "Option::is_none")]
1532 pub error: Option<String>,
1533 pub is_final: bool,
1534}
1535
1536#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1542#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1543pub enum MobEnsureMemberOutcomeWire {
1544 #[serde(rename = "spawned")]
1545 Spawned(MobSpawnReceiptWire),
1546 #[serde(rename = "existed")]
1547 Existed(MobMemberListEntryWire),
1548}
1549
1550#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1552#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1553pub struct MobEnsureMemberResult {
1554 pub outcome: MobEnsureMemberOutcomeWire,
1555}
1556
1557#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
1559#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1560#[serde(deny_unknown_fields)]
1561pub struct MobReconcileOptionsWire {
1562 #[serde(default)]
1565 pub retire_stale: bool,
1566}
1567
1568#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1570#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1571#[serde(rename_all = "snake_case")]
1572pub enum WireMobReconcileStage {
1573 Spawn,
1574 Retire,
1575}
1576
1577#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1579#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1580#[serde(deny_unknown_fields)]
1581pub struct MobReconcileParams {
1582 pub mob_id: String,
1583 #[serde(default)]
1584 pub desired: Vec<MobMemberSpecWire>,
1585 #[serde(default)]
1586 pub options: MobReconcileOptionsWire,
1587}
1588
1589#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1594#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1595pub struct WireMobError {
1596 pub code: MobSpawnManyFailureCause,
1597 pub message: String,
1598}
1599
1600#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1602#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1603pub struct MobReconcileFailureWire {
1604 pub agent_identity: String,
1605 pub stage: WireMobReconcileStage,
1606 pub error: WireMobError,
1608}
1609
1610#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1612#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1613pub struct MobReconcileReportWire {
1614 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1615 pub desired: Vec<String>,
1616 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1617 pub retained: Vec<String>,
1618 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1619 pub spawned: Vec<MobSpawnReceiptWire>,
1620 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1621 pub retired: Vec<String>,
1622 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1623 pub failures: Vec<MobReconcileFailureWire>,
1624}
1625
1626#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1628#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1629pub struct MobReconcileResult {
1630 pub report: MobReconcileReportWire,
1631}
1632
1633#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1638#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1639#[serde(rename_all = "snake_case")]
1640pub enum WireMobLifecycleAction {
1641 Stop,
1642 Resume,
1643 Complete,
1644 Reset,
1645 Destroy,
1646}
1647
1648#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1653#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1654#[serde(rename_all = "snake_case")]
1655pub enum WireMobWireAction {
1656 Wire,
1657 Unwire,
1658}
1659
1660#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1662#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1663#[serde(deny_unknown_fields)]
1664pub struct MobLifecycleParams {
1665 pub mob_id: String,
1666 pub action: WireMobLifecycleAction,
1667}
1668
1669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1671#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1672pub struct MobLifecycleResult {
1673 pub mob_id: String,
1674 pub action: WireMobLifecycleAction,
1675 pub ok: bool,
1676 #[serde(default, skip_serializing_if = "Option::is_none")]
1677 pub destroy_report: Option<Value>,
1678}
1679
1680#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1682#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1683#[serde(deny_unknown_fields)]
1684pub struct MobAppendSystemContextParams {
1685 pub mob_id: String,
1686 pub agent_identity: String,
1687 pub text: String,
1688 #[serde(default, skip_serializing_if = "Option::is_none")]
1689 pub source: Option<String>,
1690 #[serde(default, skip_serializing_if = "Option::is_none")]
1691 pub idempotency_key: Option<String>,
1692}
1693
1694#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1699#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1700#[serde(rename_all = "snake_case")]
1701pub enum WireAppendSystemContextStatus {
1702 Applied,
1703 Staged,
1704 Duplicate,
1705}
1706
1707impl From<meerkat_core::AppendSystemContextStatus> for WireAppendSystemContextStatus {
1708 fn from(status: meerkat_core::AppendSystemContextStatus) -> Self {
1709 match status {
1710 meerkat_core::AppendSystemContextStatus::Applied => Self::Applied,
1711 meerkat_core::AppendSystemContextStatus::Staged => Self::Staged,
1712 meerkat_core::AppendSystemContextStatus::Duplicate => Self::Duplicate,
1713 }
1714 }
1715}
1716
1717#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1719#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1720pub struct MobAppendSystemContextResult {
1721 pub mob_id: String,
1722 pub agent_identity: String,
1723 pub status: WireAppendSystemContextStatus,
1724}
1725
1726#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1728#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1729pub struct MobFlowsResult {
1730 pub mob_id: String,
1731 pub flows: Vec<String>,
1732}
1733
1734#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1736#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1737#[serde(deny_unknown_fields)]
1738pub struct MobFlowRunParams {
1739 pub mob_id: String,
1740 pub flow_id: String,
1741 #[serde(default)]
1742 pub params: Value,
1743}
1744
1745#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1750#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1751#[serde(deny_unknown_fields)]
1752pub struct MobRunParams {
1753 pub mob_id: String,
1754 #[serde(default, skip_serializing_if = "Option::is_none")]
1755 pub flow_id: Option<String>,
1756 #[serde(default, skip_serializing_if = "Option::is_none")]
1757 pub prompt: Option<String>,
1758 #[serde(default)]
1759 pub params: Value,
1760}
1761
1762#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1764#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1765pub struct MobFlowRunResult {
1766 pub run_id: String,
1767}
1768
1769#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1771#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1772#[serde(deny_unknown_fields)]
1773pub struct MobFlowStatusParams {
1774 pub mob_id: String,
1775 pub run_id: String,
1776}
1777
1778#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1782#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1783#[serde(rename_all = "snake_case")]
1784pub enum WireMobRunStatus {
1785 Pending,
1786 Running,
1787 Completed,
1788 Failed,
1789 Canceled,
1790}
1791
1792#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1800#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1801pub struct WireMobRun {
1802 pub run_id: String,
1803 pub mob_id: String,
1804 pub flow_id: String,
1805 pub status: WireMobRunStatus,
1806 #[serde(flatten)]
1809 pub kernel: serde_json::Map<String, Value>,
1810}
1811
1812#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1816#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1817pub struct MobFlowStatusResult {
1818 #[serde(default, skip_serializing_if = "Option::is_none")]
1819 pub run: Option<WireMobRun>,
1820}
1821
1822#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1824#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1825#[serde(deny_unknown_fields)]
1826pub struct MobRunResultParams {
1827 pub mob_id: String,
1828 pub run_id: String,
1829}
1830
1831#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1833#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1834#[serde(deny_unknown_fields)]
1835pub struct WireMobRunResultEnvelope {
1836 pub run_id: String,
1837 pub mob_id: String,
1838 pub flow_id: String,
1839 pub status: WireMobRunStatus,
1840 #[serde(default, skip_serializing_if = "Option::is_none")]
1841 pub result: Option<Value>,
1842 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1843 pub outputs: BTreeMap<String, Value>,
1844}
1845
1846#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1850#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1851pub struct MobRunResult {
1852 #[serde(default, skip_serializing_if = "Option::is_none")]
1853 pub run: Option<WireMobRunResultEnvelope>,
1854}
1855
1856#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1858#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1859#[serde(deny_unknown_fields)]
1860pub struct MobFlowCancelParams {
1861 pub mob_id: String,
1862 pub run_id: String,
1863}
1864
1865#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1867#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1868pub struct MobFlowCancelResult {
1869 pub canceled: bool,
1870}
1871
1872#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1874#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1875#[serde(deny_unknown_fields)]
1876pub struct MobSpawnHelperParams {
1877 pub mob_id: String,
1878 pub prompt: String,
1879 #[serde(default, skip_serializing_if = "Option::is_none")]
1880 pub agent_identity: Option<String>,
1881 #[serde(default, skip_serializing_if = "Option::is_none")]
1882 pub role_name: Option<String>,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub runtime_mode: Option<WireMobRuntimeMode>,
1885 #[serde(default, skip_serializing_if = "Option::is_none")]
1886 pub backend: Option<WireMobBackendKind>,
1887}
1888
1889#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1891#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1892#[serde(deny_unknown_fields)]
1893pub struct MobForkHelperParams {
1894 pub mob_id: String,
1895 pub source_member_id: String,
1896 pub prompt: String,
1897 #[serde(default, skip_serializing_if = "Option::is_none")]
1898 pub agent_identity: Option<String>,
1899 #[serde(default, skip_serializing_if = "Option::is_none")]
1900 pub role_name: Option<String>,
1901 #[serde(default, skip_serializing_if = "Option::is_none")]
1902 pub fork_context: Option<Value>,
1903 #[serde(default, skip_serializing_if = "Option::is_none")]
1904 pub runtime_mode: Option<WireMobRuntimeMode>,
1905 #[serde(default, skip_serializing_if = "Option::is_none")]
1906 pub backend: Option<WireMobBackendKind>,
1907}
1908
1909#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1911#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1912pub struct MobHelperResult {
1913 #[serde(default, skip_serializing_if = "Option::is_none")]
1914 pub output: Option<String>,
1915 pub tokens_used: u64,
1916 pub agent_identity: String,
1917 pub member_ref: WireMemberRef,
1918}
1919
1920#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1922#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1923pub struct MobForceCancelResult {
1924 pub cancelled: bool,
1925}
1926
1927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1935#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1936#[serde(deny_unknown_fields)]
1937pub struct MobTurnStartParams {
1938 pub mob_id: String,
1939 pub agent_identity: String,
1940 pub prompt: WireContentInput,
1941 #[serde(default, skip_serializing_if = "Option::is_none")]
1942 pub skill_refs: Option<Vec<meerkat_core::skills::SkillRef>>,
1943 #[serde(default, skip_serializing_if = "Option::is_none")]
1944 pub flow_tool_overlay: Option<meerkat_core::service::PublicTurnToolOverlay>,
1945 #[serde(default, skip_serializing_if = "Option::is_none")]
1946 pub additional_instructions: Option<Vec<String>>,
1947 #[serde(default, skip_serializing_if = "Option::is_none")]
1948 pub keep_alive: Option<bool>,
1949 #[serde(default, skip_serializing_if = "Option::is_none")]
1950 pub model: Option<String>,
1951 #[serde(default, skip_serializing_if = "Option::is_none")]
1952 pub provider: Option<String>,
1953 #[serde(default, skip_serializing_if = "Option::is_none")]
1954 pub max_tokens: Option<u32>,
1955 #[serde(default, skip_serializing_if = "Option::is_none")]
1956 pub system_prompt: Option<String>,
1957 #[serde(default, skip_serializing_if = "Option::is_none")]
1958 pub output_schema: Option<Value>,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub structured_output_retries: Option<u32>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub provider_params:
1963 Option<WireTurnMetadataOverride<crate::wire::runtime::WireProviderParamsOverride>>,
1964 #[serde(default, skip_serializing_if = "Option::is_none")]
1965 pub auth_binding: Option<WireTurnMetadataOverride<WireAuthBindingRef>>,
1966}
1967
1968#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1970#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1971pub struct WireUnreachablePeer {
1972 pub peer: String,
1973 #[serde(default, skip_serializing_if = "Option::is_none")]
1974 pub reason: Option<String>,
1975}
1976
1977#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1980#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1981pub struct WirePeerConnectivitySnapshot {
1982 pub reachable_peer_count: usize,
1983 pub unknown_peer_count: usize,
1984 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1985 pub unreachable_peers: Vec<WireUnreachablePeer>,
1986}
1987
1988#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1997#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1998#[serde(tag = "status", rename_all = "snake_case")]
1999pub enum WirePeerConnectivity {
2000 NotApplicable,
2003 ProbeTimedOut,
2005 Known {
2007 snapshot: WirePeerConnectivitySnapshot,
2008 },
2009}
2010
2011#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2013#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2014pub struct MobMemberStatusResult {
2015 pub status: WireMobMemberStatus,
2016 pub member_ref: WireMemberRef,
2018 #[serde(default, skip_serializing_if = "Option::is_none")]
2019 pub output_preview: Option<String>,
2020 #[serde(default, skip_serializing_if = "Option::is_none")]
2021 pub error: Option<String>,
2022 pub tokens_used: u64,
2023 pub is_final: bool,
2024 #[serde(default, skip_serializing_if = "Option::is_none")]
2025 pub current_session_id: Option<String>,
2026 #[serde(default, skip_serializing_if = "Option::is_none")]
2027 pub peer_connectivity: Option<WirePeerConnectivity>,
2028 #[serde(default, skip_serializing_if = "Option::is_none")]
2029 pub kickoff: Option<Value>,
2030 #[serde(default, skip_serializing_if = "Option::is_none")]
2031 pub external_member: Option<Value>,
2032 #[serde(default, skip_serializing_if = "Option::is_none")]
2033 pub resolved_capabilities: Option<crate::wire::WireResolvedModelCapabilities>,
2034}
2035
2036#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2038#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2039pub struct MobSnapshotResult {
2040 pub mob_id: String,
2041 pub status: WireMobLifecycleStatus,
2042 pub members: Vec<MobMemberListEntryWire>,
2043}
2044
2045#[cfg(test)]
2046mod member_status_capability_tests {
2047 use super::*;
2048
2049 #[test]
2050 fn member_status_result_round_trips_resolved_capabilities() -> Result<(), serde_json::Error> {
2051 let capabilities = crate::wire::WireResolvedModelCapabilities {
2052 vision: true,
2053 image_input: true,
2054 image_tool_results: false,
2055 inline_video: false,
2056 realtime: true,
2057 web_search: true,
2058 image_generation: true,
2059 };
2060 let result = MobMemberStatusResult {
2061 status: WireMobMemberStatus::Active,
2062 member_ref: WireMemberRef::encode("mob-1", "worker-1"),
2063 output_preview: None,
2064 error: None,
2065 tokens_used: 0,
2066 is_final: false,
2067 current_session_id: Some("session-1".to_string()),
2068 peer_connectivity: Some(WirePeerConnectivity::Known {
2069 snapshot: WirePeerConnectivitySnapshot {
2070 reachable_peer_count: 1,
2071 unknown_peer_count: 0,
2072 unreachable_peers: Vec::new(),
2073 },
2074 }),
2075 kickoff: None,
2076 external_member: None,
2077 resolved_capabilities: Some(capabilities.clone()),
2078 };
2079
2080 let json = serde_json::to_string(&result)?;
2081 assert!(json.contains("\"resolved_capabilities\""));
2082 let parsed: MobMemberStatusResult = serde_json::from_str(&json)?;
2083 assert_eq!(parsed.resolved_capabilities, Some(capabilities));
2084 Ok(())
2085 }
2086}
2087
2088#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2090#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2091pub struct MobDestroyResult {
2092 pub mob_id: String,
2093 pub ok: bool,
2094 pub destroy_report: Value,
2095}
2096
2097#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2099#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2100pub struct MobRotateSupervisorResult {
2101 pub mob_id: String,
2102 pub ok: bool,
2103 pub report: SupervisorRotationReportWire,
2104}
2105
2106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2108#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2109pub struct SupervisorRotationReportWire {
2110 pub previous_epoch: u64,
2111 pub current_epoch: u64,
2112 pub public_peer_id: String,
2113}
2114
2115#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2117#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2118#[serde(rename_all = "snake_case")]
2119pub enum SupervisorRotationIncompleteKind {
2120 SupervisorRotationIncomplete,
2121}
2122
2123#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2125#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2126#[serde(rename_all = "snake_case")]
2127pub enum SupervisorRotationRetryAuthority {
2128 PendingRotation,
2129 PreRotation,
2130}
2131
2132#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2134#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2135#[serde(rename_all = "snake_case")]
2136pub enum SupervisorRotationRetryScope {
2137 Durable,
2138 PreRotation,
2139}
2140
2141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2143#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2144#[serde(rename_all = "snake_case")]
2145pub struct SupervisorRotationIncompleteDetailsWire {
2146 pub kind: SupervisorRotationIncompleteKind,
2147 pub previous_epoch: u64,
2148 pub attempted_epoch: u64,
2149 pub attempted_public_peer_id: String,
2150 pub rotated_peer_count: usize,
2151 pub rollback_succeeded: bool,
2152 pub pending_authority_recorded: bool,
2153 #[serde(default, skip_serializing_if = "Option::is_none")]
2154 pub rollback_error: Option<String>,
2155 pub retry_authority: SupervisorRotationRetryAuthority,
2156 pub retry_scope: SupervisorRotationRetryScope,
2157}
2158
2159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2161#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2162#[serde(rename_all = "snake_case")]
2163pub struct SupervisorRotationIncompleteDataWire {
2164 pub code: String,
2165 pub message: String,
2166 pub details: SupervisorRotationIncompleteDetailsWire,
2167}
2168
2169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2172#[serde(deny_unknown_fields)]
2173pub struct MobWaitParams {
2174 pub mob_id: String,
2175 #[serde(default, skip_serializing_if = "Option::is_none")]
2176 pub member_ids: Option<Vec<String>>,
2177 #[serde(default, skip_serializing_if = "Option::is_none")]
2178 pub timeout_ms: Option<u64>,
2179}
2180
2181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2183#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2184pub struct MobWaitMembersResult {
2185 pub members: Vec<Value>,
2186}
2187
2188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2190#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2191pub struct MobCancelWorkResult {
2192 pub mob_id: String,
2193 pub ok: bool,
2194}
2195
2196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2199pub struct MobCancelAllWorkResult {
2200 pub mob_id: String,
2201 pub ok: bool,
2202}
2203
2204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2206#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2207#[serde(deny_unknown_fields)]
2208pub struct MobProfileCreateParams {
2209 pub name: String,
2210 pub profile: MobProfileInput,
2211}
2212
2213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2216#[serde(deny_unknown_fields)]
2217pub struct MobProfileNameParams {
2218 pub name: String,
2219}
2220
2221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2223#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2224#[serde(deny_unknown_fields)]
2225pub struct MobProfileUpdateParams {
2226 pub name: String,
2227 pub profile: MobProfileInput,
2228 pub expected_revision: u64,
2229}
2230
2231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2233#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2234#[serde(deny_unknown_fields)]
2235pub struct MobProfileDeleteParams {
2236 pub name: String,
2237 pub expected_revision: u64,
2238}
2239
2240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2242#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2243pub struct MobProfileLookupResult {
2244 #[serde(default)]
2245 pub not_found: bool,
2246 pub name: String,
2247 #[serde(default, skip_serializing_if = "Option::is_none")]
2248 pub profile: Option<WireMobProfile>,
2249 #[serde(default, skip_serializing_if = "Option::is_none")]
2250 pub revision: Option<u64>,
2251 #[serde(default, skip_serializing_if = "Option::is_none")]
2252 pub created_at: Option<String>,
2253 #[serde(default, skip_serializing_if = "Option::is_none")]
2254 pub updated_at: Option<String>,
2255}
2256
2257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2259#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2260pub struct MobProfileListResult {
2261 pub profiles: Vec<MobProfileLookupResult>,
2262}
2263
2264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2266#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2267pub struct MobProfileDeleteResult {
2268 pub name: String,
2269 pub deleted_revision: u64,
2270}
2271
2272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2274#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2275#[serde(deny_unknown_fields)]
2276pub struct MobStreamOpenParams {
2277 pub mob_id: String,
2278 #[serde(default, skip_serializing_if = "Option::is_none")]
2279 pub agent_identity: Option<String>,
2280}
2281
2282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2284#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2285pub struct MobStreamOpenResult {
2286 pub stream_id: String,
2287 pub opened: bool,
2288}
2289
2290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2292#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2293#[serde(deny_unknown_fields)]
2294pub struct MobStreamCloseParams {
2295 pub stream_id: String,
2296}
2297
2298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2300#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2301pub struct MobStreamCloseResult {
2302 pub stream_id: String,
2303 pub closed: bool,
2304 pub already_closed: bool,
2305}
2306
2307#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
2310#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2311#[serde(rename_all = "snake_case")]
2312pub enum WireWorkOrigin {
2313 #[default]
2314 External,
2315 Internal,
2316}
2317
2318#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2324#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2325#[serde(deny_unknown_fields)]
2326pub struct MobSubmitWorkParams {
2327 pub member_ref: WireMemberRef,
2328 #[serde(default, skip_serializing_if = "Option::is_none")]
2331 pub work_ref: Option<String>,
2332 pub content: WireContentInput,
2333 #[serde(default)]
2334 pub origin: WireWorkOrigin,
2335}
2336
2337#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2339#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2340pub struct MobSubmitWorkResult {
2341 pub mob_id: String,
2342 pub work_ref: String,
2343 pub member_ref: WireMemberRef,
2344}
2345
2346#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2348#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2349#[serde(deny_unknown_fields)]
2350pub struct MobCancelWorkParams {
2351 pub mob_id: String,
2352 pub work_ref: String,
2353}
2354
2355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2357#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2358#[serde(deny_unknown_fields)]
2359pub struct MobCancelAllWorkParams {
2360 pub member_ref: WireMemberRef,
2361}
2362
2363#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
2366#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2367#[serde(deny_unknown_fields)]
2368pub struct MobMemberFilterWire {
2369 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
2371 pub labels: BTreeMap<String, String>,
2372 #[serde(default, skip_serializing_if = "Option::is_none")]
2374 pub role: Option<String>,
2375 #[serde(default, skip_serializing_if = "Option::is_none")]
2377 pub status: Option<WireMobMemberStatus>,
2378}
2379
2380#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2382#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2383#[serde(deny_unknown_fields)]
2384pub struct MobListMembersMatchingParams {
2385 pub mob_id: String,
2386 #[serde(default)]
2387 pub filter: MobMemberFilterWire,
2388}
2389
2390#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2393#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2394pub struct MobListMembersMatchingResult {
2395 #[serde(default)]
2396 pub members: Vec<Value>,
2397}
2398
2399#[cfg(test)]
2400#[allow(clippy::expect_used, clippy::panic)]
2401mod tests {
2402 use super::*;
2403
2404 #[test]
2405 fn wire_mob_profile_parses_provider_fields_fail_closed() {
2406 let legacy: WireMobProfile =
2408 serde_json::from_str(r#"{"model":"claude-opus-4-8"}"#).expect("legacy profile parses");
2409 assert_eq!(legacy.provider, None);
2410 assert!(legacy.resume_overrides.is_empty());
2411
2412 let full: WireMobProfile = serde_json::from_str(
2414 r#"{
2415 "model": "claude-internal-preview",
2416 "provider": "anthropic",
2417 "image_generation_provider": "gemini",
2418 "auto_compact_threshold": 60000,
2419 "resume_overrides": ["model", "provider"]
2420 }"#,
2421 )
2422 .expect("typed profile parses");
2423 assert_eq!(full.provider, Some(meerkat_core::Provider::Anthropic));
2424 assert_eq!(
2425 full.image_generation_provider,
2426 Some(meerkat_core::Provider::Gemini)
2427 );
2428 assert_eq!(
2429 full.resume_overrides,
2430 vec![
2431 WireMobResumeOverrideField::Model,
2432 WireMobResumeOverrideField::Provider
2433 ]
2434 );
2435
2436 assert!(
2438 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","provider":"not-a-provider"}"#)
2439 .is_err(),
2440 "unknown provider names must fail closed at the wire boundary"
2441 );
2442 assert!(
2443 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","auto_compact_threshold":0}"#)
2444 .is_err(),
2445 "zero auto_compact_threshold must fail closed at the wire boundary"
2446 );
2447 assert!(
2448 serde_json::from_str::<WireMobProfile>(
2449 r#"{"model":"m","resume_overrides":["everything"]}"#
2450 )
2451 .is_err(),
2452 "resume_overrides vocabulary is closed"
2453 );
2454 }
2455
2456 #[test]
2457 fn mob_definition_input_parses_custom_models() {
2458 let input: MobDefinitionInput = serde_json::from_str(
2459 r#"{
2460 "id": "m",
2461 "profiles": {"worker": {"model": "claude-internal-preview"}},
2462 "models": {
2463 "claude-internal-preview": {
2464 "provider": "anthropic",
2465 "context_window": 500000,
2466 "vision": true
2467 }
2468 },
2469 "image_generation_provider": "openai"
2470 }"#,
2471 )
2472 .expect("definition with custom models parses");
2473 let model = input
2474 .models
2475 .get("claude-internal-preview")
2476 .expect("custom model present");
2477 assert_eq!(model.provider, meerkat_core::Provider::Anthropic);
2478 assert_eq!(model.context_window, Some(500_000));
2479 assert_eq!(model.vision, Some(true));
2480 assert_eq!(
2481 input.image_generation_provider,
2482 Some(meerkat_core::Provider::OpenAI)
2483 );
2484 }
2485
2486 #[test]
2487 fn wire_member_ref_round_trips_through_encode_decode() {
2488 let token = WireMemberRef::encode("mob-42", "worker-1");
2489 let (mob_id, agent_identity) = token.decode().expect("decode round-trips");
2490 assert_eq!(mob_id, "mob-42");
2491 assert_eq!(agent_identity, "worker-1");
2492 }
2493
2494 #[test]
2495 fn wire_member_ref_rejects_malformed_token() {
2496 let err = WireMemberRef::from_token("not-a-token-payload")
2497 .decode()
2498 .expect_err("malformed tokens must fail to decode");
2499 assert!(matches!(err, WireMemberRefError::Malformed));
2500 }
2501
2502 #[test]
2503 fn mob_member_spec_exposes_shared_surface_metadata() {
2504 let spec = MobMemberSpecWire {
2505 profile: "worker".into(),
2506 agent_identity: "w1".into(),
2507 initial_message: None,
2508 runtime_mode: None,
2509 backend: None,
2510 binding: None,
2511 context: Some(serde_json::json!({"client_ref": "member-card"})),
2512 labels: Some(BTreeMap::from([("client.member_id".into(), "w1".into())])),
2513 additional_instructions: None,
2514 auto_wire_parent: None,
2515 };
2516
2517 let metadata = spec.surface_metadata();
2518 assert_eq!(
2519 metadata.labels.get("client.member_id").map(String::as_str),
2520 Some("w1")
2521 );
2522 assert_eq!(
2523 metadata.app_context,
2524 Some(serde_json::json!({"client_ref": "member-card"}))
2525 );
2526 }
2527
2528 #[test]
2529 fn mob_member_spec_surface_metadata_rejects_reserved_keys() {
2530 let spec = MobMemberSpecWire {
2531 profile: "worker".into(),
2532 agent_identity: "w1".into(),
2533 initial_message: None,
2534 runtime_mode: None,
2535 backend: None,
2536 binding: None,
2537 context: None,
2538 labels: Some(BTreeMap::from([("mob_id".into(), "spoof".into())])),
2539 additional_instructions: None,
2540 auto_wire_parent: None,
2541 };
2542
2543 assert!(spec.validate_public_surface_metadata().is_err());
2544 }
2545
2546 #[test]
2547 fn mob_reconcile_failure_stage_is_typed_wire_enum() {
2548 let failure = MobReconcileFailureWire {
2549 agent_identity: "worker-1".into(),
2550 stage: WireMobReconcileStage::Spawn,
2551 error: WireMobError {
2552 code: MobSpawnManyFailureCause::ProfileNotFound,
2553 message: "spawn failed".into(),
2554 },
2555 };
2556
2557 let json = serde_json::to_value(&failure).expect("serialize failure");
2558 assert_eq!(json["stage"], "spawn");
2559 assert_eq!(json["error"]["code"], "profile_not_found");
2560 assert_eq!(json["error"]["message"], "spawn failed");
2561
2562 let round_trip: MobReconcileFailureWire =
2563 serde_json::from_value(json).expect("deserialize failure");
2564 assert_eq!(round_trip.stage, WireMobReconcileStage::Spawn);
2565 assert_eq!(
2566 round_trip.error.code,
2567 MobSpawnManyFailureCause::ProfileNotFound
2568 );
2569
2570 let err = serde_json::from_value::<MobReconcileFailureWire>(serde_json::json!({
2571 "agent_identity": "worker-1",
2572 "stage": "restart",
2573 "error": { "code": "profile_not_found", "message": "bad stage" }
2574 }))
2575 .expect_err("unknown reconcile stage must be rejected");
2576 assert!(err.to_string().contains("unknown variant"));
2577 }
2578
2579 #[test]
2580 fn mob_lifecycle_params_reject_unknown_action_string() {
2581 let err = serde_json::from_value::<MobLifecycleParams>(serde_json::json!({
2582 "mob_id": "mob-1",
2583 "action": "explode"
2584 }))
2585 .expect_err("unknown lifecycle actions must fail at the typed wire boundary");
2586
2587 assert!(
2588 err.to_string().contains("unknown variant"),
2589 "unexpected error: {err}"
2590 );
2591 }
2592
2593 #[test]
2594 fn mob_lifecycle_result_round_trips_typed_action() {
2595 let result = MobLifecycleResult {
2596 mob_id: "mob-1".into(),
2597 action: WireMobLifecycleAction::Complete,
2598 ok: true,
2599 destroy_report: None,
2600 };
2601
2602 let json = serde_json::to_value(&result).expect("serialize lifecycle result");
2603 assert_eq!(json["action"], "complete");
2604
2605 let round_trip: MobLifecycleResult =
2606 serde_json::from_value(json).expect("deserialize lifecycle result");
2607 assert_eq!(round_trip.action, WireMobLifecycleAction::Complete);
2608 }
2609
2610 #[test]
2611 fn mob_wire_members_batch_contract_is_local_edge_native() {
2612 let params: MobWireMembersBatchParams = serde_json::from_value(serde_json::json!({
2613 "mob_id": "mob-1",
2614 "edges": [
2615 { "a": "lead", "b": "worker-b" },
2616 { "a": "worker-a", "b": "lead" }
2617 ]
2618 }))
2619 .expect("batch wire params deserialize");
2620
2621 assert_eq!(params.mob_id, "mob-1");
2622 assert_eq!(params.edges.len(), 2);
2623 assert_eq!(params.edges[0].a, "lead");
2624 assert_eq!(params.edges[0].b, "worker-b");
2625
2626 let result = MobWireMembersBatchResult {
2627 requested: 2,
2628 wired: vec![MobWireMembersBatchEdge {
2629 a: "lead".into(),
2630 b: "worker-a".into(),
2631 }],
2632 already_wired: vec![MobWireMembersBatchEdge {
2633 a: "lead".into(),
2634 b: "worker-b".into(),
2635 }],
2636 };
2637 let json = serde_json::to_value(&result).expect("serialize batch wire result");
2638 assert_eq!(json["requested"], 2);
2639 assert_eq!(json["wired"][0]["a"], "lead");
2640 assert_eq!(json["already_wired"][0]["b"], "worker-b");
2641
2642 let err = serde_json::from_value::<MobWireMembersBatchParams>(serde_json::json!({
2643 "mob_id": "mob-1",
2644 "edges": [{ "member": "lead", "peer": "worker-a" }]
2645 }))
2646 .expect_err("mixed local/external mob/wire shape must not deserialize");
2647 let message = err.to_string();
2648 assert!(
2649 message.contains("unknown field `member`") || message.contains("missing field `a`"),
2650 "unexpected error: {message}"
2651 );
2652 }
2653
2654 #[test]
2655 fn mob_spawn_many_result_entry_uses_typed_status_result_envelope() {
2656 let member_ref = WireMemberRef::encode("mob-1", "worker-1");
2657 let entry = MobSpawnManyResultEntry::spawned("worker-1", member_ref.clone());
2658
2659 let json = serde_json::to_value(&entry).expect("serialize typed spawn_many row");
2660 assert_eq!(json["status"], "spawned");
2661 assert_eq!(json["result"]["agent_identity"], "worker-1");
2662 assert_eq!(json["result"]["member_ref"], member_ref.as_str());
2663 assert!(json.get("ok").is_none());
2664 assert!(json.get("error").is_none());
2665
2666 let round_trip: MobSpawnManyResultEntry =
2667 serde_json::from_value(json).expect("deserialize typed spawn_many row");
2668 assert_eq!(round_trip, entry);
2669
2670 let failed = MobSpawnManyResultEntry::failed(
2671 MobSpawnManyFailureCause::ProfileNotFound,
2672 "profile missing",
2673 );
2674 let json = serde_json::to_value(&failed).expect("serialize typed failed spawn_many row");
2675 assert_eq!(json["status"], "failed");
2676 assert_eq!(json["result"]["cause"], "profile_not_found");
2677 assert_eq!(json["result"]["message"], "profile missing");
2678 assert!(json.get("ok").is_none());
2679 assert!(json.get("error").is_none());
2680
2681 let round_trip: MobSpawnManyResultEntry =
2682 serde_json::from_value(json).expect("deserialize typed failed spawn_many row");
2683 assert_eq!(round_trip, failed);
2684 }
2685
2686 #[test]
2687 fn mob_spawn_many_result_entry_rejects_legacy_or_malformed_envelopes() {
2688 let legacy = serde_json::json!({
2689 "ok": true,
2690 "agent_identity": "worker-1",
2691 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2692 });
2693 let err = serde_json::from_value::<MobSpawnManyResultEntry>(legacy)
2694 .expect_err("legacy ok carrier must not deserialize");
2695 assert!(
2696 err.to_string().contains("missing field `status`")
2697 || err.to_string().contains("unknown field"),
2698 "unexpected error: {err}"
2699 );
2700
2701 let missing_result = serde_json::json!({
2702 "status": "spawned"
2703 });
2704 let err = serde_json::from_value::<MobSpawnManyResultEntry>(missing_result)
2705 .expect_err("missing typed result must fail closed");
2706 assert!(
2707 err.to_string().contains("missing field `result`"),
2708 "unexpected error: {err}"
2709 );
2710
2711 let unknown_status = serde_json::json!({
2712 "status": "ok",
2713 "result": {
2714 "agent_identity": "worker-1",
2715 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2716 }
2717 });
2718 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_status)
2719 .expect_err("unknown typed status must fail closed");
2720 assert!(
2721 err.to_string().contains("unknown variant"),
2722 "unexpected error: {err}"
2723 );
2724
2725 let mismatched = serde_json::json!({
2726 "status": "spawned",
2727 "result": {
2728 "cause": "profile_not_found",
2729 "message": "profile missing"
2730 }
2731 });
2732 let err = serde_json::from_value::<MobSpawnManyResultEntry>(mismatched)
2733 .expect_err("status/result mismatch must fail closed");
2734 assert!(
2735 err.to_string()
2736 .contains("status spawned requires spawned result"),
2737 "unexpected error: {err}"
2738 );
2739
2740 let message_only_failure = serde_json::json!({
2741 "status": "failed",
2742 "result": {
2743 "message": "profile missing"
2744 }
2745 });
2746 let err = serde_json::from_value::<MobSpawnManyResultEntry>(message_only_failure)
2747 .expect_err("string-only failure result must fail closed");
2748 assert!(
2749 err.to_string().contains("data did not match any variant")
2750 || err.to_string().contains("missing field `cause`"),
2751 "unexpected error: {err}"
2752 );
2753
2754 let unknown_failure_cause = serde_json::json!({
2755 "status": "failed",
2756 "result": {
2757 "cause": "future_failure",
2758 "message": "future failure"
2759 }
2760 });
2761 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_failure_cause)
2762 .expect_err("unknown failure cause must fail closed");
2763 assert!(
2764 err.to_string().contains("data did not match any variant")
2765 || err.to_string().contains("unknown variant"),
2766 "unexpected error: {err}"
2767 );
2768 }
2769
2770 #[test]
2771 fn mob_wire_params_reject_legacy_local_target_shape() {
2772 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2773 "mob_id": "mob-1",
2774 "local": "member-a",
2775 "target": { "local": "member-b" }
2776 }))
2777 .expect_err("legacy local/target shape must be rejected");
2778
2779 let msg = err.to_string();
2780 assert!(
2781 msg.contains("unknown field `local`") || msg.contains("missing field `member`"),
2782 "unexpected error: {msg}"
2783 );
2784 }
2785
2786 #[test]
2787 fn mob_wire_params_accept_canonical_external_peer_identity() {
2788 let params = serde_json::from_value::<MobWireParams>(serde_json::json!({
2789 "mob_id": "mob-1",
2790 "member": "member-a",
2791 "peer": {
2792 "external": {
2793 "name": "external-worker",
2794 "address": "inproc://external-worker",
2795 "identity": {
2796 "kind": "ed25519_public_key",
2797 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2798 }
2799 }
2800 }
2801 }))
2802 .expect("canonical external peer identity should deserialize");
2803
2804 let MobPeerTarget::External(spec) = params.peer else {
2805 panic!("expected external peer target");
2806 };
2807 assert_eq!(spec.name, "external-worker");
2808 }
2809
2810 #[test]
2811 fn mob_wire_params_reject_raw_external_peer_id_shape() {
2812 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2813 "mob_id": "mob-1",
2814 "member": "member-a",
2815 "peer": {
2816 "external": {
2817 "name": "external-worker",
2818 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2819 "address": "inproc://external-worker",
2820 "pubkey": vec![7u8; 32]
2821 }
2822 }
2823 }))
2824 .expect_err("raw peer_id/pubkey external peer shape must be rejected");
2825
2826 let msg = err.to_string();
2827 assert!(
2828 msg.contains("peer_id") || msg.contains("identity"),
2829 "unexpected error: {msg}"
2830 );
2831 }
2832
2833 #[test]
2834 fn mob_wire_params_reject_missing_external_peer_pubkey_material() {
2835 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2836 "mob_id": "mob-1",
2837 "member": "member-a",
2838 "peer": {
2839 "external": {
2840 "name": "external-worker",
2841 "address": "inproc://external-worker",
2842 "identity": {
2843 "kind": "ed25519_public_key"
2844 }
2845 }
2846 }
2847 }))
2848 .expect_err("missing external peer pubkey material must fail closed");
2849
2850 let msg = err.to_string();
2851 assert!(
2852 msg.contains("public_key") || msg.contains("identity"),
2853 "unexpected error: {msg}"
2854 );
2855 }
2856
2857 #[test]
2858 fn runtime_binding_accepts_canonical_external_peer_identity() {
2859 let binding = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2860 "kind": "external",
2861 "address": "inproc://external-worker",
2862 "identity": {
2863 "kind": "ed25519_public_key",
2864 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2865 }
2866 }))
2867 .expect("canonical external runtime binding identity should deserialize");
2868
2869 let WireRuntimeBinding::External {
2870 identity, address, ..
2871 } = binding
2872 else {
2873 panic!("expected external runtime binding");
2874 };
2875 assert_eq!(address, "inproc://external-worker");
2876 assert_eq!(
2877 identity.resolve().expect("identity resolves").pubkey,
2878 [7u8; 32]
2879 );
2880 }
2881
2882 #[test]
2883 fn runtime_binding_rejects_raw_external_peer_id_shape() {
2884 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2885 "kind": "external",
2886 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2887 "address": "inproc://external-worker",
2888 "pubkey": vec![7u8; 32]
2889 }))
2890 .expect_err("raw peer_id/pubkey external runtime binding shape must be rejected");
2891
2892 let msg = err.to_string();
2893 assert!(
2894 msg.contains("peer_id") || msg.contains("identity"),
2895 "unexpected error: {msg}"
2896 );
2897 }
2898
2899 #[test]
2900 fn runtime_binding_rejects_missing_external_peer_pubkey_material() {
2901 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2902 "kind": "external",
2903 "address": "inproc://external-worker",
2904 "identity": {
2905 "kind": "ed25519_public_key"
2906 }
2907 }))
2908 .expect_err("missing external runtime binding pubkey material must fail closed");
2909
2910 let msg = err.to_string();
2911 assert!(
2912 msg.contains("public_key") || msg.contains("identity"),
2913 "unexpected error: {msg}"
2914 );
2915 }
2916
2917 #[test]
2918 fn mob_turn_start_params_capture_turn_override_fields() {
2919 let params = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2920 "mob_id": "mob-1",
2921 "agent_identity": "worker",
2922 "prompt": "continue",
2923 "output_schema": { "type": "object" },
2924 "structured_output_retries": 2
2925 }))
2926 .expect("turn_start should accept explicit turn override fields");
2927
2928 assert_eq!(params.mob_id, "mob-1");
2929 assert_eq!(params.agent_identity, "worker");
2930 assert_eq!(params.prompt, WireContentInput::Text("continue".into()));
2931 assert_eq!(
2932 params.output_schema,
2933 Some(serde_json::json!({ "type": "object" }))
2934 );
2935 assert_eq!(params.structured_output_retries, Some(2));
2936
2937 let err = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2938 "mob_id": "mob-1",
2939 "agent_identity": "worker",
2940 "prompt": "continue",
2941 "unknown_override": true
2942 }))
2943 .expect_err("turn_start must reject unknown override fields");
2944 assert!(
2945 err.to_string().contains("unknown field"),
2946 "unexpected error: {err}"
2947 );
2948 }
2949
2950 #[test]
2951 fn mob_create_params_reject_reserved_runtime_lifecycle_fields() {
2952 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2953 "definition": {
2954 "id": "mob-1",
2955 "owner_runtime_binding": "runtime:worker:0",
2956 "profiles": {
2957 "worker": { "model": "claude-sonnet-4-6" }
2958 }
2959 }
2960 }))
2961 .expect_err("reserved runtime lifecycle fields must be rejected");
2962
2963 assert!(
2964 err.to_string()
2965 .contains("unknown field `owner_runtime_binding`"),
2966 "unexpected error: {err}"
2967 );
2968 }
2969
2970 #[test]
2971 fn mob_create_params_reject_reserved_runtime_bridge_owner_field() {
2972 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2973 "definition": {
2974 "id": "mob-1",
2975 "owner_transport_binding": "transport:worker:0",
2976 "profiles": {
2977 "worker": { "model": "claude-sonnet-4-6" }
2978 }
2979 }
2980 }))
2981 .expect_err("reserved runtime bridge owner field must be rejected");
2982
2983 assert!(
2984 err.to_string()
2985 .contains("unknown field `owner_transport_binding`"),
2986 "unexpected error: {err}"
2987 );
2988 }
2989
2990 #[test]
2991 fn mob_create_params_reject_internal_profile_tool_bundles() {
2992 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2993 "definition": {
2994 "id": "mob-1",
2995 "profiles": {
2996 "worker": {
2997 "model": "claude-sonnet-4-6",
2998 "tools": {
2999 "rust_bundles": ["internal-only"]
3000 }
3001 }
3002 }
3003 }
3004 }))
3005 .expect_err("internal rust tool bundles must be rejected");
3006
3007 assert!(
3010 err.to_string().contains("did not match any variant")
3011 || err.to_string().contains("unknown field `rust_bundles`"),
3012 "unexpected error: {err}"
3013 );
3014 }
3015
3016 #[test]
3017 fn mob_create_params_accept_typed_nested_flow_definition() {
3018 let params = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3019 "definition": {
3020 "id": "mob-1",
3021 "profiles": {
3022 "worker": { "model": "claude-sonnet-4-6" }
3023 },
3024 "flows": {
3025 "review": {
3026 "description": "review flow",
3027 "steps": {
3028 "draft": {
3029 "role": "worker",
3030 "message": "draft it"
3031 }
3032 }
3033 }
3034 }
3035 }
3036 }))
3037 .expect("typed nested flow definition should parse");
3038
3039 assert_eq!(
3040 params.definition.flows["review"].steps["draft"].role,
3041 "worker"
3042 );
3043 }
3044}