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 model_override: Option<String>,
720 #[serde(default, skip_serializing_if = "Option::is_none")]
721 pub auth_binding: Option<WireAuthBindingRef>,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
726#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
727pub struct MobSpawnResult {
728 pub mob_id: String,
729 pub agent_identity: String,
730 pub member_ref: WireMemberRef,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
735#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
736#[serde(deny_unknown_fields)]
737pub struct MobSpawnSpecParams {
738 pub profile: String,
739 pub agent_identity: String,
740 #[serde(default, skip_serializing_if = "Option::is_none")]
741 pub initial_message: Option<WireContentInput>,
742 #[serde(default, skip_serializing_if = "Option::is_none")]
743 pub runtime_mode: Option<WireMobRuntimeMode>,
744 #[serde(default, skip_serializing_if = "Option::is_none")]
745 pub backend: Option<WireMobBackendKind>,
746 #[serde(default, skip_serializing_if = "Option::is_none")]
747 pub labels: Option<BTreeMap<String, String>>,
748 #[serde(default, skip_serializing_if = "Option::is_none")]
749 pub context: Option<Value>,
750 #[serde(default, skip_serializing_if = "Option::is_none")]
751 pub additional_instructions: Option<Vec<String>>,
752 #[serde(default, skip_serializing_if = "Option::is_none")]
753 pub model_override: Option<String>,
754 #[serde(default, skip_serializing_if = "Option::is_none")]
755 pub auth_binding: Option<WireAuthBindingRef>,
756}
757
758#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
760#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
761#[serde(deny_unknown_fields)]
762pub struct MobSpawnManyParams {
763 pub mob_id: String,
764 pub specs: Vec<MobSpawnSpecParams>,
765}
766
767#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
769#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
770#[serde(rename_all = "snake_case")]
771pub enum MobSpawnManyResultStatus {
772 Spawned,
773 Failed,
774}
775
776#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
778#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
779#[serde(deny_unknown_fields)]
780pub struct MobSpawnManySpawnedResult {
781 pub agent_identity: String,
782 pub member_ref: WireMemberRef,
783}
784
785#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
787#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
788#[serde(rename_all = "snake_case")]
789pub enum MobSpawnManyFailureCause {
790 ProfileNotFound,
791 MemberNotFound,
792 MemberAlreadyExists,
793 NotExternallyAddressable,
794 InvalidTransition,
795 WiringError,
796 BridgeCommandRejected,
797 MemberRestoreFailed,
798 KickoffWaitTimedOut,
799 ReadyWaitTimedOut,
800 DefinitionError,
801 FlowNotFound,
802 FlowFailed,
803 RunNotFound,
804 RunCanceled,
805 FlowTurnTimedOut,
806 FrameDepthLimitExceeded,
807 FrameAtomicPersistenceUnavailable,
808 SpecRevisionConflict,
809 SchemaValidation,
810 InsufficientTargets,
811 TopologyViolation,
812 BridgeDeliveryRejected,
813 SupervisorEscalation,
814 UnsupportedForMode,
815 MissingMemberCapability,
816 ResetBarrier,
817 StorageError,
818 SessionError,
819 CommsError,
820 CallbackPending,
821 StaleFenceToken,
822 StaleEventCursor,
823 WorkNotFound,
824 Internal,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
829#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
830#[serde(deny_unknown_fields)]
831pub struct MobSpawnManyFailedResult {
832 pub cause: MobSpawnManyFailureCause,
833 pub message: String,
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
838#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
839#[serde(untagged)]
840pub enum MobSpawnManyResultPayload {
841 Spawned(MobSpawnManySpawnedResult),
842 Failed(MobSpawnManyFailedResult),
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
847#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
848#[serde(try_from = "MobSpawnManyResultEntryRaw")]
849pub struct MobSpawnManyResultEntry {
850 pub status: MobSpawnManyResultStatus,
851 pub result: MobSpawnManyResultPayload,
852}
853
854#[derive(Debug, Deserialize)]
855#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
856#[serde(deny_unknown_fields)]
857struct MobSpawnManyResultEntryRaw {
858 status: MobSpawnManyResultStatus,
859 result: MobSpawnManyResultPayload,
860}
861
862impl TryFrom<MobSpawnManyResultEntryRaw> for MobSpawnManyResultEntry {
863 type Error = String;
864
865 fn try_from(raw: MobSpawnManyResultEntryRaw) -> Result<Self, Self::Error> {
866 let entry = Self {
867 status: raw.status,
868 result: raw.result,
869 };
870 entry.validate().map_err(str::to_owned)?;
871 Ok(entry)
872 }
873}
874
875impl MobSpawnManyResultEntry {
876 pub fn spawned(agent_identity: impl Into<String>, member_ref: WireMemberRef) -> Self {
877 Self {
878 status: MobSpawnManyResultStatus::Spawned,
879 result: MobSpawnManyResultPayload::Spawned(MobSpawnManySpawnedResult {
880 agent_identity: agent_identity.into(),
881 member_ref,
882 }),
883 }
884 }
885
886 pub fn failed(cause: MobSpawnManyFailureCause, message: impl Into<String>) -> Self {
887 Self {
888 status: MobSpawnManyResultStatus::Failed,
889 result: MobSpawnManyResultPayload::Failed(MobSpawnManyFailedResult {
890 cause,
891 message: message.into(),
892 }),
893 }
894 }
895
896 pub fn validate(&self) -> Result<(), &'static str> {
897 match (&self.status, &self.result) {
898 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Spawned(_))
899 | (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Failed(_)) => Ok(()),
900 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Failed(_)) => {
901 Err("mob spawn_many result status spawned requires spawned result")
902 }
903 (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Spawned(_)) => {
904 Err("mob spawn_many result status failed requires failed result")
905 }
906 }
907 }
908}
909
910#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
912#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
913pub struct MobSpawnManyResult {
914 pub results: Vec<MobSpawnManyResultEntry>,
915}
916
917#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
919#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
920pub struct MobRetireResult {
921 pub retired: bool,
922}
923
924#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
926#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
927#[serde(deny_unknown_fields)]
928pub struct MobRespawnParams {
929 pub mob_id: String,
930 pub agent_identity: String,
931 #[serde(default, skip_serializing_if = "Option::is_none")]
932 pub initial_message: Option<WireContentInput>,
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
937#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
938pub struct MobRespawnReceipt {
939 pub identity: String,
940 pub member_ref: WireMemberRef,
941}
942
943#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
948#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
949#[serde(rename_all = "snake_case")]
950pub enum WireMobRespawnOutcome {
951 Completed,
952 TopologyRestoreFailed,
953}
954
955#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
957#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
958pub struct MobRespawnResult {
959 pub status: WireMobRespawnOutcome,
960 pub receipt: MobRespawnReceipt,
961 #[serde(default, skip_serializing_if = "Vec::is_empty")]
962 pub failed_peer_ids: Vec<String>,
963}
964
965#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
967#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
968pub struct MobMembersResult {
969 pub mob_id: String,
970 pub members: Vec<MobMemberListEntryWire>,
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
975#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
976#[serde(deny_unknown_fields)]
977pub struct MobEventsParams {
978 pub mob_id: String,
979 #[serde(default)]
980 pub after_cursor: u64,
981 #[serde(default = "default_mob_events_limit")]
982 pub limit: usize,
983 #[serde(default)]
984 pub strict: bool,
985}
986
987const fn default_mob_events_limit() -> usize {
988 100
989}
990
991#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
993#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
994pub struct MobEventsResult {
995 pub events: Vec<Value>,
996}
997
998#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1000#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1001#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1002pub enum WireTrustedPeerIdentity {
1003 Ed25519PublicKey { public_key: String },
1005}
1006
1007#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1009pub struct ResolvedWireTrustedPeerIdentity {
1010 pub peer_id: meerkat_core::comms::PeerId,
1011 pub pubkey: [u8; 32],
1012}
1013
1014#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
1016pub enum WireTrustedPeerIdentityError {
1017 #[error("external peer identity public_key must start with 'ed25519:'")]
1018 MissingEd25519Prefix,
1019 #[error("external peer identity public_key is not valid base64: {0}")]
1020 InvalidBase64(String),
1021 #[error("external peer identity public_key must decode to 32 bytes, got {actual}")]
1022 InvalidLength { actual: usize },
1023 #[error("external peer identity public_key must be non-zero")]
1024 ZeroPublicKey,
1025}
1026
1027impl WireTrustedPeerIdentity {
1028 pub fn resolve(&self) -> Result<ResolvedWireTrustedPeerIdentity, WireTrustedPeerIdentityError> {
1029 match self {
1030 Self::Ed25519PublicKey { public_key } => {
1031 let pubkey = parse_ed25519_public_key(public_key)?;
1032 if pubkey == [0u8; 32] {
1033 return Err(WireTrustedPeerIdentityError::ZeroPublicKey);
1034 }
1035 Ok(ResolvedWireTrustedPeerIdentity {
1036 peer_id: meerkat_core::comms::PeerId::from_ed25519_pubkey(&pubkey),
1037 pubkey,
1038 })
1039 }
1040 }
1041 }
1042}
1043
1044fn parse_ed25519_public_key(raw: &str) -> Result<[u8; 32], WireTrustedPeerIdentityError> {
1045 const PREFIX: &str = "ed25519:";
1046 let encoded = raw
1047 .strip_prefix(PREFIX)
1048 .ok_or(WireTrustedPeerIdentityError::MissingEd25519Prefix)?;
1049 let bytes = BASE64
1050 .decode(encoded)
1051 .map_err(|err| WireTrustedPeerIdentityError::InvalidBase64(err.to_string()))?;
1052 let actual = bytes.len();
1053 let pubkey: [u8; 32] = bytes
1054 .try_into()
1055 .map_err(|_| WireTrustedPeerIdentityError::InvalidLength { actual })?;
1056 Ok(pubkey)
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1065#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1066#[serde(deny_unknown_fields)]
1067pub struct WireTrustedPeerSpec {
1068 pub name: String,
1069 pub address: String,
1070 pub identity: WireTrustedPeerIdentity,
1071}
1072
1073#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1075#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1076#[serde(rename_all = "snake_case")]
1077pub enum MobPeerTarget {
1078 Local(String),
1079 External(WireTrustedPeerSpec),
1080}
1081
1082#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1084#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1085#[serde(deny_unknown_fields)]
1086pub struct MobWireParams {
1087 pub mob_id: String,
1088 pub member: String,
1089 pub peer: MobPeerTarget,
1090}
1091
1092#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1094#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1095pub struct MobWireResult {
1096 pub wired: bool,
1097}
1098
1099#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1101#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1102#[serde(deny_unknown_fields)]
1103pub struct MobWireMembersBatchEdge {
1104 pub a: String,
1105 pub b: String,
1106}
1107
1108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1110#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1111#[serde(deny_unknown_fields)]
1112pub struct MobWireMembersBatchParams {
1113 pub mob_id: String,
1114 pub edges: Vec<MobWireMembersBatchEdge>,
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1119#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1120pub struct MobWireMembersBatchResult {
1121 pub requested: usize,
1122 pub wired: Vec<MobWireMembersBatchEdge>,
1123 pub already_wired: Vec<MobWireMembersBatchEdge>,
1124}
1125
1126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1128#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1129#[serde(deny_unknown_fields)]
1130pub struct MobUnwireParams {
1131 pub mob_id: String,
1132 pub member: String,
1133 pub peer: MobPeerTarget,
1134}
1135
1136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1138#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1139pub struct MobUnwireResult {
1140 pub unwired: bool,
1141}
1142
1143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1146#[serde(deny_unknown_fields)]
1147pub struct MobMemberSendParams {
1148 pub mob_id: String,
1149 pub agent_identity: String,
1150 pub content: WireContentInput,
1151 #[serde(default)]
1152 pub handling_mode: WireHandlingMode,
1153 #[serde(default, skip_serializing_if = "Option::is_none")]
1154 pub render_metadata: Option<WireRenderMetadata>,
1155}
1156
1157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1159#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1160pub struct WireAgentRuntimeId {
1161 pub identity: String,
1162 pub generation: u64,
1163}
1164
1165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1167#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1168pub struct MobMemberSendResult {
1169 pub mob_id: String,
1170 pub agent_identity: String,
1172 pub member_ref: WireMemberRef,
1177 pub handling_mode: WireHandlingMode,
1178}
1179
1180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1186#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1187#[serde(deny_unknown_fields)]
1188pub struct MobIngressInteractionParams {
1189 pub mob_id: String,
1190 pub spec: MobMemberSpecWire,
1191 pub content: WireContentInput,
1192 #[serde(default)]
1193 pub handling_mode: WireHandlingMode,
1194 #[serde(default, skip_serializing_if = "Option::is_none")]
1195 pub render_metadata: Option<WireRenderMetadata>,
1196}
1197
1198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1200#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1201pub struct MobIngressInteractionResult {
1202 pub mob_id: String,
1203 pub agent_identity: String,
1204 pub member_ref: WireMemberRef,
1205 pub ensure_outcome: MobEnsureMemberOutcomeWire,
1206 pub delivery: MobMemberSendResult,
1207 pub events_after_cursor: u64,
1209 pub latest_event_cursor: u64,
1211}
1212
1213#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1216#[serde(rename_all = "snake_case")]
1217pub enum WireHandlingMode {
1218 #[default]
1219 Queue,
1220 Steer,
1221}
1222
1223impl From<WireHandlingMode> for HandlingMode {
1224 fn from(mode: WireHandlingMode) -> Self {
1225 match mode {
1226 WireHandlingMode::Queue => HandlingMode::Queue,
1227 WireHandlingMode::Steer => HandlingMode::Steer,
1228 }
1229 }
1230}
1231
1232impl From<HandlingMode> for WireHandlingMode {
1233 fn from(mode: HandlingMode) -> Self {
1234 match mode {
1235 HandlingMode::Queue => WireHandlingMode::Queue,
1236 HandlingMode::Steer => WireHandlingMode::Steer,
1237 }
1238 }
1239}
1240
1241#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1244#[serde(rename_all = "snake_case")]
1245pub enum WireRenderClass {
1246 UserPrompt,
1247 PeerMessage,
1248 PeerRequest,
1249 PeerResponse,
1250 ExternalEvent,
1251 FlowStep,
1252 Continuation,
1253 SystemNotice,
1254 ToolScopeNotice,
1255 OpsProgress,
1256}
1257
1258impl From<WireRenderClass> for RenderClass {
1259 fn from(class: WireRenderClass) -> Self {
1260 match class {
1261 WireRenderClass::UserPrompt => RenderClass::UserPrompt,
1262 WireRenderClass::PeerMessage => RenderClass::PeerMessage,
1263 WireRenderClass::PeerRequest => RenderClass::PeerRequest,
1264 WireRenderClass::PeerResponse => RenderClass::PeerResponse,
1265 WireRenderClass::ExternalEvent => RenderClass::ExternalEvent,
1266 WireRenderClass::FlowStep => RenderClass::FlowStep,
1267 WireRenderClass::Continuation => RenderClass::Continuation,
1268 WireRenderClass::SystemNotice => RenderClass::SystemNotice,
1269 WireRenderClass::ToolScopeNotice => RenderClass::ToolScopeNotice,
1270 WireRenderClass::OpsProgress => RenderClass::OpsProgress,
1271 }
1272 }
1273}
1274
1275impl From<RenderClass> for WireRenderClass {
1276 fn from(class: RenderClass) -> Self {
1277 match class {
1278 RenderClass::UserPrompt => WireRenderClass::UserPrompt,
1279 RenderClass::PeerMessage => WireRenderClass::PeerMessage,
1280 RenderClass::PeerRequest => WireRenderClass::PeerRequest,
1281 RenderClass::PeerResponse => WireRenderClass::PeerResponse,
1282 RenderClass::ExternalEvent => WireRenderClass::ExternalEvent,
1283 RenderClass::FlowStep => WireRenderClass::FlowStep,
1284 RenderClass::Continuation => WireRenderClass::Continuation,
1285 RenderClass::SystemNotice => WireRenderClass::SystemNotice,
1286 RenderClass::ToolScopeNotice => WireRenderClass::ToolScopeNotice,
1287 RenderClass::OpsProgress => WireRenderClass::OpsProgress,
1288 }
1289 }
1290}
1291
1292#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1294#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1295#[serde(rename_all = "snake_case")]
1296pub enum WireRenderSalience {
1297 Background,
1298 Normal,
1299 Important,
1300 Urgent,
1301}
1302
1303impl From<WireRenderSalience> for RenderSalience {
1304 fn from(salience: WireRenderSalience) -> Self {
1305 match salience {
1306 WireRenderSalience::Background => RenderSalience::Background,
1307 WireRenderSalience::Normal => RenderSalience::Normal,
1308 WireRenderSalience::Important => RenderSalience::Important,
1309 WireRenderSalience::Urgent => RenderSalience::Urgent,
1310 }
1311 }
1312}
1313
1314impl From<RenderSalience> for WireRenderSalience {
1315 fn from(salience: RenderSalience) -> Self {
1316 match salience {
1317 RenderSalience::Background => WireRenderSalience::Background,
1318 RenderSalience::Normal => WireRenderSalience::Normal,
1319 RenderSalience::Important => WireRenderSalience::Important,
1320 RenderSalience::Urgent => WireRenderSalience::Urgent,
1321 }
1322 }
1323}
1324
1325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1327#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1328pub struct WireRenderMetadata {
1329 pub class: WireRenderClass,
1330 #[serde(default, skip_serializing_if = "Option::is_none")]
1331 pub salience: Option<WireRenderSalience>,
1332}
1333
1334impl From<WireRenderMetadata> for RenderMetadata {
1335 fn from(metadata: WireRenderMetadata) -> Self {
1336 Self {
1337 class: metadata.class.into(),
1338 salience: metadata
1339 .salience
1340 .unwrap_or(WireRenderSalience::Normal)
1341 .into(),
1342 }
1343 }
1344}
1345
1346impl From<RenderMetadata> for WireRenderMetadata {
1347 fn from(metadata: RenderMetadata) -> Self {
1348 Self {
1349 class: metadata.class.into(),
1350 salience: Some(metadata.salience.into()),
1351 }
1352 }
1353}
1354
1355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1370#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1371pub struct MobMemberSpecWire {
1372 pub profile: String,
1374 pub agent_identity: String,
1376 #[serde(default, skip_serializing_if = "Option::is_none")]
1377 pub initial_message: Option<WireContentInput>,
1378 #[serde(default, skip_serializing_if = "Option::is_none")]
1379 pub runtime_mode: Option<WireMobRuntimeMode>,
1380 #[serde(default, skip_serializing_if = "Option::is_none")]
1381 pub backend: Option<WireMobBackendKind>,
1382 #[serde(default, skip_serializing_if = "Option::is_none")]
1383 pub binding: Option<WireRuntimeBinding>,
1384 #[serde(default, skip_serializing_if = "Option::is_none")]
1385 pub context: Option<Value>,
1386 #[serde(default, skip_serializing_if = "Option::is_none")]
1387 pub labels: Option<BTreeMap<String, String>>,
1388 #[serde(default, skip_serializing_if = "Option::is_none")]
1389 pub additional_instructions: Option<Vec<String>>,
1390 #[serde(default, skip_serializing_if = "Option::is_none")]
1391 pub auto_wire_parent: Option<bool>,
1392}
1393
1394impl MobMemberSpecWire {
1395 #[must_use]
1398 pub fn surface_metadata(&self) -> SurfaceMetadata {
1399 SurfaceMetadata::from_optional_parts(self.labels.clone(), self.context.clone())
1400 }
1401
1402 pub fn validate_public_surface_metadata(&self) -> Result<(), SurfaceMetadataError> {
1404 self.surface_metadata().validate_public()
1405 }
1406}
1407
1408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1410#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1411#[serde(deny_unknown_fields)]
1412pub struct MobEnsureMemberParams {
1413 pub mob_id: String,
1414 pub spec: MobMemberSpecWire,
1415}
1416
1417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1429#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1430#[serde(transparent)]
1431pub struct WireMemberRef(String);
1432
1433impl WireMemberRef {
1434 #[must_use]
1438 pub fn encode(mob_id: &str, agent_identity: &str) -> Self {
1439 let payload = serde_json::json!({ "m": mob_id, "a": agent_identity });
1443 Self(base64_url_encode(payload.to_string().as_bytes()))
1444 }
1445
1446 #[must_use]
1448 pub fn as_str(&self) -> &str {
1449 &self.0
1450 }
1451
1452 #[must_use]
1455 pub fn from_token(token: impl Into<String>) -> Self {
1456 Self(token.into())
1457 }
1458
1459 pub fn decode(&self) -> Result<(String, String), WireMemberRefError> {
1462 let bytes = base64_url_decode(&self.0).map_err(|_| WireMemberRefError::Malformed)?;
1463 let value: Value =
1464 serde_json::from_slice(&bytes).map_err(|_| WireMemberRefError::Malformed)?;
1465 let mob_id = value
1466 .get("m")
1467 .and_then(Value::as_str)
1468 .ok_or(WireMemberRefError::Malformed)?;
1469 let agent_identity = value
1470 .get("a")
1471 .and_then(Value::as_str)
1472 .ok_or(WireMemberRefError::Malformed)?;
1473 Ok((mob_id.to_string(), agent_identity.to_string()))
1474 }
1475}
1476
1477#[derive(Debug, thiserror::Error)]
1479pub enum WireMemberRefError {
1480 #[error("malformed member ref token")]
1483 Malformed,
1484}
1485
1486fn base64_url_encode(bytes: &[u8]) -> String {
1487 use base64::Engine as _;
1488 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
1489}
1490
1491fn base64_url_decode(input: &str) -> Result<Vec<u8>, base64::DecodeError> {
1492 use base64::Engine as _;
1493 base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)
1494}
1495
1496#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1498#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1499pub struct MobSpawnReceiptWire {
1500 pub agent_identity: String,
1501 pub member_ref: WireMemberRef,
1505}
1506
1507#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1509#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1510#[serde(rename_all = "snake_case")]
1511pub enum WireMobMemberStatus {
1512 Active,
1513 Retiring,
1514 Broken,
1515 Completed,
1516 Unknown,
1517}
1518
1519#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1524#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1525pub struct MobMemberListEntryWire {
1526 pub agent_identity: String,
1527 pub member_ref: WireMemberRef,
1528 pub role: String,
1529 pub runtime_mode: WireMobRuntimeMode,
1530 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1531 pub wired_to: Vec<String>,
1532 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1533 pub labels: BTreeMap<String, String>,
1534 pub status: WireMobMemberStatus,
1535 #[serde(default, skip_serializing_if = "Option::is_none")]
1536 pub error: Option<String>,
1537 pub is_final: bool,
1538}
1539
1540#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1546#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1547pub enum MobEnsureMemberOutcomeWire {
1548 #[serde(rename = "spawned")]
1549 Spawned(MobSpawnReceiptWire),
1550 #[serde(rename = "existed")]
1551 Existed(MobMemberListEntryWire),
1552}
1553
1554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1557pub struct MobEnsureMemberResult {
1558 pub outcome: MobEnsureMemberOutcomeWire,
1559}
1560
1561#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
1563#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1564#[serde(deny_unknown_fields)]
1565pub struct MobReconcileOptionsWire {
1566 #[serde(default)]
1569 pub retire_stale: bool,
1570}
1571
1572#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1574#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1575#[serde(rename_all = "snake_case")]
1576pub enum WireMobReconcileStage {
1577 Spawn,
1578 Retire,
1579}
1580
1581#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1583#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1584#[serde(deny_unknown_fields)]
1585pub struct MobReconcileParams {
1586 pub mob_id: String,
1587 #[serde(default)]
1588 pub desired: Vec<MobMemberSpecWire>,
1589 #[serde(default)]
1590 pub options: MobReconcileOptionsWire,
1591}
1592
1593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1598#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1599pub struct WireMobError {
1600 pub code: MobSpawnManyFailureCause,
1601 pub message: String,
1602}
1603
1604#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1606#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1607pub struct MobReconcileFailureWire {
1608 pub agent_identity: String,
1609 pub stage: WireMobReconcileStage,
1610 pub error: WireMobError,
1612}
1613
1614#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1616#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1617pub struct MobReconcileReportWire {
1618 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1619 pub desired: Vec<String>,
1620 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1621 pub retained: Vec<String>,
1622 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1623 pub spawned: Vec<MobSpawnReceiptWire>,
1624 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1625 pub retired: Vec<String>,
1626 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1627 pub failures: Vec<MobReconcileFailureWire>,
1628}
1629
1630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1632#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1633pub struct MobReconcileResult {
1634 pub report: MobReconcileReportWire,
1635}
1636
1637#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1642#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1643#[serde(rename_all = "snake_case")]
1644pub enum WireMobLifecycleAction {
1645 Stop,
1646 Resume,
1647 Complete,
1648 Reset,
1649 Destroy,
1650}
1651
1652#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1657#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1658#[serde(rename_all = "snake_case")]
1659pub enum WireMobWireAction {
1660 Wire,
1661 Unwire,
1662}
1663
1664#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1666#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1667#[serde(deny_unknown_fields)]
1668pub struct MobLifecycleParams {
1669 pub mob_id: String,
1670 pub action: WireMobLifecycleAction,
1671}
1672
1673#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1675#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1676pub struct MobLifecycleResult {
1677 pub mob_id: String,
1678 pub action: WireMobLifecycleAction,
1679 pub ok: bool,
1680 #[serde(default, skip_serializing_if = "Option::is_none")]
1681 pub destroy_report: Option<Value>,
1682}
1683
1684#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1686#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1687#[serde(deny_unknown_fields)]
1688pub struct MobAppendSystemContextParams {
1689 pub mob_id: String,
1690 pub agent_identity: String,
1691 pub text: String,
1692 #[serde(default, skip_serializing_if = "Option::is_none")]
1693 pub source: Option<String>,
1694 #[serde(default, skip_serializing_if = "Option::is_none")]
1695 pub idempotency_key: Option<String>,
1696}
1697
1698#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1703#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1704#[serde(rename_all = "snake_case")]
1705pub enum WireAppendSystemContextStatus {
1706 Applied,
1707 Staged,
1708 Duplicate,
1709}
1710
1711impl From<meerkat_core::AppendSystemContextStatus> for WireAppendSystemContextStatus {
1712 fn from(status: meerkat_core::AppendSystemContextStatus) -> Self {
1713 match status {
1714 meerkat_core::AppendSystemContextStatus::Applied => Self::Applied,
1715 meerkat_core::AppendSystemContextStatus::Staged => Self::Staged,
1716 meerkat_core::AppendSystemContextStatus::Duplicate => Self::Duplicate,
1717 }
1718 }
1719}
1720
1721#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1723#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1724pub struct MobAppendSystemContextResult {
1725 pub mob_id: String,
1726 pub agent_identity: String,
1727 pub status: WireAppendSystemContextStatus,
1728}
1729
1730#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1732#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1733pub struct MobFlowsResult {
1734 pub mob_id: String,
1735 pub flows: Vec<String>,
1736}
1737
1738#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1740#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1741#[serde(deny_unknown_fields)]
1742pub struct MobFlowRunParams {
1743 pub mob_id: String,
1744 pub flow_id: String,
1745 #[serde(default)]
1746 pub params: Value,
1747}
1748
1749#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1754#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1755#[serde(deny_unknown_fields)]
1756pub struct MobRunParams {
1757 pub mob_id: String,
1758 #[serde(default, skip_serializing_if = "Option::is_none")]
1759 pub flow_id: Option<String>,
1760 #[serde(default, skip_serializing_if = "Option::is_none")]
1761 pub prompt: Option<String>,
1762 #[serde(default)]
1763 pub params: Value,
1764}
1765
1766#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1768#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1769pub struct MobFlowRunResult {
1770 pub run_id: String,
1771}
1772
1773#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1775#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1776#[serde(deny_unknown_fields)]
1777pub struct MobFlowStatusParams {
1778 pub mob_id: String,
1779 pub run_id: String,
1780}
1781
1782#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1786#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1787#[serde(rename_all = "snake_case")]
1788pub enum WireMobRunStatus {
1789 Pending,
1790 Running,
1791 Completed,
1792 Failed,
1793 Canceled,
1794}
1795
1796#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1804#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1805pub struct WireMobRun {
1806 pub run_id: String,
1807 pub mob_id: String,
1808 pub flow_id: String,
1809 pub status: WireMobRunStatus,
1810 #[serde(flatten)]
1813 pub kernel: serde_json::Map<String, Value>,
1814}
1815
1816#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1820#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1821pub struct MobFlowStatusResult {
1822 #[serde(default, skip_serializing_if = "Option::is_none")]
1823 pub run: Option<WireMobRun>,
1824}
1825
1826#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1828#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1829#[serde(deny_unknown_fields)]
1830pub struct MobRunResultParams {
1831 pub mob_id: String,
1832 pub run_id: String,
1833}
1834
1835#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1837#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1838#[serde(deny_unknown_fields)]
1839pub struct WireMobRunResultEnvelope {
1840 pub run_id: String,
1841 pub mob_id: String,
1842 pub flow_id: String,
1843 pub status: WireMobRunStatus,
1844 #[serde(default, skip_serializing_if = "Option::is_none")]
1845 pub result: Option<Value>,
1846 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1847 pub outputs: BTreeMap<String, Value>,
1848}
1849
1850#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1854#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1855pub struct MobRunResult {
1856 #[serde(default, skip_serializing_if = "Option::is_none")]
1857 pub run: Option<WireMobRunResultEnvelope>,
1858}
1859
1860#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1862#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1863#[serde(deny_unknown_fields)]
1864pub struct MobFlowCancelParams {
1865 pub mob_id: String,
1866 pub run_id: String,
1867}
1868
1869#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1871#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1872pub struct MobFlowCancelResult {
1873 pub canceled: bool,
1874}
1875
1876#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1878#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1879#[serde(deny_unknown_fields)]
1880pub struct MobSpawnHelperParams {
1881 pub mob_id: String,
1882 pub prompt: String,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub agent_identity: Option<String>,
1885 #[serde(default, skip_serializing_if = "Option::is_none")]
1886 pub role_name: Option<String>,
1887 #[serde(default, skip_serializing_if = "Option::is_none")]
1888 pub model_override: Option<String>,
1889 #[serde(default, skip_serializing_if = "Option::is_none")]
1890 pub auth_binding: Option<WireAuthBindingRef>,
1891 #[serde(default, skip_serializing_if = "Option::is_none")]
1892 pub runtime_mode: Option<WireMobRuntimeMode>,
1893 #[serde(default, skip_serializing_if = "Option::is_none")]
1894 pub backend: Option<WireMobBackendKind>,
1895}
1896
1897#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1899#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1900#[serde(deny_unknown_fields)]
1901pub struct MobForkHelperParams {
1902 pub mob_id: String,
1903 pub source_member_id: String,
1904 pub prompt: String,
1905 #[serde(default, skip_serializing_if = "Option::is_none")]
1906 pub agent_identity: Option<String>,
1907 #[serde(default, skip_serializing_if = "Option::is_none")]
1908 pub role_name: Option<String>,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub model_override: Option<String>,
1911 #[serde(default, skip_serializing_if = "Option::is_none")]
1912 pub auth_binding: Option<WireAuthBindingRef>,
1913 #[serde(default, skip_serializing_if = "Option::is_none")]
1914 pub fork_context: Option<Value>,
1915 #[serde(default, skip_serializing_if = "Option::is_none")]
1916 pub runtime_mode: Option<WireMobRuntimeMode>,
1917 #[serde(default, skip_serializing_if = "Option::is_none")]
1918 pub backend: Option<WireMobBackendKind>,
1919}
1920
1921#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1923#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1924pub struct MobHelperResult {
1925 #[serde(default, skip_serializing_if = "Option::is_none")]
1926 pub output: Option<String>,
1927 pub tokens_used: u64,
1928 pub agent_identity: String,
1929 pub member_ref: WireMemberRef,
1930}
1931
1932#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1934#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1935pub struct MobForceCancelResult {
1936 pub cancelled: bool,
1937}
1938
1939#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1947#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1948#[serde(deny_unknown_fields)]
1949pub struct MobTurnStartParams {
1950 pub mob_id: String,
1951 pub agent_identity: String,
1952 pub prompt: WireContentInput,
1953 #[serde(default, skip_serializing_if = "Option::is_none")]
1954 pub skill_refs: Option<Vec<meerkat_core::skills::SkillRef>>,
1955 #[serde(default, skip_serializing_if = "Option::is_none")]
1956 pub turn_tool_overlay: Option<meerkat_core::service::PublicTurnToolOverlay>,
1957 #[serde(default, skip_serializing_if = "Option::is_none")]
1958 pub additional_instructions: Option<Vec<String>>,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub keep_alive: Option<bool>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub model: Option<String>,
1963 #[serde(default, skip_serializing_if = "Option::is_none")]
1964 pub provider: Option<String>,
1965 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub max_tokens: Option<u32>,
1967 #[serde(default, skip_serializing_if = "Option::is_none")]
1968 pub system_prompt: Option<String>,
1969 #[serde(default, skip_serializing_if = "Option::is_none")]
1970 pub output_schema: Option<Value>,
1971 #[serde(default, skip_serializing_if = "Option::is_none")]
1972 pub structured_output_retries: Option<u32>,
1973 #[serde(default, skip_serializing_if = "Option::is_none")]
1974 pub provider_params:
1975 Option<WireTurnMetadataOverride<crate::wire::runtime::WireProviderParamsOverride>>,
1976 #[serde(default, skip_serializing_if = "Option::is_none")]
1977 pub auth_binding: Option<WireTurnMetadataOverride<WireAuthBindingRef>>,
1978 #[serde(default, skip_serializing_if = "Option::is_none")]
1983 pub injected_context: Option<Vec<WireContentInput>>,
1984}
1985
1986#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1988#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1989pub struct WireUnreachablePeer {
1990 pub peer: String,
1991 #[serde(default, skip_serializing_if = "Option::is_none")]
1992 pub reason: Option<String>,
1993}
1994
1995#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1998#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1999pub struct WirePeerConnectivitySnapshot {
2000 pub reachable_peer_count: usize,
2001 pub unknown_peer_count: usize,
2002 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2003 pub unreachable_peers: Vec<WireUnreachablePeer>,
2004}
2005
2006#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2015#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2016#[serde(tag = "status", rename_all = "snake_case")]
2017pub enum WirePeerConnectivity {
2018 NotApplicable,
2021 ProbeTimedOut,
2023 Known {
2025 snapshot: WirePeerConnectivitySnapshot,
2026 },
2027}
2028
2029#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2031#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2032pub struct MobMemberStatusResult {
2033 pub status: WireMobMemberStatus,
2034 pub member_ref: WireMemberRef,
2036 #[serde(default, skip_serializing_if = "Option::is_none")]
2037 pub output_preview: Option<String>,
2038 #[serde(default, skip_serializing_if = "Option::is_none")]
2039 pub error: Option<String>,
2040 pub tokens_used: u64,
2041 pub is_final: bool,
2042 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 pub current_session_id: Option<String>,
2044 #[serde(default, skip_serializing_if = "Option::is_none")]
2045 pub peer_connectivity: Option<WirePeerConnectivity>,
2046 #[serde(default, skip_serializing_if = "Option::is_none")]
2047 pub kickoff: Option<Value>,
2048 #[serde(default, skip_serializing_if = "Option::is_none")]
2049 pub external_member: Option<Value>,
2050 #[serde(default, skip_serializing_if = "Option::is_none")]
2051 pub resolved_capabilities: Option<crate::wire::WireResolvedModelCapabilities>,
2052 #[serde(default, skip_serializing_if = "Option::is_none")]
2053 pub progress: Option<WireMemberProgressSnapshot>,
2054}
2055
2056#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2057#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2058#[serde(rename_all = "snake_case")]
2059pub enum WireMemberRunState {
2060 Idle,
2061 RunOpen,
2062 Unknown,
2063}
2064
2065#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2066#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2067#[serde(rename_all = "snake_case")]
2068pub enum WireMemberHealthClass {
2069 Healthy,
2070 Degraded,
2071 Wedged,
2072 Unknown,
2073}
2074
2075#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2076#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2077#[serde(rename_all = "snake_case")]
2078pub enum WireMemberProgressEvent {
2079 ExecutionAdvanced,
2080 BecameIdle,
2081 Unchanged,
2082}
2083
2084#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2085#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2086pub struct WireMemberProgressSnapshot {
2087 pub run_state: WireMemberRunState,
2088 pub in_flight_work: u64,
2089 pub last_progress_at_ms: u64,
2090 pub last_progress_event: WireMemberProgressEvent,
2091 pub health: WireMemberHealthClass,
2092}
2093
2094#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2096#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2097pub struct MobSnapshotResult {
2098 pub mob_id: String,
2099 pub status: WireMobLifecycleStatus,
2100 pub members: Vec<MobMemberListEntryWire>,
2101}
2102
2103#[cfg(test)]
2104mod member_status_capability_tests {
2105 use super::*;
2106
2107 #[test]
2108 fn member_status_result_round_trips_resolved_capabilities() -> Result<(), serde_json::Error> {
2109 let capabilities = crate::wire::WireResolvedModelCapabilities {
2110 vision: true,
2111 image_input: true,
2112 image_tool_results: false,
2113 inline_video: false,
2114 realtime: true,
2115 web_search: true,
2116 image_generation: true,
2117 };
2118 let result = MobMemberStatusResult {
2119 status: WireMobMemberStatus::Active,
2120 member_ref: WireMemberRef::encode("mob-1", "worker-1"),
2121 output_preview: None,
2122 error: None,
2123 tokens_used: 0,
2124 is_final: false,
2125 current_session_id: Some("session-1".to_string()),
2126 peer_connectivity: Some(WirePeerConnectivity::Known {
2127 snapshot: WirePeerConnectivitySnapshot {
2128 reachable_peer_count: 1,
2129 unknown_peer_count: 0,
2130 unreachable_peers: Vec::new(),
2131 },
2132 }),
2133 kickoff: None,
2134 external_member: None,
2135 resolved_capabilities: Some(capabilities.clone()),
2136 progress: None,
2137 };
2138
2139 let json = serde_json::to_string(&result)?;
2140 assert!(json.contains("\"resolved_capabilities\""));
2141 let parsed: MobMemberStatusResult = serde_json::from_str(&json)?;
2142 assert_eq!(parsed.resolved_capabilities, Some(capabilities));
2143 Ok(())
2144 }
2145}
2146
2147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2149#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2150pub struct MobDestroyResult {
2151 pub mob_id: String,
2152 pub ok: bool,
2153 pub destroy_report: Value,
2154}
2155
2156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2158#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2159pub struct MobRotateSupervisorResult {
2160 pub mob_id: String,
2161 pub ok: bool,
2162 pub report: SupervisorRotationReportWire,
2163}
2164
2165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2167#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2168pub struct SupervisorRotationReportWire {
2169 pub previous_epoch: u64,
2170 pub current_epoch: u64,
2171 pub public_peer_id: String,
2172}
2173
2174#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2177#[serde(rename_all = "snake_case")]
2178pub enum SupervisorRotationIncompleteKind {
2179 SupervisorRotationIncomplete,
2180}
2181
2182#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2184#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2185#[serde(rename_all = "snake_case")]
2186pub enum SupervisorRotationRetryAuthority {
2187 PendingRotation,
2188 PreRotation,
2189}
2190
2191#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
2193#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2194#[serde(rename_all = "snake_case")]
2195pub enum SupervisorRotationRetryScope {
2196 Durable,
2197 PreRotation,
2198}
2199
2200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2202#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2203#[serde(rename_all = "snake_case")]
2204pub struct SupervisorRotationIncompleteDetailsWire {
2205 pub kind: SupervisorRotationIncompleteKind,
2206 pub previous_epoch: u64,
2207 pub attempted_epoch: u64,
2208 pub attempted_public_peer_id: String,
2209 pub rotated_peer_count: usize,
2210 pub rollback_succeeded: bool,
2211 pub pending_authority_recorded: bool,
2212 #[serde(default, skip_serializing_if = "Option::is_none")]
2213 pub rollback_error: Option<String>,
2214 pub retry_authority: SupervisorRotationRetryAuthority,
2215 pub retry_scope: SupervisorRotationRetryScope,
2216}
2217
2218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2220#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2221#[serde(rename_all = "snake_case")]
2222pub struct SupervisorRotationIncompleteDataWire {
2223 pub code: String,
2224 pub message: String,
2225 pub details: SupervisorRotationIncompleteDetailsWire,
2226}
2227
2228#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2230#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2231#[serde(deny_unknown_fields)]
2232pub struct MobWaitParams {
2233 pub mob_id: String,
2234 #[serde(default, skip_serializing_if = "Option::is_none")]
2235 pub member_ids: Option<Vec<String>>,
2236 #[serde(default, skip_serializing_if = "Option::is_none")]
2237 pub timeout_ms: Option<u64>,
2238}
2239
2240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2242#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2243pub struct MobWaitMembersResult {
2244 pub members: Vec<Value>,
2245}
2246
2247#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2249#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2250pub struct MobCancelWorkResult {
2251 pub mob_id: String,
2252 pub ok: bool,
2253}
2254
2255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2257#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2258pub struct MobCancelAllWorkResult {
2259 pub mob_id: String,
2260 pub ok: bool,
2261}
2262
2263#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2265#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2266#[serde(deny_unknown_fields)]
2267pub struct MobProfileCreateParams {
2268 pub name: String,
2269 pub profile: MobProfileInput,
2270}
2271
2272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2274#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2275#[serde(deny_unknown_fields)]
2276pub struct MobProfileNameParams {
2277 pub name: String,
2278}
2279
2280#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2282#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2283#[serde(deny_unknown_fields)]
2284pub struct MobProfileUpdateParams {
2285 pub name: String,
2286 pub profile: MobProfileInput,
2287 pub expected_revision: u64,
2288}
2289
2290#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2292#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2293#[serde(deny_unknown_fields)]
2294pub struct MobProfileDeleteParams {
2295 pub name: String,
2296 pub expected_revision: u64,
2297}
2298
2299#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2301#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2302pub struct MobProfileLookupResult {
2303 #[serde(default)]
2304 pub not_found: bool,
2305 pub name: String,
2306 #[serde(default, skip_serializing_if = "Option::is_none")]
2307 pub profile: Option<WireMobProfile>,
2308 #[serde(default, skip_serializing_if = "Option::is_none")]
2309 pub revision: Option<u64>,
2310 #[serde(default, skip_serializing_if = "Option::is_none")]
2311 pub created_at: Option<String>,
2312 #[serde(default, skip_serializing_if = "Option::is_none")]
2313 pub updated_at: Option<String>,
2314}
2315
2316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2318#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2319pub struct MobProfileListResult {
2320 pub profiles: Vec<MobProfileLookupResult>,
2321}
2322
2323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2325#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2326pub struct MobProfileDeleteResult {
2327 pub name: String,
2328 pub deleted_revision: u64,
2329}
2330
2331#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2333#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2334#[serde(deny_unknown_fields)]
2335pub struct MobStreamOpenParams {
2336 pub mob_id: String,
2337 #[serde(default, skip_serializing_if = "Option::is_none")]
2338 pub agent_identity: Option<String>,
2339}
2340
2341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2343#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2344pub struct MobStreamOpenResult {
2345 pub stream_id: String,
2346 pub opened: bool,
2347}
2348
2349#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2352#[serde(deny_unknown_fields)]
2353pub struct MobStreamCloseParams {
2354 pub stream_id: String,
2355}
2356
2357#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2359#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2360pub struct MobStreamCloseResult {
2361 pub stream_id: String,
2362 pub closed: bool,
2363 pub already_closed: bool,
2364}
2365
2366#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
2369#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2370#[serde(rename_all = "snake_case")]
2371pub enum WireWorkOrigin {
2372 #[default]
2373 External,
2374 Internal,
2375}
2376
2377#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2384#[serde(deny_unknown_fields)]
2385pub struct MobSubmitWorkParams {
2386 pub member_ref: WireMemberRef,
2387 #[serde(default, skip_serializing_if = "Option::is_none")]
2390 pub work_ref: Option<String>,
2391 pub content: WireContentInput,
2392 #[serde(default)]
2393 pub origin: WireWorkOrigin,
2394 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub injected_context: Option<Vec<WireContentInput>>,
2401 #[serde(default, skip_serializing_if = "Option::is_none")]
2403 pub objective_id: Option<String>,
2404}
2405
2406#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2408#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2409pub struct MobSubmitWorkResult {
2410 pub mob_id: String,
2411 pub work_ref: String,
2412 pub member_ref: WireMemberRef,
2413 #[serde(default, skip_serializing_if = "Option::is_none")]
2414 pub objective_id: Option<String>,
2415}
2416
2417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2419#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2420#[serde(deny_unknown_fields)]
2421pub struct MobConcludeObjectiveParams {
2422 pub member_ref: WireMemberRef,
2423 pub objective_id: String,
2424 pub outcome: String,
2425}
2426
2427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2428#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2429pub struct MobConcludeObjectiveResult {
2430 pub member_ref: WireMemberRef,
2431 pub objective_id: String,
2432 pub concluded: bool,
2433}
2434
2435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2437#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2438#[serde(deny_unknown_fields)]
2439pub struct MobCancelWorkParams {
2440 pub mob_id: String,
2441 pub work_ref: String,
2442}
2443
2444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2446#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2447#[serde(deny_unknown_fields)]
2448pub struct MobCancelAllWorkParams {
2449 pub member_ref: WireMemberRef,
2450}
2451
2452#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
2455#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2456#[serde(deny_unknown_fields)]
2457pub struct MobMemberFilterWire {
2458 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
2460 pub labels: BTreeMap<String, String>,
2461 #[serde(default, skip_serializing_if = "Option::is_none")]
2463 pub role: Option<String>,
2464 #[serde(default, skip_serializing_if = "Option::is_none")]
2466 pub status: Option<WireMobMemberStatus>,
2467}
2468
2469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2471#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2472#[serde(deny_unknown_fields)]
2473pub struct MobListMembersMatchingParams {
2474 pub mob_id: String,
2475 #[serde(default)]
2476 pub filter: MobMemberFilterWire,
2477}
2478
2479#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2482#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2483pub struct MobListMembersMatchingResult {
2484 #[serde(default)]
2485 pub members: Vec<Value>,
2486}
2487
2488#[cfg(test)]
2489#[allow(clippy::expect_used, clippy::panic)]
2490mod tests {
2491 use super::*;
2492
2493 #[test]
2494 fn mob_helper_params_carry_structural_auth_binding() {
2495 let parsed: MobSpawnHelperParams = serde_json::from_value(serde_json::json!({
2496 "mob_id": "mob-1",
2497 "prompt": "help",
2498 "agent_identity": "helper",
2499 "auth_binding": {
2500 "realm": "dev",
2501 "binding": "default_anthropic",
2502 "profile": "console"
2503 }
2504 }))
2505 .expect("spawn helper params parse");
2506 let auth_binding = parsed.auth_binding.expect("auth_binding should parse");
2507 assert_eq!(auth_binding.realm.as_str(), "dev");
2508 assert_eq!(auth_binding.binding.as_str(), "default_anthropic");
2509 assert_eq!(
2510 auth_binding
2511 .profile
2512 .as_ref()
2513 .map(|profile| profile.as_str()),
2514 Some("console")
2515 );
2516
2517 let parsed: MobForkHelperParams = serde_json::from_value(serde_json::json!({
2518 "mob_id": "mob-1",
2519 "source_member_id": "source",
2520 "prompt": "help",
2521 "agent_identity": "helper",
2522 "auth_binding": {
2523 "realm": "dev",
2524 "binding": "default_anthropic"
2525 }
2526 }))
2527 .expect("fork helper params parse");
2528 let auth_binding = parsed.auth_binding.expect("auth_binding should parse");
2529 assert_eq!(auth_binding.realm.as_str(), "dev");
2530 assert_eq!(auth_binding.binding.as_str(), "default_anthropic");
2531 assert!(auth_binding.profile.is_none());
2532 }
2533
2534 #[test]
2535 fn wire_mob_profile_parses_provider_fields_fail_closed() {
2536 let legacy: WireMobProfile =
2538 serde_json::from_str(r#"{"model":"claude-opus-4-8"}"#).expect("legacy profile parses");
2539 assert_eq!(legacy.provider, None);
2540 assert!(legacy.resume_overrides.is_empty());
2541
2542 let full: WireMobProfile = serde_json::from_str(
2544 r#"{
2545 "model": "claude-internal-preview",
2546 "provider": "anthropic",
2547 "image_generation_provider": "gemini",
2548 "auto_compact_threshold": 60000,
2549 "resume_overrides": ["model", "provider"]
2550 }"#,
2551 )
2552 .expect("typed profile parses");
2553 assert_eq!(full.provider, Some(meerkat_core::Provider::Anthropic));
2554 assert_eq!(
2555 full.image_generation_provider,
2556 Some(meerkat_core::Provider::Gemini)
2557 );
2558 assert_eq!(
2559 full.resume_overrides,
2560 vec![
2561 WireMobResumeOverrideField::Model,
2562 WireMobResumeOverrideField::Provider
2563 ]
2564 );
2565
2566 assert!(
2568 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","provider":"not-a-provider"}"#)
2569 .is_err(),
2570 "unknown provider names must fail closed at the wire boundary"
2571 );
2572 assert!(
2573 serde_json::from_str::<WireMobProfile>(r#"{"model":"m","auto_compact_threshold":0}"#)
2574 .is_err(),
2575 "zero auto_compact_threshold must fail closed at the wire boundary"
2576 );
2577 assert!(
2578 serde_json::from_str::<WireMobProfile>(
2579 r#"{"model":"m","resume_overrides":["everything"]}"#
2580 )
2581 .is_err(),
2582 "resume_overrides vocabulary is closed"
2583 );
2584 }
2585
2586 #[test]
2587 fn mob_definition_input_parses_custom_models() {
2588 let input: MobDefinitionInput = serde_json::from_str(
2589 r#"{
2590 "id": "m",
2591 "profiles": {"worker": {"model": "claude-internal-preview"}},
2592 "models": {
2593 "claude-internal-preview": {
2594 "provider": "anthropic",
2595 "context_window": 500000,
2596 "vision": true
2597 }
2598 },
2599 "image_generation_provider": "openai"
2600 }"#,
2601 )
2602 .expect("definition with custom models parses");
2603 let model = input
2604 .models
2605 .get("claude-internal-preview")
2606 .expect("custom model present");
2607 assert_eq!(model.provider, meerkat_core::Provider::Anthropic);
2608 assert_eq!(model.context_window, Some(500_000));
2609 assert_eq!(model.vision, Some(true));
2610 assert_eq!(
2611 input.image_generation_provider,
2612 Some(meerkat_core::Provider::OpenAI)
2613 );
2614 }
2615
2616 #[test]
2617 fn wire_member_ref_round_trips_through_encode_decode() {
2618 let token = WireMemberRef::encode("mob-42", "worker-1");
2619 let (mob_id, agent_identity) = token.decode().expect("decode round-trips");
2620 assert_eq!(mob_id, "mob-42");
2621 assert_eq!(agent_identity, "worker-1");
2622 }
2623
2624 #[test]
2625 fn wire_member_ref_rejects_malformed_token() {
2626 let err = WireMemberRef::from_token("not-a-token-payload")
2627 .decode()
2628 .expect_err("malformed tokens must fail to decode");
2629 assert!(matches!(err, WireMemberRefError::Malformed));
2630 }
2631
2632 #[test]
2633 fn mob_member_spec_exposes_shared_surface_metadata() {
2634 let spec = MobMemberSpecWire {
2635 profile: "worker".into(),
2636 agent_identity: "w1".into(),
2637 initial_message: None,
2638 runtime_mode: None,
2639 backend: None,
2640 binding: None,
2641 context: Some(serde_json::json!({"client_ref": "member-card"})),
2642 labels: Some(BTreeMap::from([("client.member_id".into(), "w1".into())])),
2643 additional_instructions: None,
2644 auto_wire_parent: None,
2645 };
2646
2647 let metadata = spec.surface_metadata();
2648 assert_eq!(
2649 metadata.labels.get("client.member_id").map(String::as_str),
2650 Some("w1")
2651 );
2652 assert_eq!(
2653 metadata.app_context,
2654 Some(serde_json::json!({"client_ref": "member-card"}))
2655 );
2656 }
2657
2658 #[test]
2659 fn mob_member_spec_surface_metadata_rejects_reserved_keys() {
2660 let spec = MobMemberSpecWire {
2661 profile: "worker".into(),
2662 agent_identity: "w1".into(),
2663 initial_message: None,
2664 runtime_mode: None,
2665 backend: None,
2666 binding: None,
2667 context: None,
2668 labels: Some(BTreeMap::from([("mob_id".into(), "spoof".into())])),
2669 additional_instructions: None,
2670 auto_wire_parent: None,
2671 };
2672
2673 assert!(spec.validate_public_surface_metadata().is_err());
2674 }
2675
2676 #[test]
2677 fn mob_reconcile_failure_stage_is_typed_wire_enum() {
2678 let failure = MobReconcileFailureWire {
2679 agent_identity: "worker-1".into(),
2680 stage: WireMobReconcileStage::Spawn,
2681 error: WireMobError {
2682 code: MobSpawnManyFailureCause::ProfileNotFound,
2683 message: "spawn failed".into(),
2684 },
2685 };
2686
2687 let json = serde_json::to_value(&failure).expect("serialize failure");
2688 assert_eq!(json["stage"], "spawn");
2689 assert_eq!(json["error"]["code"], "profile_not_found");
2690 assert_eq!(json["error"]["message"], "spawn failed");
2691
2692 let round_trip: MobReconcileFailureWire =
2693 serde_json::from_value(json).expect("deserialize failure");
2694 assert_eq!(round_trip.stage, WireMobReconcileStage::Spawn);
2695 assert_eq!(
2696 round_trip.error.code,
2697 MobSpawnManyFailureCause::ProfileNotFound
2698 );
2699
2700 let err = serde_json::from_value::<MobReconcileFailureWire>(serde_json::json!({
2701 "agent_identity": "worker-1",
2702 "stage": "restart",
2703 "error": { "code": "profile_not_found", "message": "bad stage" }
2704 }))
2705 .expect_err("unknown reconcile stage must be rejected");
2706 assert!(err.to_string().contains("unknown variant"));
2707 }
2708
2709 #[test]
2710 fn mob_lifecycle_params_reject_unknown_action_string() {
2711 let err = serde_json::from_value::<MobLifecycleParams>(serde_json::json!({
2712 "mob_id": "mob-1",
2713 "action": "explode"
2714 }))
2715 .expect_err("unknown lifecycle actions must fail at the typed wire boundary");
2716
2717 assert!(
2718 err.to_string().contains("unknown variant"),
2719 "unexpected error: {err}"
2720 );
2721 }
2722
2723 #[test]
2724 fn mob_lifecycle_result_round_trips_typed_action() {
2725 let result = MobLifecycleResult {
2726 mob_id: "mob-1".into(),
2727 action: WireMobLifecycleAction::Complete,
2728 ok: true,
2729 destroy_report: None,
2730 };
2731
2732 let json = serde_json::to_value(&result).expect("serialize lifecycle result");
2733 assert_eq!(json["action"], "complete");
2734
2735 let round_trip: MobLifecycleResult =
2736 serde_json::from_value(json).expect("deserialize lifecycle result");
2737 assert_eq!(round_trip.action, WireMobLifecycleAction::Complete);
2738 }
2739
2740 #[test]
2741 fn mob_wire_members_batch_contract_is_local_edge_native() {
2742 let params: MobWireMembersBatchParams = serde_json::from_value(serde_json::json!({
2743 "mob_id": "mob-1",
2744 "edges": [
2745 { "a": "lead", "b": "worker-b" },
2746 { "a": "worker-a", "b": "lead" }
2747 ]
2748 }))
2749 .expect("batch wire params deserialize");
2750
2751 assert_eq!(params.mob_id, "mob-1");
2752 assert_eq!(params.edges.len(), 2);
2753 assert_eq!(params.edges[0].a, "lead");
2754 assert_eq!(params.edges[0].b, "worker-b");
2755
2756 let result = MobWireMembersBatchResult {
2757 requested: 2,
2758 wired: vec![MobWireMembersBatchEdge {
2759 a: "lead".into(),
2760 b: "worker-a".into(),
2761 }],
2762 already_wired: vec![MobWireMembersBatchEdge {
2763 a: "lead".into(),
2764 b: "worker-b".into(),
2765 }],
2766 };
2767 let json = serde_json::to_value(&result).expect("serialize batch wire result");
2768 assert_eq!(json["requested"], 2);
2769 assert_eq!(json["wired"][0]["a"], "lead");
2770 assert_eq!(json["already_wired"][0]["b"], "worker-b");
2771
2772 let err = serde_json::from_value::<MobWireMembersBatchParams>(serde_json::json!({
2773 "mob_id": "mob-1",
2774 "edges": [{ "member": "lead", "peer": "worker-a" }]
2775 }))
2776 .expect_err("mixed local/external mob/wire shape must not deserialize");
2777 let message = err.to_string();
2778 assert!(
2779 message.contains("unknown field `member`") || message.contains("missing field `a`"),
2780 "unexpected error: {message}"
2781 );
2782 }
2783
2784 #[test]
2785 fn mob_spawn_many_result_entry_uses_typed_status_result_envelope() {
2786 let member_ref = WireMemberRef::encode("mob-1", "worker-1");
2787 let entry = MobSpawnManyResultEntry::spawned("worker-1", member_ref.clone());
2788
2789 let json = serde_json::to_value(&entry).expect("serialize typed spawn_many row");
2790 assert_eq!(json["status"], "spawned");
2791 assert_eq!(json["result"]["agent_identity"], "worker-1");
2792 assert_eq!(json["result"]["member_ref"], member_ref.as_str());
2793 assert!(json.get("ok").is_none());
2794 assert!(json.get("error").is_none());
2795
2796 let round_trip: MobSpawnManyResultEntry =
2797 serde_json::from_value(json).expect("deserialize typed spawn_many row");
2798 assert_eq!(round_trip, entry);
2799
2800 let failed = MobSpawnManyResultEntry::failed(
2801 MobSpawnManyFailureCause::ProfileNotFound,
2802 "profile missing",
2803 );
2804 let json = serde_json::to_value(&failed).expect("serialize typed failed spawn_many row");
2805 assert_eq!(json["status"], "failed");
2806 assert_eq!(json["result"]["cause"], "profile_not_found");
2807 assert_eq!(json["result"]["message"], "profile missing");
2808 assert!(json.get("ok").is_none());
2809 assert!(json.get("error").is_none());
2810
2811 let round_trip: MobSpawnManyResultEntry =
2812 serde_json::from_value(json).expect("deserialize typed failed spawn_many row");
2813 assert_eq!(round_trip, failed);
2814 }
2815
2816 #[test]
2817 fn mob_spawn_many_result_entry_rejects_legacy_or_malformed_envelopes() {
2818 let legacy = serde_json::json!({
2819 "ok": true,
2820 "agent_identity": "worker-1",
2821 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2822 });
2823 let err = serde_json::from_value::<MobSpawnManyResultEntry>(legacy)
2824 .expect_err("legacy ok carrier must not deserialize");
2825 assert!(
2826 err.to_string().contains("missing field `status`")
2827 || err.to_string().contains("unknown field"),
2828 "unexpected error: {err}"
2829 );
2830
2831 let missing_result = serde_json::json!({
2832 "status": "spawned"
2833 });
2834 let err = serde_json::from_value::<MobSpawnManyResultEntry>(missing_result)
2835 .expect_err("missing typed result must fail closed");
2836 assert!(
2837 err.to_string().contains("missing field `result`"),
2838 "unexpected error: {err}"
2839 );
2840
2841 let unknown_status = serde_json::json!({
2842 "status": "ok",
2843 "result": {
2844 "agent_identity": "worker-1",
2845 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2846 }
2847 });
2848 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_status)
2849 .expect_err("unknown typed status must fail closed");
2850 assert!(
2851 err.to_string().contains("unknown variant"),
2852 "unexpected error: {err}"
2853 );
2854
2855 let mismatched = serde_json::json!({
2856 "status": "spawned",
2857 "result": {
2858 "cause": "profile_not_found",
2859 "message": "profile missing"
2860 }
2861 });
2862 let err = serde_json::from_value::<MobSpawnManyResultEntry>(mismatched)
2863 .expect_err("status/result mismatch must fail closed");
2864 assert!(
2865 err.to_string()
2866 .contains("status spawned requires spawned result"),
2867 "unexpected error: {err}"
2868 );
2869
2870 let message_only_failure = serde_json::json!({
2871 "status": "failed",
2872 "result": {
2873 "message": "profile missing"
2874 }
2875 });
2876 let err = serde_json::from_value::<MobSpawnManyResultEntry>(message_only_failure)
2877 .expect_err("string-only failure result must fail closed");
2878 assert!(
2879 err.to_string().contains("data did not match any variant")
2880 || err.to_string().contains("missing field `cause`"),
2881 "unexpected error: {err}"
2882 );
2883
2884 let unknown_failure_cause = serde_json::json!({
2885 "status": "failed",
2886 "result": {
2887 "cause": "future_failure",
2888 "message": "future failure"
2889 }
2890 });
2891 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_failure_cause)
2892 .expect_err("unknown failure cause must fail closed");
2893 assert!(
2894 err.to_string().contains("data did not match any variant")
2895 || err.to_string().contains("unknown variant"),
2896 "unexpected error: {err}"
2897 );
2898 }
2899
2900 #[test]
2901 fn mob_wire_params_reject_legacy_local_target_shape() {
2902 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2903 "mob_id": "mob-1",
2904 "local": "member-a",
2905 "target": { "local": "member-b" }
2906 }))
2907 .expect_err("legacy local/target shape must be rejected");
2908
2909 let msg = err.to_string();
2910 assert!(
2911 msg.contains("unknown field `local`") || msg.contains("missing field `member`"),
2912 "unexpected error: {msg}"
2913 );
2914 }
2915
2916 #[test]
2917 fn mob_wire_params_accept_canonical_external_peer_identity() {
2918 let params = serde_json::from_value::<MobWireParams>(serde_json::json!({
2919 "mob_id": "mob-1",
2920 "member": "member-a",
2921 "peer": {
2922 "external": {
2923 "name": "external-worker",
2924 "address": "inproc://external-worker",
2925 "identity": {
2926 "kind": "ed25519_public_key",
2927 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2928 }
2929 }
2930 }
2931 }))
2932 .expect("canonical external peer identity should deserialize");
2933
2934 let MobPeerTarget::External(spec) = params.peer else {
2935 panic!("expected external peer target");
2936 };
2937 assert_eq!(spec.name, "external-worker");
2938 }
2939
2940 #[test]
2941 fn mob_wire_params_reject_raw_external_peer_id_shape() {
2942 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2943 "mob_id": "mob-1",
2944 "member": "member-a",
2945 "peer": {
2946 "external": {
2947 "name": "external-worker",
2948 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2949 "address": "inproc://external-worker",
2950 "pubkey": vec![7u8; 32]
2951 }
2952 }
2953 }))
2954 .expect_err("raw peer_id/pubkey external peer shape must be rejected");
2955
2956 let msg = err.to_string();
2957 assert!(
2958 msg.contains("peer_id") || msg.contains("identity"),
2959 "unexpected error: {msg}"
2960 );
2961 }
2962
2963 #[test]
2964 fn mob_wire_params_reject_missing_external_peer_pubkey_material() {
2965 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2966 "mob_id": "mob-1",
2967 "member": "member-a",
2968 "peer": {
2969 "external": {
2970 "name": "external-worker",
2971 "address": "inproc://external-worker",
2972 "identity": {
2973 "kind": "ed25519_public_key"
2974 }
2975 }
2976 }
2977 }))
2978 .expect_err("missing external peer pubkey material must fail closed");
2979
2980 let msg = err.to_string();
2981 assert!(
2982 msg.contains("public_key") || msg.contains("identity"),
2983 "unexpected error: {msg}"
2984 );
2985 }
2986
2987 #[test]
2988 fn runtime_binding_accepts_canonical_external_peer_identity() {
2989 let binding = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2990 "kind": "external",
2991 "address": "inproc://external-worker",
2992 "identity": {
2993 "kind": "ed25519_public_key",
2994 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2995 }
2996 }))
2997 .expect("canonical external runtime binding identity should deserialize");
2998
2999 let WireRuntimeBinding::External {
3000 identity, address, ..
3001 } = binding
3002 else {
3003 panic!("expected external runtime binding");
3004 };
3005 assert_eq!(address, "inproc://external-worker");
3006 assert_eq!(
3007 identity.resolve().expect("identity resolves").pubkey,
3008 [7u8; 32]
3009 );
3010 }
3011
3012 #[test]
3013 fn runtime_binding_rejects_raw_external_peer_id_shape() {
3014 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
3015 "kind": "external",
3016 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
3017 "address": "inproc://external-worker",
3018 "pubkey": vec![7u8; 32]
3019 }))
3020 .expect_err("raw peer_id/pubkey external runtime binding shape must be rejected");
3021
3022 let msg = err.to_string();
3023 assert!(
3024 msg.contains("peer_id") || msg.contains("identity"),
3025 "unexpected error: {msg}"
3026 );
3027 }
3028
3029 #[test]
3030 fn runtime_binding_rejects_missing_external_peer_pubkey_material() {
3031 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
3032 "kind": "external",
3033 "address": "inproc://external-worker",
3034 "identity": {
3035 "kind": "ed25519_public_key"
3036 }
3037 }))
3038 .expect_err("missing external runtime binding pubkey material must fail closed");
3039
3040 let msg = err.to_string();
3041 assert!(
3042 msg.contains("public_key") || msg.contains("identity"),
3043 "unexpected error: {msg}"
3044 );
3045 }
3046
3047 #[test]
3048 fn mob_turn_start_params_capture_turn_override_fields() {
3049 let params = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
3050 "mob_id": "mob-1",
3051 "agent_identity": "worker",
3052 "prompt": "continue",
3053 "output_schema": { "type": "object" },
3054 "structured_output_retries": 2
3055 }))
3056 .expect("turn_start should accept explicit turn override fields");
3057
3058 assert_eq!(params.mob_id, "mob-1");
3059 assert_eq!(params.agent_identity, "worker");
3060 assert_eq!(params.prompt, WireContentInput::Text("continue".into()));
3061 assert_eq!(
3062 params.output_schema,
3063 Some(serde_json::json!({ "type": "object" }))
3064 );
3065 assert_eq!(params.structured_output_retries, Some(2));
3066
3067 let err = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
3068 "mob_id": "mob-1",
3069 "agent_identity": "worker",
3070 "prompt": "continue",
3071 "unknown_override": true
3072 }))
3073 .expect_err("turn_start must reject unknown override fields");
3074 assert!(
3075 err.to_string().contains("unknown field"),
3076 "unexpected error: {err}"
3077 );
3078 }
3079
3080 #[test]
3081 fn mob_create_params_reject_reserved_runtime_lifecycle_fields() {
3082 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3083 "definition": {
3084 "id": "mob-1",
3085 "owner_runtime_binding": "runtime:worker:0",
3086 "profiles": {
3087 "worker": { "model": "claude-sonnet-4-6" }
3088 }
3089 }
3090 }))
3091 .expect_err("reserved runtime lifecycle fields must be rejected");
3092
3093 assert!(
3094 err.to_string()
3095 .contains("unknown field `owner_runtime_binding`"),
3096 "unexpected error: {err}"
3097 );
3098 }
3099
3100 #[test]
3101 fn mob_create_params_reject_reserved_runtime_bridge_owner_field() {
3102 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3103 "definition": {
3104 "id": "mob-1",
3105 "owner_transport_binding": "transport:worker:0",
3106 "profiles": {
3107 "worker": { "model": "claude-sonnet-4-6" }
3108 }
3109 }
3110 }))
3111 .expect_err("reserved runtime bridge owner field must be rejected");
3112
3113 assert!(
3114 err.to_string()
3115 .contains("unknown field `owner_transport_binding`"),
3116 "unexpected error: {err}"
3117 );
3118 }
3119
3120 #[test]
3121 fn mob_create_params_reject_internal_profile_tool_bundles() {
3122 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3123 "definition": {
3124 "id": "mob-1",
3125 "profiles": {
3126 "worker": {
3127 "model": "claude-sonnet-4-6",
3128 "tools": {
3129 "rust_bundles": ["internal-only"]
3130 }
3131 }
3132 }
3133 }
3134 }))
3135 .expect_err("internal rust tool bundles must be rejected");
3136
3137 assert!(
3140 err.to_string().contains("did not match any variant")
3141 || err.to_string().contains("unknown field `rust_bundles`"),
3142 "unexpected error: {err}"
3143 );
3144 }
3145
3146 #[test]
3147 fn mob_create_params_accept_typed_nested_flow_definition() {
3148 let params = serde_json::from_value::<MobCreateParams>(serde_json::json!({
3149 "definition": {
3150 "id": "mob-1",
3151 "profiles": {
3152 "worker": { "model": "claude-sonnet-4-6" }
3153 },
3154 "flows": {
3155 "review": {
3156 "description": "review flow",
3157 "steps": {
3158 "draft": {
3159 "role": "worker",
3160 "message": "draft it"
3161 }
3162 }
3163 }
3164 }
3165 }
3166 }))
3167 .expect("typed nested flow definition should parse");
3168
3169 assert_eq!(
3170 params.definition.flows["review"].steps["draft"].role,
3171 "worker"
3172 );
3173 }
3174}