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 auth_binding: Option<WireAuthBindingRef>,
1885 #[serde(default, skip_serializing_if = "Option::is_none")]
1886 pub runtime_mode: Option<WireMobRuntimeMode>,
1887 #[serde(default, skip_serializing_if = "Option::is_none")]
1888 pub backend: Option<WireMobBackendKind>,
1889}
1890
1891#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1893#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1894#[serde(deny_unknown_fields)]
1895pub struct MobForkHelperParams {
1896 pub mob_id: String,
1897 pub source_member_id: String,
1898 pub prompt: String,
1899 #[serde(default, skip_serializing_if = "Option::is_none")]
1900 pub agent_identity: Option<String>,
1901 #[serde(default, skip_serializing_if = "Option::is_none")]
1902 pub role_name: Option<String>,
1903 #[serde(default, skip_serializing_if = "Option::is_none")]
1904 pub auth_binding: Option<WireAuthBindingRef>,
1905 #[serde(default, skip_serializing_if = "Option::is_none")]
1906 pub fork_context: Option<Value>,
1907 #[serde(default, skip_serializing_if = "Option::is_none")]
1908 pub runtime_mode: Option<WireMobRuntimeMode>,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub backend: Option<WireMobBackendKind>,
1911}
1912
1913#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1915#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1916pub struct MobHelperResult {
1917 #[serde(default, skip_serializing_if = "Option::is_none")]
1918 pub output: Option<String>,
1919 pub tokens_used: u64,
1920 pub agent_identity: String,
1921 pub member_ref: WireMemberRef,
1922}
1923
1924#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1926#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1927pub struct MobForceCancelResult {
1928 pub cancelled: bool,
1929}
1930
1931#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1939#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1940#[serde(deny_unknown_fields)]
1941pub struct MobTurnStartParams {
1942 pub mob_id: String,
1943 pub agent_identity: String,
1944 pub prompt: WireContentInput,
1945 #[serde(default, skip_serializing_if = "Option::is_none")]
1946 pub skill_refs: Option<Vec<meerkat_core::skills::SkillRef>>,
1947 #[serde(default, skip_serializing_if = "Option::is_none")]
1948 pub turn_tool_overlay: Option<meerkat_core::service::PublicTurnToolOverlay>,
1949 #[serde(default, skip_serializing_if = "Option::is_none")]
1950 pub additional_instructions: Option<Vec<String>>,
1951 #[serde(default, skip_serializing_if = "Option::is_none")]
1952 pub keep_alive: Option<bool>,
1953 #[serde(default, skip_serializing_if = "Option::is_none")]
1954 pub model: Option<String>,
1955 #[serde(default, skip_serializing_if = "Option::is_none")]
1956 pub provider: Option<String>,
1957 #[serde(default, skip_serializing_if = "Option::is_none")]
1958 pub max_tokens: Option<u32>,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub system_prompt: Option<String>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub output_schema: Option<Value>,
1963 #[serde(default, skip_serializing_if = "Option::is_none")]
1964 pub structured_output_retries: Option<u32>,
1965 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub provider_params:
1967 Option<WireTurnMetadataOverride<crate::wire::runtime::WireProviderParamsOverride>>,
1968 #[serde(default, skip_serializing_if = "Option::is_none")]
1969 pub auth_binding: Option<WireTurnMetadataOverride<WireAuthBindingRef>>,
1970 #[serde(default, skip_serializing_if = "Option::is_none")]
1975 pub injected_context: Option<Vec<WireContentInput>>,
1976}
1977
1978#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1980#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1981pub struct WireUnreachablePeer {
1982 pub peer: String,
1983 #[serde(default, skip_serializing_if = "Option::is_none")]
1984 pub reason: Option<String>,
1985}
1986
1987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1990#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1991pub struct WirePeerConnectivitySnapshot {
1992 pub reachable_peer_count: usize,
1993 pub unknown_peer_count: usize,
1994 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1995 pub unreachable_peers: Vec<WireUnreachablePeer>,
1996}
1997
1998#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2007#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2008#[serde(tag = "status", rename_all = "snake_case")]
2009pub enum WirePeerConnectivity {
2010 NotApplicable,
2013 ProbeTimedOut,
2015 Known {
2017 snapshot: WirePeerConnectivitySnapshot,
2018 },
2019}
2020
2021#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2023#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2024pub struct MobMemberStatusResult {
2025 pub status: WireMobMemberStatus,
2026 pub member_ref: WireMemberRef,
2028 #[serde(default, skip_serializing_if = "Option::is_none")]
2029 pub output_preview: Option<String>,
2030 #[serde(default, skip_serializing_if = "Option::is_none")]
2031 pub error: Option<String>,
2032 pub tokens_used: u64,
2033 pub is_final: bool,
2034 #[serde(default, skip_serializing_if = "Option::is_none")]
2035 pub current_session_id: Option<String>,
2036 #[serde(default, skip_serializing_if = "Option::is_none")]
2037 pub peer_connectivity: Option<WirePeerConnectivity>,
2038 #[serde(default, skip_serializing_if = "Option::is_none")]
2039 pub kickoff: Option<Value>,
2040 #[serde(default, skip_serializing_if = "Option::is_none")]
2041 pub external_member: Option<Value>,
2042 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 pub resolved_capabilities: Option<crate::wire::WireResolvedModelCapabilities>,
2044}
2045
2046#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2048#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2049pub struct MobSnapshotResult {
2050 pub mob_id: String,
2051 pub status: WireMobLifecycleStatus,
2052 pub members: Vec<MobMemberListEntryWire>,
2053}
2054
2055#[cfg(test)]
2056mod member_status_capability_tests {
2057 use super::*;
2058
2059 #[test]
2060 fn member_status_result_round_trips_resolved_capabilities() -> Result<(), serde_json::Error> {
2061 let capabilities = crate::wire::WireResolvedModelCapabilities {
2062 vision: true,
2063 image_input: true,
2064 image_tool_results: false,
2065 inline_video: false,
2066 realtime: true,
2067 web_search: true,
2068 image_generation: true,
2069 };
2070 let result = MobMemberStatusResult {
2071 status: WireMobMemberStatus::Active,
2072 member_ref: WireMemberRef::encode("mob-1", "worker-1"),
2073 output_preview: None,
2074 error: None,
2075 tokens_used: 0,
2076 is_final: false,
2077 current_session_id: Some("session-1".to_string()),
2078 peer_connectivity: Some(WirePeerConnectivity::Known {
2079 snapshot: WirePeerConnectivitySnapshot {
2080 reachable_peer_count: 1,
2081 unknown_peer_count: 0,
2082 unreachable_peers: Vec::new(),
2083 },
2084 }),
2085 kickoff: None,
2086 external_member: None,
2087 resolved_capabilities: Some(capabilities.clone()),
2088 };
2089
2090 let json = serde_json::to_string(&result)?;
2091 assert!(json.contains("\"resolved_capabilities\""));
2092 let parsed: MobMemberStatusResult = serde_json::from_str(&json)?;
2093 assert_eq!(parsed.resolved_capabilities, Some(capabilities));
2094 Ok(())
2095 }
2096}
2097
2098#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2100#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2101pub struct MobDestroyResult {
2102 pub mob_id: String,
2103 pub ok: bool,
2104 pub destroy_report: Value,
2105}
2106
2107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2110pub struct MobRotateSupervisorResult {
2111 pub mob_id: String,
2112 pub ok: bool,
2113 pub report: SupervisorRotationReportWire,
2114}
2115
2116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2118#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2119pub struct SupervisorRotationReportWire {
2120 pub previous_epoch: u64,
2121 pub current_epoch: u64,
2122 pub public_peer_id: String,
2123}
2124
2125#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2127#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2128#[serde(rename_all = "snake_case")]
2129pub enum SupervisorRotationIncompleteKind {
2130 SupervisorRotationIncomplete,
2131}
2132
2133#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2135#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2136#[serde(rename_all = "snake_case")]
2137pub enum SupervisorRotationRetryAuthority {
2138 PendingRotation,
2139 PreRotation,
2140}
2141
2142#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2144#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2145#[serde(rename_all = "snake_case")]
2146pub enum SupervisorRotationRetryScope {
2147 Durable,
2148 PreRotation,
2149}
2150
2151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2153#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2154#[serde(rename_all = "snake_case")]
2155pub struct SupervisorRotationIncompleteDetailsWire {
2156 pub kind: SupervisorRotationIncompleteKind,
2157 pub previous_epoch: u64,
2158 pub attempted_epoch: u64,
2159 pub attempted_public_peer_id: String,
2160 pub rotated_peer_count: usize,
2161 pub rollback_succeeded: bool,
2162 pub pending_authority_recorded: bool,
2163 #[serde(default, skip_serializing_if = "Option::is_none")]
2164 pub rollback_error: Option<String>,
2165 pub retry_authority: SupervisorRotationRetryAuthority,
2166 pub retry_scope: SupervisorRotationRetryScope,
2167}
2168
2169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2172#[serde(rename_all = "snake_case")]
2173pub struct SupervisorRotationIncompleteDataWire {
2174 pub code: String,
2175 pub message: String,
2176 pub details: SupervisorRotationIncompleteDetailsWire,
2177}
2178
2179#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2181#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2182#[serde(deny_unknown_fields)]
2183pub struct MobWaitParams {
2184 pub mob_id: String,
2185 #[serde(default, skip_serializing_if = "Option::is_none")]
2186 pub member_ids: Option<Vec<String>>,
2187 #[serde(default, skip_serializing_if = "Option::is_none")]
2188 pub timeout_ms: Option<u64>,
2189}
2190
2191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2193#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2194pub struct MobWaitMembersResult {
2195 pub members: Vec<Value>,
2196}
2197
2198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2200#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2201pub struct MobCancelWorkResult {
2202 pub mob_id: String,
2203 pub ok: bool,
2204}
2205
2206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2208#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2209pub struct MobCancelAllWorkResult {
2210 pub mob_id: String,
2211 pub ok: bool,
2212}
2213
2214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2216#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2217#[serde(deny_unknown_fields)]
2218pub struct MobProfileCreateParams {
2219 pub name: String,
2220 pub profile: MobProfileInput,
2221}
2222
2223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2225#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2226#[serde(deny_unknown_fields)]
2227pub struct MobProfileNameParams {
2228 pub name: String,
2229}
2230
2231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2233#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2234#[serde(deny_unknown_fields)]
2235pub struct MobProfileUpdateParams {
2236 pub name: String,
2237 pub profile: MobProfileInput,
2238 pub expected_revision: u64,
2239}
2240
2241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2244#[serde(deny_unknown_fields)]
2245pub struct MobProfileDeleteParams {
2246 pub name: String,
2247 pub expected_revision: u64,
2248}
2249
2250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2253pub struct MobProfileLookupResult {
2254 #[serde(default)]
2255 pub not_found: bool,
2256 pub name: String,
2257 #[serde(default, skip_serializing_if = "Option::is_none")]
2258 pub profile: Option<WireMobProfile>,
2259 #[serde(default, skip_serializing_if = "Option::is_none")]
2260 pub revision: Option<u64>,
2261 #[serde(default, skip_serializing_if = "Option::is_none")]
2262 pub created_at: Option<String>,
2263 #[serde(default, skip_serializing_if = "Option::is_none")]
2264 pub updated_at: Option<String>,
2265}
2266
2267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2269#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2270pub struct MobProfileListResult {
2271 pub profiles: Vec<MobProfileLookupResult>,
2272}
2273
2274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2276#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2277pub struct MobProfileDeleteResult {
2278 pub name: String,
2279 pub deleted_revision: u64,
2280}
2281
2282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2284#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2285#[serde(deny_unknown_fields)]
2286pub struct MobStreamOpenParams {
2287 pub mob_id: String,
2288 #[serde(default, skip_serializing_if = "Option::is_none")]
2289 pub agent_identity: Option<String>,
2290}
2291
2292#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2294#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2295pub struct MobStreamOpenResult {
2296 pub stream_id: String,
2297 pub opened: bool,
2298}
2299
2300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2302#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2303#[serde(deny_unknown_fields)]
2304pub struct MobStreamCloseParams {
2305 pub stream_id: String,
2306}
2307
2308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2310#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2311pub struct MobStreamCloseResult {
2312 pub stream_id: String,
2313 pub closed: bool,
2314 pub already_closed: bool,
2315}
2316
2317#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
2320#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2321#[serde(rename_all = "snake_case")]
2322pub enum WireWorkOrigin {
2323 #[default]
2324 External,
2325 Internal,
2326}
2327
2328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2334#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2335#[serde(deny_unknown_fields)]
2336pub struct MobSubmitWorkParams {
2337 pub member_ref: WireMemberRef,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2341 pub work_ref: Option<String>,
2342 pub content: WireContentInput,
2343 #[serde(default)]
2344 pub origin: WireWorkOrigin,
2345 #[serde(default, skip_serializing_if = "Option::is_none")]
2351 pub injected_context: Option<Vec<WireContentInput>>,
2352}
2353
2354#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2356#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2357pub struct MobSubmitWorkResult {
2358 pub mob_id: String,
2359 pub work_ref: String,
2360 pub member_ref: WireMemberRef,
2361}
2362
2363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2365#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2366#[serde(deny_unknown_fields)]
2367pub struct MobCancelWorkParams {
2368 pub mob_id: String,
2369 pub work_ref: String,
2370}
2371
2372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2374#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2375#[serde(deny_unknown_fields)]
2376pub struct MobCancelAllWorkParams {
2377 pub member_ref: WireMemberRef,
2378}
2379
2380#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
2383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2384#[serde(deny_unknown_fields)]
2385pub struct MobMemberFilterWire {
2386 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
2388 pub labels: BTreeMap<String, String>,
2389 #[serde(default, skip_serializing_if = "Option::is_none")]
2391 pub role: Option<String>,
2392 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub status: Option<WireMobMemberStatus>,
2395}
2396
2397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2399#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2400#[serde(deny_unknown_fields)]
2401pub struct MobListMembersMatchingParams {
2402 pub mob_id: String,
2403 #[serde(default)]
2404 pub filter: MobMemberFilterWire,
2405}
2406
2407#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2410#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2411pub struct MobListMembersMatchingResult {
2412 #[serde(default)]
2413 pub members: Vec<Value>,
2414}
2415
2416#[cfg(test)]
2417#[allow(clippy::expect_used, clippy::panic)]
2418mod tests {
2419 use super::*;
2420
2421 #[test]
2422 fn mob_helper_params_carry_structural_auth_binding() {
2423 let parsed: MobSpawnHelperParams = serde_json::from_value(serde_json::json!({
2424 "mob_id": "mob-1",
2425 "prompt": "help",
2426 "agent_identity": "helper",
2427 "auth_binding": {
2428 "realm": "dev",
2429 "binding": "default_anthropic",
2430 "profile": "console"
2431 }
2432 }))
2433 .expect("spawn helper params parse");
2434 let auth_binding = parsed.auth_binding.expect("auth_binding should parse");
2435 assert_eq!(auth_binding.realm.as_str(), "dev");
2436 assert_eq!(auth_binding.binding.as_str(), "default_anthropic");
2437 assert_eq!(
2438 auth_binding
2439 .profile
2440 .as_ref()
2441 .map(|profile| profile.as_str()),
2442 Some("console")
2443 );
2444
2445 let parsed: MobForkHelperParams = serde_json::from_value(serde_json::json!({
2446 "mob_id": "mob-1",
2447 "source_member_id": "source",
2448 "prompt": "help",
2449 "agent_identity": "helper",
2450 "auth_binding": {
2451 "realm": "dev",
2452 "binding": "default_anthropic"
2453 }
2454 }))
2455 .expect("fork helper params parse");
2456 let auth_binding = parsed.auth_binding.expect("auth_binding should parse");
2457 assert_eq!(auth_binding.realm.as_str(), "dev");
2458 assert_eq!(auth_binding.binding.as_str(), "default_anthropic");
2459 assert!(auth_binding.profile.is_none());
2460 }
2461
2462 #[test]
2463 fn wire_mob_profile_parses_provider_fields_fail_closed() {
2464 let legacy: WireMobProfile =
2466 serde_json::from_str(r#"{"model":"claude-opus-4-8"}"#).expect("legacy profile parses");
2467 assert_eq!(legacy.provider, None);
2468 assert!(legacy.resume_overrides.is_empty());
2469
2470 let full: WireMobProfile = serde_json::from_str(
2472 r#"{
2473 "model": "claude-internal-preview",
2474 "provider": "anthropic",
2475 "image_generation_provider": "gemini",
2476 "auto_compact_threshold": 60000,
2477 "resume_overrides": ["model", "provider"]
2478 }"#,
2479 )
2480 .expect("typed profile parses");
2481 assert_eq!(full.provider, Some(meerkat_core::Provider::Anthropic));
2482 assert_eq!(
2483 full.image_generation_provider,
2484 Some(meerkat_core::Provider::Gemini)
2485 );
2486 assert_eq!(
2487 full.resume_overrides,
2488 vec![
2489 WireMobResumeOverrideField::Model,
2490 WireMobResumeOverrideField::Provider
2491 ]
2492 );
2493
2494 assert!(
2496 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","provider":"not-a-provider"}"#)
2497 .is_err(),
2498 "unknown provider names must fail closed at the wire boundary"
2499 );
2500 assert!(
2501 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","auto_compact_threshold":0}"#)
2502 .is_err(),
2503 "zero auto_compact_threshold must fail closed at the wire boundary"
2504 );
2505 assert!(
2506 serde_json::from_str::<WireMobProfile>(
2507 r#"{"model":"m","resume_overrides":["everything"]}"#
2508 )
2509 .is_err(),
2510 "resume_overrides vocabulary is closed"
2511 );
2512 }
2513
2514 #[test]
2515 fn mob_definition_input_parses_custom_models() {
2516 let input: MobDefinitionInput = serde_json::from_str(
2517 r#"{
2518 "id": "m",
2519 "profiles": {"worker": {"model": "claude-internal-preview"}},
2520 "models": {
2521 "claude-internal-preview": {
2522 "provider": "anthropic",
2523 "context_window": 500000,
2524 "vision": true
2525 }
2526 },
2527 "image_generation_provider": "openai"
2528 }"#,
2529 )
2530 .expect("definition with custom models parses");
2531 let model = input
2532 .models
2533 .get("claude-internal-preview")
2534 .expect("custom model present");
2535 assert_eq!(model.provider, meerkat_core::Provider::Anthropic);
2536 assert_eq!(model.context_window, Some(500_000));
2537 assert_eq!(model.vision, Some(true));
2538 assert_eq!(
2539 input.image_generation_provider,
2540 Some(meerkat_core::Provider::OpenAI)
2541 );
2542 }
2543
2544 #[test]
2545 fn wire_member_ref_round_trips_through_encode_decode() {
2546 let token = WireMemberRef::encode("mob-42", "worker-1");
2547 let (mob_id, agent_identity) = token.decode().expect("decode round-trips");
2548 assert_eq!(mob_id, "mob-42");
2549 assert_eq!(agent_identity, "worker-1");
2550 }
2551
2552 #[test]
2553 fn wire_member_ref_rejects_malformed_token() {
2554 let err = WireMemberRef::from_token("not-a-token-payload")
2555 .decode()
2556 .expect_err("malformed tokens must fail to decode");
2557 assert!(matches!(err, WireMemberRefError::Malformed));
2558 }
2559
2560 #[test]
2561 fn mob_member_spec_exposes_shared_surface_metadata() {
2562 let spec = MobMemberSpecWire {
2563 profile: "worker".into(),
2564 agent_identity: "w1".into(),
2565 initial_message: None,
2566 runtime_mode: None,
2567 backend: None,
2568 binding: None,
2569 context: Some(serde_json::json!({"client_ref": "member-card"})),
2570 labels: Some(BTreeMap::from([("client.member_id".into(), "w1".into())])),
2571 additional_instructions: None,
2572 auto_wire_parent: None,
2573 };
2574
2575 let metadata = spec.surface_metadata();
2576 assert_eq!(
2577 metadata.labels.get("client.member_id").map(String::as_str),
2578 Some("w1")
2579 );
2580 assert_eq!(
2581 metadata.app_context,
2582 Some(serde_json::json!({"client_ref": "member-card"}))
2583 );
2584 }
2585
2586 #[test]
2587 fn mob_member_spec_surface_metadata_rejects_reserved_keys() {
2588 let spec = MobMemberSpecWire {
2589 profile: "worker".into(),
2590 agent_identity: "w1".into(),
2591 initial_message: None,
2592 runtime_mode: None,
2593 backend: None,
2594 binding: None,
2595 context: None,
2596 labels: Some(BTreeMap::from([("mob_id".into(), "spoof".into())])),
2597 additional_instructions: None,
2598 auto_wire_parent: None,
2599 };
2600
2601 assert!(spec.validate_public_surface_metadata().is_err());
2602 }
2603
2604 #[test]
2605 fn mob_reconcile_failure_stage_is_typed_wire_enum() {
2606 let failure = MobReconcileFailureWire {
2607 agent_identity: "worker-1".into(),
2608 stage: WireMobReconcileStage::Spawn,
2609 error: WireMobError {
2610 code: MobSpawnManyFailureCause::ProfileNotFound,
2611 message: "spawn failed".into(),
2612 },
2613 };
2614
2615 let json = serde_json::to_value(&failure).expect("serialize failure");
2616 assert_eq!(json["stage"], "spawn");
2617 assert_eq!(json["error"]["code"], "profile_not_found");
2618 assert_eq!(json["error"]["message"], "spawn failed");
2619
2620 let round_trip: MobReconcileFailureWire =
2621 serde_json::from_value(json).expect("deserialize failure");
2622 assert_eq!(round_trip.stage, WireMobReconcileStage::Spawn);
2623 assert_eq!(
2624 round_trip.error.code,
2625 MobSpawnManyFailureCause::ProfileNotFound
2626 );
2627
2628 let err = serde_json::from_value::<MobReconcileFailureWire>(serde_json::json!({
2629 "agent_identity": "worker-1",
2630 "stage": "restart",
2631 "error": { "code": "profile_not_found", "message": "bad stage" }
2632 }))
2633 .expect_err("unknown reconcile stage must be rejected");
2634 assert!(err.to_string().contains("unknown variant"));
2635 }
2636
2637 #[test]
2638 fn mob_lifecycle_params_reject_unknown_action_string() {
2639 let err = serde_json::from_value::<MobLifecycleParams>(serde_json::json!({
2640 "mob_id": "mob-1",
2641 "action": "explode"
2642 }))
2643 .expect_err("unknown lifecycle actions must fail at the typed wire boundary");
2644
2645 assert!(
2646 err.to_string().contains("unknown variant"),
2647 "unexpected error: {err}"
2648 );
2649 }
2650
2651 #[test]
2652 fn mob_lifecycle_result_round_trips_typed_action() {
2653 let result = MobLifecycleResult {
2654 mob_id: "mob-1".into(),
2655 action: WireMobLifecycleAction::Complete,
2656 ok: true,
2657 destroy_report: None,
2658 };
2659
2660 let json = serde_json::to_value(&result).expect("serialize lifecycle result");
2661 assert_eq!(json["action"], "complete");
2662
2663 let round_trip: MobLifecycleResult =
2664 serde_json::from_value(json).expect("deserialize lifecycle result");
2665 assert_eq!(round_trip.action, WireMobLifecycleAction::Complete);
2666 }
2667
2668 #[test]
2669 fn mob_wire_members_batch_contract_is_local_edge_native() {
2670 let params: MobWireMembersBatchParams = serde_json::from_value(serde_json::json!({
2671 "mob_id": "mob-1",
2672 "edges": [
2673 { "a": "lead", "b": "worker-b" },
2674 { "a": "worker-a", "b": "lead" }
2675 ]
2676 }))
2677 .expect("batch wire params deserialize");
2678
2679 assert_eq!(params.mob_id, "mob-1");
2680 assert_eq!(params.edges.len(), 2);
2681 assert_eq!(params.edges[0].a, "lead");
2682 assert_eq!(params.edges[0].b, "worker-b");
2683
2684 let result = MobWireMembersBatchResult {
2685 requested: 2,
2686 wired: vec![MobWireMembersBatchEdge {
2687 a: "lead".into(),
2688 b: "worker-a".into(),
2689 }],
2690 already_wired: vec![MobWireMembersBatchEdge {
2691 a: "lead".into(),
2692 b: "worker-b".into(),
2693 }],
2694 };
2695 let json = serde_json::to_value(&result).expect("serialize batch wire result");
2696 assert_eq!(json["requested"], 2);
2697 assert_eq!(json["wired"][0]["a"], "lead");
2698 assert_eq!(json["already_wired"][0]["b"], "worker-b");
2699
2700 let err = serde_json::from_value::<MobWireMembersBatchParams>(serde_json::json!({
2701 "mob_id": "mob-1",
2702 "edges": [{ "member": "lead", "peer": "worker-a" }]
2703 }))
2704 .expect_err("mixed local/external mob/wire shape must not deserialize");
2705 let message = err.to_string();
2706 assert!(
2707 message.contains("unknown field `member`") || message.contains("missing field `a`"),
2708 "unexpected error: {message}"
2709 );
2710 }
2711
2712 #[test]
2713 fn mob_spawn_many_result_entry_uses_typed_status_result_envelope() {
2714 let member_ref = WireMemberRef::encode("mob-1", "worker-1");
2715 let entry = MobSpawnManyResultEntry::spawned("worker-1", member_ref.clone());
2716
2717 let json = serde_json::to_value(&entry).expect("serialize typed spawn_many row");
2718 assert_eq!(json["status"], "spawned");
2719 assert_eq!(json["result"]["agent_identity"], "worker-1");
2720 assert_eq!(json["result"]["member_ref"], member_ref.as_str());
2721 assert!(json.get("ok").is_none());
2722 assert!(json.get("error").is_none());
2723
2724 let round_trip: MobSpawnManyResultEntry =
2725 serde_json::from_value(json).expect("deserialize typed spawn_many row");
2726 assert_eq!(round_trip, entry);
2727
2728 let failed = MobSpawnManyResultEntry::failed(
2729 MobSpawnManyFailureCause::ProfileNotFound,
2730 "profile missing",
2731 );
2732 let json = serde_json::to_value(&failed).expect("serialize typed failed spawn_many row");
2733 assert_eq!(json["status"], "failed");
2734 assert_eq!(json["result"]["cause"], "profile_not_found");
2735 assert_eq!(json["result"]["message"], "profile missing");
2736 assert!(json.get("ok").is_none());
2737 assert!(json.get("error").is_none());
2738
2739 let round_trip: MobSpawnManyResultEntry =
2740 serde_json::from_value(json).expect("deserialize typed failed spawn_many row");
2741 assert_eq!(round_trip, failed);
2742 }
2743
2744 #[test]
2745 fn mob_spawn_many_result_entry_rejects_legacy_or_malformed_envelopes() {
2746 let legacy = serde_json::json!({
2747 "ok": true,
2748 "agent_identity": "worker-1",
2749 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2750 });
2751 let err = serde_json::from_value::<MobSpawnManyResultEntry>(legacy)
2752 .expect_err("legacy ok carrier must not deserialize");
2753 assert!(
2754 err.to_string().contains("missing field `status`")
2755 || err.to_string().contains("unknown field"),
2756 "unexpected error: {err}"
2757 );
2758
2759 let missing_result = serde_json::json!({
2760 "status": "spawned"
2761 });
2762 let err = serde_json::from_value::<MobSpawnManyResultEntry>(missing_result)
2763 .expect_err("missing typed result must fail closed");
2764 assert!(
2765 err.to_string().contains("missing field `result`"),
2766 "unexpected error: {err}"
2767 );
2768
2769 let unknown_status = serde_json::json!({
2770 "status": "ok",
2771 "result": {
2772 "agent_identity": "worker-1",
2773 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2774 }
2775 });
2776 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_status)
2777 .expect_err("unknown typed status must fail closed");
2778 assert!(
2779 err.to_string().contains("unknown variant"),
2780 "unexpected error: {err}"
2781 );
2782
2783 let mismatched = serde_json::json!({
2784 "status": "spawned",
2785 "result": {
2786 "cause": "profile_not_found",
2787 "message": "profile missing"
2788 }
2789 });
2790 let err = serde_json::from_value::<MobSpawnManyResultEntry>(mismatched)
2791 .expect_err("status/result mismatch must fail closed");
2792 assert!(
2793 err.to_string()
2794 .contains("status spawned requires spawned result"),
2795 "unexpected error: {err}"
2796 );
2797
2798 let message_only_failure = serde_json::json!({
2799 "status": "failed",
2800 "result": {
2801 "message": "profile missing"
2802 }
2803 });
2804 let err = serde_json::from_value::<MobSpawnManyResultEntry>(message_only_failure)
2805 .expect_err("string-only failure result must fail closed");
2806 assert!(
2807 err.to_string().contains("data did not match any variant")
2808 || err.to_string().contains("missing field `cause`"),
2809 "unexpected error: {err}"
2810 );
2811
2812 let unknown_failure_cause = serde_json::json!({
2813 "status": "failed",
2814 "result": {
2815 "cause": "future_failure",
2816 "message": "future failure"
2817 }
2818 });
2819 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_failure_cause)
2820 .expect_err("unknown failure cause must fail closed");
2821 assert!(
2822 err.to_string().contains("data did not match any variant")
2823 || err.to_string().contains("unknown variant"),
2824 "unexpected error: {err}"
2825 );
2826 }
2827
2828 #[test]
2829 fn mob_wire_params_reject_legacy_local_target_shape() {
2830 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2831 "mob_id": "mob-1",
2832 "local": "member-a",
2833 "target": { "local": "member-b" }
2834 }))
2835 .expect_err("legacy local/target shape must be rejected");
2836
2837 let msg = err.to_string();
2838 assert!(
2839 msg.contains("unknown field `local`") || msg.contains("missing field `member`"),
2840 "unexpected error: {msg}"
2841 );
2842 }
2843
2844 #[test]
2845 fn mob_wire_params_accept_canonical_external_peer_identity() {
2846 let params = serde_json::from_value::<MobWireParams>(serde_json::json!({
2847 "mob_id": "mob-1",
2848 "member": "member-a",
2849 "peer": {
2850 "external": {
2851 "name": "external-worker",
2852 "address": "inproc://external-worker",
2853 "identity": {
2854 "kind": "ed25519_public_key",
2855 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2856 }
2857 }
2858 }
2859 }))
2860 .expect("canonical external peer identity should deserialize");
2861
2862 let MobPeerTarget::External(spec) = params.peer else {
2863 panic!("expected external peer target");
2864 };
2865 assert_eq!(spec.name, "external-worker");
2866 }
2867
2868 #[test]
2869 fn mob_wire_params_reject_raw_external_peer_id_shape() {
2870 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2871 "mob_id": "mob-1",
2872 "member": "member-a",
2873 "peer": {
2874 "external": {
2875 "name": "external-worker",
2876 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2877 "address": "inproc://external-worker",
2878 "pubkey": vec![7u8; 32]
2879 }
2880 }
2881 }))
2882 .expect_err("raw peer_id/pubkey external peer shape must be rejected");
2883
2884 let msg = err.to_string();
2885 assert!(
2886 msg.contains("peer_id") || msg.contains("identity"),
2887 "unexpected error: {msg}"
2888 );
2889 }
2890
2891 #[test]
2892 fn mob_wire_params_reject_missing_external_peer_pubkey_material() {
2893 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2894 "mob_id": "mob-1",
2895 "member": "member-a",
2896 "peer": {
2897 "external": {
2898 "name": "external-worker",
2899 "address": "inproc://external-worker",
2900 "identity": {
2901 "kind": "ed25519_public_key"
2902 }
2903 }
2904 }
2905 }))
2906 .expect_err("missing external peer pubkey material must fail closed");
2907
2908 let msg = err.to_string();
2909 assert!(
2910 msg.contains("public_key") || msg.contains("identity"),
2911 "unexpected error: {msg}"
2912 );
2913 }
2914
2915 #[test]
2916 fn runtime_binding_accepts_canonical_external_peer_identity() {
2917 let binding = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2918 "kind": "external",
2919 "address": "inproc://external-worker",
2920 "identity": {
2921 "kind": "ed25519_public_key",
2922 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2923 }
2924 }))
2925 .expect("canonical external runtime binding identity should deserialize");
2926
2927 let WireRuntimeBinding::External {
2928 identity, address, ..
2929 } = binding
2930 else {
2931 panic!("expected external runtime binding");
2932 };
2933 assert_eq!(address, "inproc://external-worker");
2934 assert_eq!(
2935 identity.resolve().expect("identity resolves").pubkey,
2936 [7u8; 32]
2937 );
2938 }
2939
2940 #[test]
2941 fn runtime_binding_rejects_raw_external_peer_id_shape() {
2942 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2943 "kind": "external",
2944 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2945 "address": "inproc://external-worker",
2946 "pubkey": vec![7u8; 32]
2947 }))
2948 .expect_err("raw peer_id/pubkey external runtime binding shape must be rejected");
2949
2950 let msg = err.to_string();
2951 assert!(
2952 msg.contains("peer_id") || msg.contains("identity"),
2953 "unexpected error: {msg}"
2954 );
2955 }
2956
2957 #[test]
2958 fn runtime_binding_rejects_missing_external_peer_pubkey_material() {
2959 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2960 "kind": "external",
2961 "address": "inproc://external-worker",
2962 "identity": {
2963 "kind": "ed25519_public_key"
2964 }
2965 }))
2966 .expect_err("missing external runtime binding pubkey material must fail closed");
2967
2968 let msg = err.to_string();
2969 assert!(
2970 msg.contains("public_key") || msg.contains("identity"),
2971 "unexpected error: {msg}"
2972 );
2973 }
2974
2975 #[test]
2976 fn mob_turn_start_params_capture_turn_override_fields() {
2977 let params = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2978 "mob_id": "mob-1",
2979 "agent_identity": "worker",
2980 "prompt": "continue",
2981 "output_schema": { "type": "object" },
2982 "structured_output_retries": 2
2983 }))
2984 .expect("turn_start should accept explicit turn override fields");
2985
2986 assert_eq!(params.mob_id, "mob-1");
2987 assert_eq!(params.agent_identity, "worker");
2988 assert_eq!(params.prompt, WireContentInput::Text("continue".into()));
2989 assert_eq!(
2990 params.output_schema,
2991 Some(serde_json::json!({ "type": "object" }))
2992 );
2993 assert_eq!(params.structured_output_retries, Some(2));
2994
2995 let err = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2996 "mob_id": "mob-1",
2997 "agent_identity": "worker",
2998 "prompt": "continue",
2999 "unknown_override": true
3000 }))
3001 .expect_err("turn_start must reject unknown override fields");
3002 assert!(
3003 err.to_string().contains("unknown field"),
3004 "unexpected error: {err}"
3005 );
3006 }
3007
3008 #[test]
3009 fn mob_create_params_reject_reserved_runtime_lifecycle_fields() {
3010 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3011 "definition": {
3012 "id": "mob-1",
3013 "owner_runtime_binding": "runtime:worker:0",
3014 "profiles": {
3015 "worker": { "model": "claude-sonnet-4-6" }
3016 }
3017 }
3018 }))
3019 .expect_err("reserved runtime lifecycle fields must be rejected");
3020
3021 assert!(
3022 err.to_string()
3023 .contains("unknown field `owner_runtime_binding`"),
3024 "unexpected error: {err}"
3025 );
3026 }
3027
3028 #[test]
3029 fn mob_create_params_reject_reserved_runtime_bridge_owner_field() {
3030 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3031 "definition": {
3032 "id": "mob-1",
3033 "owner_transport_binding": "transport:worker:0",
3034 "profiles": {
3035 "worker": { "model": "claude-sonnet-4-6" }
3036 }
3037 }
3038 }))
3039 .expect_err("reserved runtime bridge owner field must be rejected");
3040
3041 assert!(
3042 err.to_string()
3043 .contains("unknown field `owner_transport_binding`"),
3044 "unexpected error: {err}"
3045 );
3046 }
3047
3048 #[test]
3049 fn mob_create_params_reject_internal_profile_tool_bundles() {
3050 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3051 "definition": {
3052 "id": "mob-1",
3053 "profiles": {
3054 "worker": {
3055 "model": "claude-sonnet-4-6",
3056 "tools": {
3057 "rust_bundles": ["internal-only"]
3058 }
3059 }
3060 }
3061 }
3062 }))
3063 .expect_err("internal rust tool bundles must be rejected");
3064
3065 assert!(
3068 err.to_string().contains("did not match any variant")
3069 || err.to_string().contains("unknown field `rust_bundles`"),
3070 "unexpected error: {err}"
3071 );
3072 }
3073
3074 #[test]
3075 fn mob_create_params_accept_typed_nested_flow_definition() {
3076 let params = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3077 "definition": {
3078 "id": "mob-1",
3079 "profiles": {
3080 "worker": { "model": "claude-sonnet-4-6" }
3081 },
3082 "flows": {
3083 "review": {
3084 "description": "review flow",
3085 "steps": {
3086 "draft": {
3087 "role": "worker",
3088 "message": "draft it"
3089 }
3090 }
3091 }
3092 }
3093 }
3094 }))
3095 .expect("typed nested flow definition should parse");
3096
3097 assert_eq!(
3098 params.definition.flows["review"].steps["draft"].role,
3099 "worker"
3100 );
3101 }
3102}