1use super::connection::WireAuthBindingRef;
4use super::session::WireContentInput;
5use super::supervisor_bridge::BridgeBootstrapToken;
6use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
7use meerkat_core::OutputSchema;
8use meerkat_core::{
9 HandlingMode,
10 types::{RenderClass, RenderMetadata, RenderSalience},
11};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::collections::BTreeMap;
15
16use meerkat_core::{SurfaceMetadata, SurfaceMetadataError};
17
18#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20#[serde(rename_all = "snake_case")]
21pub enum WireMobBackendKind {
22 #[default]
23 Session,
24 External,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
34#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
35pub enum WireRuntimeBinding {
36 Session,
37 External {
38 address: String,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 bootstrap_token: Option<BridgeBootstrapToken>,
41 identity: WireTrustedPeerIdentity,
45 },
46}
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
49#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
50#[serde(rename_all = "snake_case")]
51pub enum WireMobRuntimeMode {
52 #[default]
53 AutonomousHost,
54 TurnDriven,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[serde(tag = "mode", rename_all = "snake_case")]
61pub enum WireMemberLaunchMode {
62 Fresh,
63 Resume {
64 #[serde(alias = "session_id")]
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 mob: bool,
134 #[serde(default)]
135 pub schedule: bool,
136 #[serde(default)]
137 pub image_generation: bool,
138 #[serde(default)]
139 pub mcp: Vec<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
145#[serde(deny_unknown_fields)]
146pub struct WireMobProfile {
147 pub model: String,
148 #[serde(default)]
149 pub skills: Vec<String>,
150 #[serde(default)]
151 pub tools: WireMobToolConfig,
152 #[serde(default)]
153 pub peer_description: String,
154 #[serde(default)]
155 pub external_addressable: bool,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub backend: Option<WireMobBackendKind>,
158 #[serde(default)]
159 pub runtime_mode: WireMobRuntimeMode,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub max_inline_peer_notifications: Option<i32>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub output_schema: Option<Value>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub provider_params: Option<Value>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
169#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
170#[serde(deny_unknown_fields)]
171pub struct MobOrchestratorInput {
172 pub profile: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
177#[serde(tag = "source", rename_all = "snake_case")]
178pub enum MobSkillSourceInput {
179 Inline { content: String },
180 Path { path: String },
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
185#[serde(deny_unknown_fields)]
186pub struct MobRoleWiringRuleInput {
187 pub a: String,
188 pub b: String,
189}
190
191#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
192#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
193#[serde(deny_unknown_fields)]
194pub struct MobWiringRulesInput {
195 #[serde(default)]
196 pub auto_wire_orchestrator: bool,
197 #[serde(default, skip_serializing_if = "Vec::is_empty")]
198 pub role_wiring: Vec<MobRoleWiringRuleInput>,
199}
200
201#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
202#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
203#[serde(deny_unknown_fields)]
204pub struct MobToolConfigInput {
205 #[serde(default)]
206 pub builtins: bool,
207 #[serde(default)]
208 pub shell: bool,
209 #[serde(default)]
210 pub comms: bool,
211 #[serde(default)]
212 pub memory: bool,
213 #[serde(default)]
214 pub mob: bool,
215 #[serde(default)]
216 pub schedule: bool,
217 #[serde(default)]
218 pub image_generation: bool,
219 #[serde(default, skip_serializing_if = "Vec::is_empty")]
220 pub mcp: Vec<String>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
229#[allow(clippy::large_enum_variant)]
230#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
231#[serde(untagged)]
232pub enum MobProfileBindingInput {
233 RealmRef {
235 realm_profile: String,
237 },
238 Inline(MobProfileInput),
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
244#[serde(deny_unknown_fields)]
245pub struct MobProfileInput {
246 pub model: String,
247 #[serde(default, skip_serializing_if = "Vec::is_empty")]
248 pub skills: Vec<String>,
249 #[serde(default)]
250 pub tools: MobToolConfigInput,
251 #[serde(default, skip_serializing_if = "String::is_empty")]
252 pub peer_description: String,
253 #[serde(default)]
254 pub external_addressable: bool,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub backend: Option<WireMobBackendKind>,
257 #[serde(default)]
258 pub runtime_mode: WireMobRuntimeMode,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
260 pub max_inline_peer_notifications: Option<i32>,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 pub output_schema: Option<OutputSchema>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub provider_params: Option<crate::wire::runtime::WireProviderParamsOverride>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
271#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
272#[serde(deny_unknown_fields)]
273pub struct MobExternalBackendConfigInput {
274 pub address_base: String,
275}
276
277#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
278#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
279#[serde(deny_unknown_fields)]
280pub struct MobBackendConfigInput {
281 #[serde(default)]
282 pub default: WireMobBackendKind,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub external: Option<MobExternalBackendConfigInput>,
285}
286
287#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
288#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
289#[serde(rename_all = "snake_case")]
290pub enum MobDispatchModeInput {
291 #[default]
292 FanOut,
293 OneToOne,
294 FanIn,
295}
296
297#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
298#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
299#[serde(tag = "type", rename_all = "snake_case")]
300pub enum MobCollectionPolicyInput {
301 #[default]
302 All,
303 Any,
304 Quorum {
305 n: u8,
306 },
307}
308
309#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
310#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
311#[serde(rename_all = "snake_case")]
312pub enum MobDependencyModeInput {
313 #[default]
314 All,
315 Any,
316}
317
318#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
319#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
320#[serde(rename_all = "snake_case")]
321pub enum MobStepOutputFormatInput {
322 #[default]
323 Json,
324 Text,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
328#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
329#[serde(tag = "op", rename_all = "snake_case")]
330pub enum MobConditionExprInput {
331 Eq { path: String, value: Value },
332 In { path: String, values: Vec<Value> },
333 Gt { path: String, value: Value },
334 Lt { path: String, value: Value },
335 And { exprs: Vec<MobConditionExprInput> },
336 Or { exprs: Vec<MobConditionExprInput> },
337 Not { expr: Box<MobConditionExprInput> },
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
341#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
342#[serde(deny_unknown_fields)]
343pub struct MobFrameSpecInput {
344 pub nodes: BTreeMap<String, MobFlowNodeInput>,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
348#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
349#[serde(tag = "kind", rename_all = "snake_case")]
350pub enum MobFlowNodeInput {
351 Step(MobFrameStepInput),
352 RepeatUntil(MobRepeatUntilInput),
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
356#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
357#[serde(deny_unknown_fields)]
358pub struct MobFrameStepInput {
359 pub step_id: String,
360 #[serde(default, skip_serializing_if = "Vec::is_empty")]
361 pub depends_on: Vec<String>,
362 #[serde(default)]
363 pub depends_on_mode: MobDependencyModeInput,
364 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub branch: Option<String>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
369#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
370#[serde(deny_unknown_fields)]
371pub struct MobRepeatUntilInput {
372 pub loop_id: String,
373 #[serde(default, skip_serializing_if = "Vec::is_empty")]
374 pub depends_on: Vec<String>,
375 #[serde(default)]
376 pub depends_on_mode: MobDependencyModeInput,
377 pub body: MobFrameSpecInput,
378 pub until: MobConditionExprInput,
379 pub max_iterations: u32,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384#[serde(deny_unknown_fields)]
385pub struct MobFlowStepInput {
386 pub role: String,
387 pub message: WireContentInput,
388 #[serde(default, skip_serializing_if = "Vec::is_empty")]
389 pub depends_on: Vec<String>,
390 #[serde(default)]
391 pub dispatch_mode: MobDispatchModeInput,
392 #[serde(default)]
393 pub collection_policy: MobCollectionPolicyInput,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub condition: Option<MobConditionExprInput>,
396 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub timeout_ms: Option<u64>,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub expected_schema_ref: Option<String>,
400 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub branch: Option<String>,
402 #[serde(default)]
403 pub depends_on_mode: MobDependencyModeInput,
404 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub allowed_tools: Option<Vec<String>>,
406 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub blocked_tools: Option<Vec<String>>,
408 #[serde(default)]
409 pub output_format: MobStepOutputFormatInput,
410}
411
412#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
413#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
414#[serde(deny_unknown_fields)]
415pub struct MobFlowSpecInput {
416 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub description: Option<String>,
418 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
419 pub steps: BTreeMap<String, MobFlowStepInput>,
420 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub root: Option<MobFrameSpecInput>,
422}
423
424#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
425#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
426#[serde(rename_all = "snake_case")]
427pub enum MobPolicyModeInput {
428 #[default]
429 Advisory,
430 Strict,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
434#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
435#[serde(deny_unknown_fields)]
436pub struct MobTopologyRuleInput {
437 pub from_role: String,
438 pub to_role: String,
439 pub allowed: bool,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
443#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
444#[serde(deny_unknown_fields)]
445pub struct MobTopologySpecInput {
446 pub mode: MobPolicyModeInput,
447 pub rules: Vec<MobTopologyRuleInput>,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
451#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
452#[serde(deny_unknown_fields)]
453pub struct MobSupervisorSpecInput {
454 pub role: String,
455 pub escalation_threshold: u32,
456}
457
458#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
459#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
460#[serde(deny_unknown_fields)]
461pub struct MobLimitsSpecInput {
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub max_flow_duration_ms: Option<u64>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub max_step_retries: Option<u32>,
466 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub max_orphaned_turns: Option<u32>,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub cancel_grace_timeout_ms: Option<u64>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub max_active_nodes: Option<u64>,
472 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub max_active_frames: Option<u64>,
474 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub max_frame_depth: Option<u64>,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
479#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
480#[serde(tag = "mode", rename_all = "snake_case")]
481pub enum MobSpawnPolicyInput {
482 None,
483 Auto {
484 profile_map: BTreeMap<String, String>,
485 },
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
489#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
490#[serde(deny_unknown_fields)]
491pub struct MobEventRouterConfigInput {
492 #[serde(default = "default_event_router_buffer_size")]
493 pub buffer_size: usize,
494 #[serde(default, skip_serializing_if = "Option::is_none")]
495 pub include_patterns: Option<Vec<String>>,
496 #[serde(default, skip_serializing_if = "Option::is_none")]
497 pub exclude_patterns: Option<Vec<String>>,
498}
499
500const fn default_event_router_buffer_size() -> usize {
501 256
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
513#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
514#[serde(deny_unknown_fields)]
515pub struct MobDefinitionInput {
516 pub id: String,
517 #[serde(default, skip_serializing_if = "Option::is_none")]
518 pub orchestrator: Option<MobOrchestratorInput>,
519 pub profiles: BTreeMap<String, MobProfileBindingInput>,
520 #[serde(default)]
521 pub wiring: MobWiringRulesInput,
522 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
523 pub skills: BTreeMap<String, MobSkillSourceInput>,
524 #[serde(default)]
525 pub backend: MobBackendConfigInput,
526 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
527 pub flows: BTreeMap<String, MobFlowSpecInput>,
528 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub topology: Option<MobTopologySpecInput>,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub supervisor: Option<MobSupervisorSpecInput>,
532 #[serde(default, skip_serializing_if = "Option::is_none")]
533 pub limits: Option<MobLimitsSpecInput>,
534 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub spawn_policy: Option<MobSpawnPolicyInput>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
537 pub event_router: Option<MobEventRouterConfigInput>,
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
542#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
543#[serde(deny_unknown_fields)]
544pub struct MobCreateParams {
545 pub definition: MobDefinitionInput,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
550#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
551pub struct MobCreateResult {
552 pub mob_id: String,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
557#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
558#[serde(deny_unknown_fields)]
559pub struct MobIdParams {
560 pub mob_id: String,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
565#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
566#[serde(deny_unknown_fields)]
567pub struct MobMemberParams {
568 pub mob_id: String,
569 pub agent_identity: String,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
574#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
575pub struct MobStatusResult {
576 pub mob_id: String,
577 pub status: String,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
582#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
583pub struct MobListResult {
584 pub mobs: Vec<MobStatusResult>,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
589#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
590#[serde(deny_unknown_fields)]
591pub struct MobSpawnParams {
592 pub mob_id: String,
593 pub profile: String,
594 pub agent_identity: String,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
596 pub initial_message: Option<WireContentInput>,
597 #[serde(default, skip_serializing_if = "Option::is_none")]
598 pub runtime_mode: Option<WireMobRuntimeMode>,
599 #[serde(default, skip_serializing_if = "Option::is_none")]
600 pub backend: Option<WireMobBackendKind>,
601 #[serde(default, skip_serializing_if = "Option::is_none")]
602 pub labels: Option<BTreeMap<String, String>>,
603 #[serde(default, skip_serializing_if = "Option::is_none")]
604 pub context: Option<Value>,
605 #[serde(default, skip_serializing_if = "Option::is_none")]
606 pub additional_instructions: Option<Vec<String>>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub binding: Option<WireRuntimeBinding>,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
610 pub shell_env: Option<BTreeMap<String, String>>,
611 #[serde(default, skip_serializing_if = "Option::is_none")]
612 pub auto_wire_parent: Option<bool>,
613 #[serde(default, skip_serializing_if = "Option::is_none")]
614 pub launch_mode: Option<WireMemberLaunchMode>,
615 #[serde(default, skip_serializing_if = "Option::is_none")]
616 pub tool_access_policy: Option<WireToolAccessPolicy>,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
618 pub budget_split_policy: Option<WireBudgetSplitPolicy>,
619 #[serde(default, skip_serializing_if = "Option::is_none")]
620 pub inherited_tool_filter: Option<WireToolFilter>,
621 #[serde(default, skip_serializing_if = "Option::is_none")]
622 pub override_profile: Option<WireMobProfile>,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
624 pub auth_binding: Option<WireAuthBindingRef>,
625}
626
627#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
629#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
630pub struct MobSpawnResult {
631 pub mob_id: String,
632 pub agent_identity: String,
633 pub member_ref: WireMemberRef,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
638#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
639#[serde(deny_unknown_fields)]
640pub struct MobSpawnSpecParams {
641 pub profile: String,
642 pub agent_identity: String,
643 #[serde(default, skip_serializing_if = "Option::is_none")]
644 pub initial_message: Option<WireContentInput>,
645 #[serde(default, skip_serializing_if = "Option::is_none")]
646 pub runtime_mode: Option<WireMobRuntimeMode>,
647 #[serde(default, skip_serializing_if = "Option::is_none")]
648 pub backend: Option<WireMobBackendKind>,
649 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub labels: Option<BTreeMap<String, String>>,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub context: Option<Value>,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub additional_instructions: Option<Vec<String>>,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub auth_binding: Option<WireAuthBindingRef>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
661#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
662#[serde(deny_unknown_fields)]
663pub struct MobSpawnManyParams {
664 pub mob_id: String,
665 pub specs: Vec<MobSpawnSpecParams>,
666}
667
668#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
670#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
671#[serde(rename_all = "snake_case")]
672pub enum MobSpawnManyResultStatus {
673 Spawned,
674 Failed,
675}
676
677#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
679#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
680#[serde(deny_unknown_fields)]
681pub struct MobSpawnManySpawnedResult {
682 pub agent_identity: String,
683 pub member_ref: WireMemberRef,
684}
685
686#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
688#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
689#[serde(rename_all = "snake_case")]
690pub enum MobSpawnManyFailureCause {
691 ProfileNotFound,
692 MemberNotFound,
693 MemberAlreadyExists,
694 NotExternallyAddressable,
695 InvalidTransition,
696 WiringError,
697 BridgeCommandRejected,
698 MemberRestoreFailed,
699 KickoffWaitTimedOut,
700 ReadyWaitTimedOut,
701 DefinitionError,
702 FlowNotFound,
703 FlowFailed,
704 RunNotFound,
705 RunCanceled,
706 FlowTurnTimedOut,
707 FrameDepthLimitExceeded,
708 FrameAtomicPersistenceUnavailable,
709 SpecRevisionConflict,
710 SchemaValidation,
711 InsufficientTargets,
712 TopologyViolation,
713 BridgeDeliveryRejected,
714 SupervisorEscalation,
715 UnsupportedForMode,
716 MissingMemberCapability,
717 ResetBarrier,
718 StorageError,
719 SessionError,
720 CommsError,
721 CallbackPending,
722 StaleFenceToken,
723 StaleEventCursor,
724 WorkNotFound,
725 Internal,
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
730#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
731#[serde(deny_unknown_fields)]
732pub struct MobSpawnManyFailedResult {
733 pub cause: MobSpawnManyFailureCause,
734 pub message: String,
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
739#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
740#[serde(untagged)]
741pub enum MobSpawnManyResultPayload {
742 Spawned(MobSpawnManySpawnedResult),
743 Failed(MobSpawnManyFailedResult),
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
748#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
749#[serde(try_from = "MobSpawnManyResultEntryRaw")]
750pub struct MobSpawnManyResultEntry {
751 pub status: MobSpawnManyResultStatus,
752 pub result: MobSpawnManyResultPayload,
753}
754
755#[derive(Debug, Deserialize)]
756#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
757#[serde(deny_unknown_fields)]
758struct MobSpawnManyResultEntryRaw {
759 status: MobSpawnManyResultStatus,
760 result: MobSpawnManyResultPayload,
761}
762
763impl TryFrom<MobSpawnManyResultEntryRaw> for MobSpawnManyResultEntry {
764 type Error = String;
765
766 fn try_from(raw: MobSpawnManyResultEntryRaw) -> Result<Self, Self::Error> {
767 let entry = Self {
768 status: raw.status,
769 result: raw.result,
770 };
771 entry.validate().map_err(str::to_owned)?;
772 Ok(entry)
773 }
774}
775
776impl MobSpawnManyResultEntry {
777 pub fn spawned(agent_identity: impl Into<String>, member_ref: WireMemberRef) -> Self {
778 Self {
779 status: MobSpawnManyResultStatus::Spawned,
780 result: MobSpawnManyResultPayload::Spawned(MobSpawnManySpawnedResult {
781 agent_identity: agent_identity.into(),
782 member_ref,
783 }),
784 }
785 }
786
787 pub fn failed(cause: MobSpawnManyFailureCause, message: impl Into<String>) -> Self {
788 Self {
789 status: MobSpawnManyResultStatus::Failed,
790 result: MobSpawnManyResultPayload::Failed(MobSpawnManyFailedResult {
791 cause,
792 message: message.into(),
793 }),
794 }
795 }
796
797 pub fn validate(&self) -> Result<(), &'static str> {
798 match (&self.status, &self.result) {
799 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Spawned(_))
800 | (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Failed(_)) => Ok(()),
801 (MobSpawnManyResultStatus::Spawned, MobSpawnManyResultPayload::Failed(_)) => {
802 Err("mob spawn_many result status spawned requires spawned result")
803 }
804 (MobSpawnManyResultStatus::Failed, MobSpawnManyResultPayload::Spawned(_)) => {
805 Err("mob spawn_many result status failed requires failed result")
806 }
807 }
808 }
809}
810
811#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
813#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
814pub struct MobSpawnManyResult {
815 pub results: Vec<MobSpawnManyResultEntry>,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
820#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
821pub struct MobRetireResult {
822 pub retired: bool,
823}
824
825#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
827#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
828#[serde(deny_unknown_fields)]
829pub struct MobRespawnParams {
830 pub mob_id: String,
831 pub agent_identity: String,
832 #[serde(default, skip_serializing_if = "Option::is_none")]
833 pub initial_message: Option<WireContentInput>,
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
838#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
839pub struct MobRespawnReceipt {
840 pub identity: String,
841 pub member_ref: WireMemberRef,
842}
843
844#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
846#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
847pub struct MobRespawnResult {
848 pub status: String,
849 pub receipt: MobRespawnReceipt,
850 #[serde(default, skip_serializing_if = "Vec::is_empty")]
851 pub failed_peer_ids: Vec<String>,
852}
853
854#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
856#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
857pub struct MobMembersResult {
858 pub mob_id: String,
859 pub members: Vec<MobMemberListEntryWire>,
860}
861
862#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
864#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
865#[serde(deny_unknown_fields)]
866pub struct MobEventsParams {
867 pub mob_id: String,
868 #[serde(default)]
869 pub after_cursor: u64,
870 #[serde(default = "default_mob_events_limit")]
871 pub limit: usize,
872 #[serde(default)]
873 pub strict: bool,
874}
875
876const fn default_mob_events_limit() -> usize {
877 100
878}
879
880#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
882#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
883pub struct MobEventsResult {
884 pub events: Vec<Value>,
885}
886
887#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
889#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
890#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
891pub enum WireTrustedPeerIdentity {
892 Ed25519PublicKey { public_key: String },
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
898pub struct ResolvedWireTrustedPeerIdentity {
899 pub peer_id: meerkat_core::comms::PeerId,
900 pub pubkey: [u8; 32],
901}
902
903#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
905pub enum WireTrustedPeerIdentityError {
906 #[error("external peer identity public_key must start with 'ed25519:'")]
907 MissingEd25519Prefix,
908 #[error("external peer identity public_key is not valid base64: {0}")]
909 InvalidBase64(String),
910 #[error("external peer identity public_key must decode to 32 bytes, got {actual}")]
911 InvalidLength { actual: usize },
912 #[error("external peer identity public_key must be non-zero")]
913 ZeroPublicKey,
914}
915
916impl WireTrustedPeerIdentity {
917 pub fn resolve(&self) -> Result<ResolvedWireTrustedPeerIdentity, WireTrustedPeerIdentityError> {
918 match self {
919 Self::Ed25519PublicKey { public_key } => {
920 let pubkey = parse_ed25519_public_key(public_key)?;
921 if pubkey == [0u8; 32] {
922 return Err(WireTrustedPeerIdentityError::ZeroPublicKey);
923 }
924 Ok(ResolvedWireTrustedPeerIdentity {
925 peer_id: meerkat_core::comms::PeerId::from_ed25519_pubkey(&pubkey),
926 pubkey,
927 })
928 }
929 }
930 }
931}
932
933fn parse_ed25519_public_key(raw: &str) -> Result<[u8; 32], WireTrustedPeerIdentityError> {
934 const PREFIX: &str = "ed25519:";
935 let encoded = raw
936 .strip_prefix(PREFIX)
937 .ok_or(WireTrustedPeerIdentityError::MissingEd25519Prefix)?;
938 let bytes = BASE64
939 .decode(encoded)
940 .map_err(|err| WireTrustedPeerIdentityError::InvalidBase64(err.to_string()))?;
941 let actual = bytes.len();
942 let pubkey: [u8; 32] = bytes
943 .try_into()
944 .map_err(|_| WireTrustedPeerIdentityError::InvalidLength { actual })?;
945 Ok(pubkey)
946}
947
948#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
954#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
955#[serde(deny_unknown_fields)]
956pub struct WireTrustedPeerSpec {
957 pub name: String,
958 pub address: String,
959 pub identity: WireTrustedPeerIdentity,
960}
961
962#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
964#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
965#[serde(rename_all = "snake_case")]
966pub enum MobPeerTarget {
967 Local(String),
968 External(WireTrustedPeerSpec),
969}
970
971#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
973#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
974#[serde(deny_unknown_fields)]
975pub struct MobWireParams {
976 pub mob_id: String,
977 pub member: String,
978 pub peer: MobPeerTarget,
979}
980
981#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
983#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
984pub struct MobWireResult {
985 pub wired: bool,
986}
987
988#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
990#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
991#[serde(deny_unknown_fields)]
992pub struct MobUnwireParams {
993 pub mob_id: String,
994 pub member: String,
995 pub peer: MobPeerTarget,
996}
997
998#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1000#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1001pub struct MobUnwireResult {
1002 pub unwired: bool,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1007#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1008#[serde(deny_unknown_fields)]
1009pub struct MobMemberSendParams {
1010 pub mob_id: String,
1011 pub agent_identity: String,
1012 pub content: WireContentInput,
1013 #[serde(default)]
1014 pub handling_mode: WireHandlingMode,
1015 #[serde(default, skip_serializing_if = "Option::is_none")]
1016 pub render_metadata: Option<WireRenderMetadata>,
1017}
1018
1019#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1021#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1022pub struct WireAgentRuntimeId {
1023 pub identity: String,
1024 pub generation: u64,
1025}
1026
1027#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1029#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1030pub struct MobMemberSendResult {
1031 pub mob_id: String,
1032 pub agent_identity: String,
1034 pub member_ref: WireMemberRef,
1039 pub handling_mode: WireHandlingMode,
1040}
1041
1042#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1048#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1049#[serde(deny_unknown_fields)]
1050pub struct MobIngressInteractionParams {
1051 pub mob_id: String,
1052 pub spec: MobMemberSpecWire,
1053 pub content: WireContentInput,
1054 #[serde(default)]
1055 pub handling_mode: WireHandlingMode,
1056 #[serde(default, skip_serializing_if = "Option::is_none")]
1057 pub render_metadata: Option<WireRenderMetadata>,
1058}
1059
1060#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1062#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1063pub struct MobIngressInteractionResult {
1064 pub mob_id: String,
1065 pub agent_identity: String,
1066 pub member_ref: WireMemberRef,
1067 pub ensure_outcome: MobEnsureMemberOutcomeWire,
1068 pub delivery: MobMemberSendResult,
1069 pub events_after_cursor: u64,
1071 pub latest_event_cursor: u64,
1073}
1074
1075#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1077#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1078#[serde(rename_all = "snake_case")]
1079pub enum WireHandlingMode {
1080 #[default]
1081 Queue,
1082 Steer,
1083}
1084
1085impl From<WireHandlingMode> for HandlingMode {
1086 fn from(mode: WireHandlingMode) -> Self {
1087 match mode {
1088 WireHandlingMode::Queue => HandlingMode::Queue,
1089 WireHandlingMode::Steer => HandlingMode::Steer,
1090 }
1091 }
1092}
1093
1094impl From<HandlingMode> for WireHandlingMode {
1095 fn from(mode: HandlingMode) -> Self {
1096 match mode {
1097 HandlingMode::Queue => WireHandlingMode::Queue,
1098 HandlingMode::Steer => WireHandlingMode::Steer,
1099 }
1100 }
1101}
1102
1103#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1105#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1106#[serde(rename_all = "snake_case")]
1107pub enum WireRenderClass {
1108 UserPrompt,
1109 PeerMessage,
1110 PeerRequest,
1111 PeerResponse,
1112 ExternalEvent,
1113 FlowStep,
1114 Continuation,
1115 SystemNotice,
1116 ToolScopeNotice,
1117 OpsProgress,
1118}
1119
1120impl From<WireRenderClass> for RenderClass {
1121 fn from(class: WireRenderClass) -> Self {
1122 match class {
1123 WireRenderClass::UserPrompt => RenderClass::UserPrompt,
1124 WireRenderClass::PeerMessage => RenderClass::PeerMessage,
1125 WireRenderClass::PeerRequest => RenderClass::PeerRequest,
1126 WireRenderClass::PeerResponse => RenderClass::PeerResponse,
1127 WireRenderClass::ExternalEvent => RenderClass::ExternalEvent,
1128 WireRenderClass::FlowStep => RenderClass::FlowStep,
1129 WireRenderClass::Continuation => RenderClass::Continuation,
1130 WireRenderClass::SystemNotice => RenderClass::SystemNotice,
1131 WireRenderClass::ToolScopeNotice => RenderClass::ToolScopeNotice,
1132 WireRenderClass::OpsProgress => RenderClass::OpsProgress,
1133 }
1134 }
1135}
1136
1137impl From<RenderClass> for WireRenderClass {
1138 fn from(class: RenderClass) -> Self {
1139 match class {
1140 RenderClass::UserPrompt => WireRenderClass::UserPrompt,
1141 RenderClass::PeerMessage => WireRenderClass::PeerMessage,
1142 RenderClass::PeerRequest => WireRenderClass::PeerRequest,
1143 RenderClass::PeerResponse => WireRenderClass::PeerResponse,
1144 RenderClass::ExternalEvent => WireRenderClass::ExternalEvent,
1145 RenderClass::FlowStep => WireRenderClass::FlowStep,
1146 RenderClass::Continuation => WireRenderClass::Continuation,
1147 RenderClass::SystemNotice => WireRenderClass::SystemNotice,
1148 RenderClass::ToolScopeNotice => WireRenderClass::ToolScopeNotice,
1149 RenderClass::OpsProgress => WireRenderClass::OpsProgress,
1150 }
1151 }
1152}
1153
1154#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1156#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1157#[serde(rename_all = "snake_case")]
1158pub enum WireRenderSalience {
1159 Background,
1160 Normal,
1161 Important,
1162 Urgent,
1163}
1164
1165impl From<WireRenderSalience> for RenderSalience {
1166 fn from(salience: WireRenderSalience) -> Self {
1167 match salience {
1168 WireRenderSalience::Background => RenderSalience::Background,
1169 WireRenderSalience::Normal => RenderSalience::Normal,
1170 WireRenderSalience::Important => RenderSalience::Important,
1171 WireRenderSalience::Urgent => RenderSalience::Urgent,
1172 }
1173 }
1174}
1175
1176impl From<RenderSalience> for WireRenderSalience {
1177 fn from(salience: RenderSalience) -> Self {
1178 match salience {
1179 RenderSalience::Background => WireRenderSalience::Background,
1180 RenderSalience::Normal => WireRenderSalience::Normal,
1181 RenderSalience::Important => WireRenderSalience::Important,
1182 RenderSalience::Urgent => WireRenderSalience::Urgent,
1183 }
1184 }
1185}
1186
1187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1189#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1190pub struct WireRenderMetadata {
1191 pub class: WireRenderClass,
1192 #[serde(default, skip_serializing_if = "Option::is_none")]
1193 pub salience: Option<WireRenderSalience>,
1194}
1195
1196impl From<WireRenderMetadata> for RenderMetadata {
1197 fn from(metadata: WireRenderMetadata) -> Self {
1198 Self {
1199 class: metadata.class.into(),
1200 salience: metadata
1201 .salience
1202 .unwrap_or(WireRenderSalience::Normal)
1203 .into(),
1204 }
1205 }
1206}
1207
1208impl From<RenderMetadata> for WireRenderMetadata {
1209 fn from(metadata: RenderMetadata) -> Self {
1210 Self {
1211 class: metadata.class.into(),
1212 salience: Some(metadata.salience.into()),
1213 }
1214 }
1215}
1216
1217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1232#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1233pub struct MobMemberSpecWire {
1234 pub profile: String,
1236 pub agent_identity: String,
1238 #[serde(default, skip_serializing_if = "Option::is_none")]
1239 pub initial_message: Option<WireContentInput>,
1240 #[serde(default, skip_serializing_if = "Option::is_none")]
1241 pub runtime_mode: Option<WireMobRuntimeMode>,
1242 #[serde(default, skip_serializing_if = "Option::is_none")]
1243 pub backend: Option<WireMobBackendKind>,
1244 #[serde(default, skip_serializing_if = "Option::is_none")]
1245 pub binding: Option<WireRuntimeBinding>,
1246 #[serde(default, skip_serializing_if = "Option::is_none")]
1247 pub context: Option<Value>,
1248 #[serde(default, skip_serializing_if = "Option::is_none")]
1249 pub labels: Option<BTreeMap<String, String>>,
1250 #[serde(default, skip_serializing_if = "Option::is_none")]
1251 pub additional_instructions: Option<Vec<String>>,
1252 #[serde(default, skip_serializing_if = "Option::is_none")]
1253 pub auto_wire_parent: Option<bool>,
1254}
1255
1256impl MobMemberSpecWire {
1257 #[must_use]
1260 pub fn surface_metadata(&self) -> SurfaceMetadata {
1261 SurfaceMetadata::from_optional_parts(self.labels.clone(), self.context.clone())
1262 }
1263
1264 pub fn validate_public_surface_metadata(&self) -> Result<(), SurfaceMetadataError> {
1266 self.surface_metadata().validate_public()
1267 }
1268}
1269
1270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1272#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1273#[serde(deny_unknown_fields)]
1274pub struct MobEnsureMemberParams {
1275 pub mob_id: String,
1276 pub spec: MobMemberSpecWire,
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1291#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1292#[serde(transparent)]
1293pub struct WireMemberRef(String);
1294
1295impl WireMemberRef {
1296 #[must_use]
1300 pub fn encode(mob_id: &str, agent_identity: &str) -> Self {
1301 let payload = serde_json::json!({ "m": mob_id, "a": agent_identity });
1305 Self(base64_url_encode(payload.to_string().as_bytes()))
1306 }
1307
1308 #[must_use]
1310 pub fn as_str(&self) -> &str {
1311 &self.0
1312 }
1313
1314 #[must_use]
1317 pub fn from_token(token: impl Into<String>) -> Self {
1318 Self(token.into())
1319 }
1320
1321 pub fn decode(&self) -> Result<(String, String), WireMemberRefError> {
1324 let bytes = base64_url_decode(&self.0).map_err(|_| WireMemberRefError::Malformed)?;
1325 let value: Value =
1326 serde_json::from_slice(&bytes).map_err(|_| WireMemberRefError::Malformed)?;
1327 let mob_id = value
1328 .get("m")
1329 .and_then(Value::as_str)
1330 .ok_or(WireMemberRefError::Malformed)?;
1331 let agent_identity = value
1332 .get("a")
1333 .and_then(Value::as_str)
1334 .ok_or(WireMemberRefError::Malformed)?;
1335 Ok((mob_id.to_string(), agent_identity.to_string()))
1336 }
1337}
1338
1339#[derive(Debug, thiserror::Error)]
1341pub enum WireMemberRefError {
1342 #[error("malformed member ref token")]
1345 Malformed,
1346}
1347
1348fn base64_url_encode(bytes: &[u8]) -> String {
1349 use base64::Engine as _;
1350 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
1351}
1352
1353fn base64_url_decode(input: &str) -> Result<Vec<u8>, base64::DecodeError> {
1354 use base64::Engine as _;
1355 base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)
1356}
1357
1358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1360#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1361pub struct MobSpawnReceiptWire {
1362 pub agent_identity: String,
1363 pub member_ref: WireMemberRef,
1367}
1368
1369#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1371#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1372#[serde(rename_all = "snake_case")]
1373pub enum WireMobMemberStatus {
1374 Active,
1375 Retiring,
1376 Broken,
1377 Completed,
1378 Unknown,
1379}
1380
1381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1386#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1387pub struct MobMemberListEntryWire {
1388 pub agent_identity: String,
1389 pub member_ref: WireMemberRef,
1390 pub role: String,
1391 pub runtime_mode: WireMobRuntimeMode,
1392 pub state: WireMemberState,
1393 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1394 pub wired_to: Vec<String>,
1395 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1396 pub labels: BTreeMap<String, String>,
1397 pub status: WireMobMemberStatus,
1398 #[serde(default, skip_serializing_if = "Option::is_none")]
1399 pub error: Option<String>,
1400 pub is_final: bool,
1401}
1402
1403#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1409#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1410pub enum MobEnsureMemberOutcomeWire {
1411 #[serde(rename = "spawned")]
1412 Spawned(MobSpawnReceiptWire),
1413 #[serde(rename = "existed")]
1414 Existed(MobMemberListEntryWire),
1415}
1416
1417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1419#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1420pub struct MobEnsureMemberResult {
1421 pub outcome: MobEnsureMemberOutcomeWire,
1422}
1423
1424#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
1426#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1427#[serde(deny_unknown_fields)]
1428pub struct MobReconcileOptionsWire {
1429 #[serde(default)]
1432 pub retire_stale: bool,
1433}
1434
1435#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1437#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1438#[serde(rename_all = "snake_case")]
1439pub enum WireMobReconcileStage {
1440 Spawn,
1441 Retire,
1442}
1443
1444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1446#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1447#[serde(deny_unknown_fields)]
1448pub struct MobReconcileParams {
1449 pub mob_id: String,
1450 #[serde(default)]
1451 pub desired: Vec<MobMemberSpecWire>,
1452 #[serde(default)]
1453 pub options: MobReconcileOptionsWire,
1454}
1455
1456#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1458#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1459pub struct MobReconcileFailureWire {
1460 pub agent_identity: String,
1461 pub stage: WireMobReconcileStage,
1462 pub error: String,
1464}
1465
1466#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1468#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1469pub struct MobReconcileReportWire {
1470 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1471 pub desired: Vec<String>,
1472 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1473 pub retained: Vec<String>,
1474 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1475 pub spawned: Vec<MobSpawnReceiptWire>,
1476 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1477 pub retired: Vec<String>,
1478 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1479 pub failures: Vec<MobReconcileFailureWire>,
1480}
1481
1482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1484#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1485pub struct MobReconcileResult {
1486 pub report: MobReconcileReportWire,
1487}
1488
1489#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1494#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1495#[serde(rename_all = "snake_case")]
1496pub enum WireMobLifecycleAction {
1497 Stop,
1498 Resume,
1499 Complete,
1500 Reset,
1501 Destroy,
1502}
1503
1504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1506#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1507#[serde(deny_unknown_fields)]
1508pub struct MobLifecycleParams {
1509 pub mob_id: String,
1510 pub action: WireMobLifecycleAction,
1511}
1512
1513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1515#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1516pub struct MobLifecycleResult {
1517 pub mob_id: String,
1518 pub action: WireMobLifecycleAction,
1519 pub ok: bool,
1520 #[serde(default, skip_serializing_if = "Option::is_none")]
1521 pub destroy_report: Option<Value>,
1522}
1523
1524#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1526#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1527#[serde(deny_unknown_fields)]
1528pub struct MobAppendSystemContextParams {
1529 pub mob_id: String,
1530 pub agent_identity: String,
1531 pub text: String,
1532 #[serde(default, skip_serializing_if = "Option::is_none")]
1533 pub source: Option<String>,
1534 #[serde(default, skip_serializing_if = "Option::is_none")]
1535 pub idempotency_key: Option<String>,
1536}
1537
1538#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1540#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1541pub struct MobAppendSystemContextResult {
1542 pub mob_id: String,
1543 pub agent_identity: String,
1544 pub status: String,
1545}
1546
1547#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1549#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1550pub struct MobFlowsResult {
1551 pub mob_id: String,
1552 pub flows: Vec<String>,
1553}
1554
1555#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1557#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1558#[serde(deny_unknown_fields)]
1559pub struct MobFlowRunParams {
1560 pub mob_id: String,
1561 pub flow_id: String,
1562 #[serde(default)]
1563 pub params: Value,
1564}
1565
1566#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1568#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1569pub struct MobFlowRunResult {
1570 pub run_id: String,
1571}
1572
1573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1575#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1576#[serde(deny_unknown_fields)]
1577pub struct MobFlowStatusParams {
1578 pub mob_id: String,
1579 pub run_id: String,
1580}
1581
1582#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1584#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1585pub struct MobFlowStatusResult {
1586 pub run: Value,
1587}
1588
1589#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1592#[serde(deny_unknown_fields)]
1593pub struct MobFlowCancelParams {
1594 pub mob_id: String,
1595 pub run_id: String,
1596}
1597
1598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1600#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1601pub struct MobFlowCancelResult {
1602 pub canceled: bool,
1603}
1604
1605#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1607#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1608#[serde(deny_unknown_fields)]
1609pub struct MobSpawnHelperParams {
1610 pub mob_id: String,
1611 pub prompt: String,
1612 #[serde(default, skip_serializing_if = "Option::is_none")]
1613 pub agent_identity: Option<String>,
1614 #[serde(default, skip_serializing_if = "Option::is_none")]
1615 pub role_name: Option<String>,
1616 #[serde(default, skip_serializing_if = "Option::is_none")]
1617 pub runtime_mode: Option<WireMobRuntimeMode>,
1618 #[serde(default, skip_serializing_if = "Option::is_none")]
1619 pub backend: Option<WireMobBackendKind>,
1620}
1621
1622#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1624#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1625#[serde(deny_unknown_fields)]
1626pub struct MobForkHelperParams {
1627 pub mob_id: String,
1628 pub source_member_id: String,
1629 pub prompt: String,
1630 #[serde(default, skip_serializing_if = "Option::is_none")]
1631 pub agent_identity: Option<String>,
1632 #[serde(default, skip_serializing_if = "Option::is_none")]
1633 pub role_name: Option<String>,
1634 #[serde(default, skip_serializing_if = "Option::is_none")]
1635 pub fork_context: Option<Value>,
1636 #[serde(default, skip_serializing_if = "Option::is_none")]
1637 pub runtime_mode: Option<WireMobRuntimeMode>,
1638 #[serde(default, skip_serializing_if = "Option::is_none")]
1639 pub backend: Option<WireMobBackendKind>,
1640}
1641
1642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1644#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1645pub struct MobHelperResult {
1646 #[serde(default, skip_serializing_if = "Option::is_none")]
1647 pub output: Option<String>,
1648 pub tokens_used: u64,
1649 pub agent_identity: String,
1650 pub member_ref: WireMemberRef,
1651}
1652
1653#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1655#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1656pub struct MobForceCancelResult {
1657 pub cancelled: bool,
1658}
1659
1660#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1662#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1663#[serde(deny_unknown_fields)]
1664pub struct MobTurnStartParams {
1665 pub mob_id: String,
1666 pub agent_identity: String,
1667 pub prompt: WireContentInput,
1668 #[serde(default, skip_serializing_if = "Option::is_none")]
1669 pub skill_refs: Option<Vec<meerkat_core::skills::SkillRef>>,
1670 #[serde(default, skip_serializing_if = "Option::is_none")]
1671 pub flow_tool_overlay: Option<meerkat_core::service::TurnToolOverlay>,
1672 #[serde(default, skip_serializing_if = "Option::is_none")]
1673 pub additional_instructions: Option<Vec<String>>,
1674 #[serde(default, skip_serializing_if = "Option::is_none")]
1675 pub keep_alive: Option<bool>,
1676 #[serde(default, skip_serializing_if = "Option::is_none")]
1677 pub model: Option<String>,
1678 #[serde(default, skip_serializing_if = "Option::is_none")]
1679 pub provider: Option<String>,
1680 #[serde(default, skip_serializing_if = "Option::is_none")]
1681 pub max_tokens: Option<u32>,
1682 #[serde(default, skip_serializing_if = "Option::is_none")]
1683 pub system_prompt: Option<String>,
1684 #[serde(default, skip_serializing_if = "Option::is_none")]
1685 pub output_schema: Option<Value>,
1686 #[serde(default, skip_serializing_if = "Option::is_none")]
1687 pub structured_output_retries: Option<u32>,
1688 #[serde(default, skip_serializing_if = "Option::is_none")]
1689 pub provider_params: Option<Value>,
1690 #[serde(default)]
1691 pub clear_provider_params: bool,
1692 #[serde(default, skip_serializing_if = "Option::is_none")]
1693 pub auth_binding: Option<WireAuthBindingRef>,
1694 #[serde(default)]
1695 pub clear_auth_binding: bool,
1696}
1697
1698#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1700#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1701pub struct MobMemberStatusResult {
1702 pub status: WireMobMemberStatus,
1703 #[serde(default, skip_serializing_if = "Option::is_none")]
1704 pub output_preview: Option<String>,
1705 #[serde(default, skip_serializing_if = "Option::is_none")]
1706 pub error: Option<String>,
1707 pub tokens_used: u64,
1708 pub is_final: bool,
1709 #[serde(default, skip_serializing_if = "Option::is_none")]
1710 pub current_session_id: Option<String>,
1711 #[serde(default, skip_serializing_if = "Option::is_none")]
1712 pub peer_connectivity: Option<Value>,
1713 #[serde(default, skip_serializing_if = "Option::is_none")]
1714 pub kickoff: Option<Value>,
1715 #[serde(default, skip_serializing_if = "Option::is_none")]
1716 pub external_member: Option<Value>,
1717 #[serde(default, skip_serializing_if = "Option::is_none")]
1718 pub resolved_capabilities: Option<crate::wire::WireResolvedModelCapabilities>,
1719}
1720
1721#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1723#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1724pub struct MobSnapshotResult {
1725 pub mob_id: String,
1726 pub status: String,
1727 pub members: Vec<Value>,
1728}
1729
1730#[cfg(test)]
1731mod member_status_capability_tests {
1732 use super::*;
1733
1734 #[test]
1735 fn member_status_result_round_trips_resolved_capabilities() -> Result<(), serde_json::Error> {
1736 let capabilities = crate::wire::WireResolvedModelCapabilities {
1737 vision: true,
1738 image_input: true,
1739 image_tool_results: false,
1740 inline_video: false,
1741 realtime: true,
1742 web_search: true,
1743 image_generation: true,
1744 };
1745 let result = MobMemberStatusResult {
1746 status: WireMobMemberStatus::Active,
1747 output_preview: None,
1748 error: None,
1749 tokens_used: 0,
1750 is_final: false,
1751 current_session_id: Some("session-1".to_string()),
1752 peer_connectivity: None,
1753 kickoff: None,
1754 external_member: None,
1755 resolved_capabilities: Some(capabilities.clone()),
1756 };
1757
1758 let json = serde_json::to_string(&result)?;
1759 assert!(json.contains("\"resolved_capabilities\""));
1760 let parsed: MobMemberStatusResult = serde_json::from_str(&json)?;
1761 assert_eq!(parsed.resolved_capabilities, Some(capabilities));
1762 Ok(())
1763 }
1764}
1765
1766#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1768#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1769pub struct MobDestroyResult {
1770 pub mob_id: String,
1771 pub ok: bool,
1772 pub destroy_report: Value,
1773}
1774
1775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1777#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1778pub struct MobRotateSupervisorResult {
1779 pub mob_id: String,
1780 pub ok: bool,
1781 pub report: SupervisorRotationReportWire,
1782}
1783
1784#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1786#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1787pub struct SupervisorRotationReportWire {
1788 pub previous_epoch: u64,
1789 pub current_epoch: u64,
1790 pub public_peer_id: String,
1791}
1792
1793#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1795#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1796#[serde(deny_unknown_fields)]
1797pub struct MobWaitParams {
1798 pub mob_id: String,
1799 #[serde(default, skip_serializing_if = "Option::is_none")]
1800 pub member_ids: Option<Vec<String>>,
1801 #[serde(default, skip_serializing_if = "Option::is_none")]
1802 pub timeout_ms: Option<u64>,
1803}
1804
1805#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1807#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1808pub struct MobWaitMembersResult {
1809 pub members: Vec<Value>,
1810}
1811
1812#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1814#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1815pub struct MobCancelWorkResult {
1816 pub mob_id: String,
1817 pub ok: bool,
1818}
1819
1820#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1822#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1823pub struct MobCancelAllWorkResult {
1824 pub mob_id: String,
1825 pub ok: bool,
1826}
1827
1828#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1830#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1831#[serde(deny_unknown_fields)]
1832pub struct MobProfileCreateParams {
1833 pub name: String,
1834 pub profile: MobProfileInput,
1835}
1836
1837#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1839#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1840#[serde(deny_unknown_fields)]
1841pub struct MobProfileNameParams {
1842 pub name: String,
1843}
1844
1845#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1847#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1848#[serde(deny_unknown_fields)]
1849pub struct MobProfileUpdateParams {
1850 pub name: String,
1851 pub profile: MobProfileInput,
1852 pub expected_revision: u64,
1853}
1854
1855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1857#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1858#[serde(deny_unknown_fields)]
1859pub struct MobProfileDeleteParams {
1860 pub name: String,
1861 pub expected_revision: u64,
1862}
1863
1864#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1866#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1867pub struct MobProfileLookupResult {
1868 #[serde(default)]
1869 pub not_found: bool,
1870 pub name: String,
1871 #[serde(default, skip_serializing_if = "Option::is_none")]
1872 pub profile: Option<Value>,
1873 #[serde(default, skip_serializing_if = "Option::is_none")]
1874 pub revision: Option<u64>,
1875 #[serde(default, skip_serializing_if = "Option::is_none")]
1876 pub created_at: Option<String>,
1877 #[serde(default, skip_serializing_if = "Option::is_none")]
1878 pub updated_at: Option<String>,
1879}
1880
1881#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1883#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1884pub struct MobProfileListResult {
1885 pub profiles: Vec<MobProfileLookupResult>,
1886}
1887
1888#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1890#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1891pub struct MobProfileDeleteResult {
1892 pub name: String,
1893 pub deleted_revision: u64,
1894}
1895
1896#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1898#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1899#[serde(deny_unknown_fields)]
1900pub struct MobStreamOpenParams {
1901 pub mob_id: String,
1902 #[serde(default, skip_serializing_if = "Option::is_none")]
1903 pub agent_identity: Option<String>,
1904}
1905
1906#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1908#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1909pub struct MobStreamOpenResult {
1910 pub stream_id: String,
1911 pub opened: bool,
1912}
1913
1914#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1916#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1917#[serde(deny_unknown_fields)]
1918pub struct MobStreamCloseParams {
1919 pub stream_id: String,
1920}
1921
1922#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1924#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1925pub struct MobStreamCloseResult {
1926 pub stream_id: String,
1927 pub closed: bool,
1928 pub already_closed: bool,
1929}
1930
1931#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1934#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1935#[serde(rename_all = "snake_case")]
1936pub enum WireWorkOrigin {
1937 #[default]
1938 External,
1939 Internal,
1940}
1941
1942#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1948#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1949#[serde(deny_unknown_fields)]
1950pub struct MobSubmitWorkParams {
1951 pub member_ref: WireMemberRef,
1952 #[serde(default, skip_serializing_if = "Option::is_none")]
1955 pub work_ref: Option<String>,
1956 pub content: WireContentInput,
1957 #[serde(default)]
1958 pub origin: WireWorkOrigin,
1959}
1960
1961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1963#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1964pub struct MobSubmitWorkResult {
1965 pub mob_id: String,
1966 pub work_ref: String,
1967 pub member_ref: WireMemberRef,
1968}
1969
1970#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1972#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1973#[serde(deny_unknown_fields)]
1974pub struct MobCancelWorkParams {
1975 pub mob_id: String,
1976 pub work_ref: String,
1977}
1978
1979#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1981#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1982#[serde(deny_unknown_fields)]
1983pub struct MobCancelAllWorkParams {
1984 pub member_ref: WireMemberRef,
1985}
1986
1987#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1989#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1990#[serde(rename_all = "snake_case")]
1991pub enum WireMemberState {
1992 Active,
1993 Retiring,
1994}
1995
1996#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
1999#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2000#[serde(deny_unknown_fields)]
2001pub struct MobMemberFilterWire {
2002 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
2004 pub labels: BTreeMap<String, String>,
2005 #[serde(default, skip_serializing_if = "Option::is_none")]
2007 pub role: Option<String>,
2008 #[serde(default, skip_serializing_if = "Option::is_none")]
2010 pub state: Option<WireMemberState>,
2011}
2012
2013#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2015#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2016#[serde(deny_unknown_fields)]
2017pub struct MobListMembersMatchingParams {
2018 pub mob_id: String,
2019 #[serde(default)]
2020 pub filter: MobMemberFilterWire,
2021}
2022
2023#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2026#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
2027pub struct MobListMembersMatchingResult {
2028 #[serde(default)]
2029 pub members: Vec<Value>,
2030}
2031
2032#[cfg(test)]
2033#[allow(clippy::expect_used, clippy::panic)]
2034mod tests {
2035 use super::*;
2036
2037 #[test]
2038 fn wire_member_ref_round_trips_through_encode_decode() {
2039 let token = WireMemberRef::encode("mob-42", "worker-1");
2040 let (mob_id, agent_identity) = token.decode().expect("decode round-trips");
2041 assert_eq!(mob_id, "mob-42");
2042 assert_eq!(agent_identity, "worker-1");
2043 }
2044
2045 #[test]
2046 fn wire_member_ref_rejects_malformed_token() {
2047 let err = WireMemberRef::from_token("not-a-token-payload")
2048 .decode()
2049 .expect_err("malformed tokens must fail to decode");
2050 assert!(matches!(err, WireMemberRefError::Malformed));
2051 }
2052
2053 #[test]
2054 fn mob_member_spec_exposes_shared_surface_metadata() {
2055 let spec = MobMemberSpecWire {
2056 profile: "worker".into(),
2057 agent_identity: "w1".into(),
2058 initial_message: None,
2059 runtime_mode: None,
2060 backend: None,
2061 binding: None,
2062 context: Some(serde_json::json!({"client_ref": "member-card"})),
2063 labels: Some(BTreeMap::from([("client.member_id".into(), "w1".into())])),
2064 additional_instructions: None,
2065 auto_wire_parent: None,
2066 };
2067
2068 let metadata = spec.surface_metadata();
2069 assert_eq!(
2070 metadata.labels.get("client.member_id").map(String::as_str),
2071 Some("w1")
2072 );
2073 assert_eq!(
2074 metadata.app_context,
2075 Some(serde_json::json!({"client_ref": "member-card"}))
2076 );
2077 }
2078
2079 #[test]
2080 fn mob_member_spec_surface_metadata_rejects_reserved_keys() {
2081 let spec = MobMemberSpecWire {
2082 profile: "worker".into(),
2083 agent_identity: "w1".into(),
2084 initial_message: None,
2085 runtime_mode: None,
2086 backend: None,
2087 binding: None,
2088 context: None,
2089 labels: Some(BTreeMap::from([("mob_id".into(), "spoof".into())])),
2090 additional_instructions: None,
2091 auto_wire_parent: None,
2092 };
2093
2094 assert!(spec.validate_public_surface_metadata().is_err());
2095 }
2096
2097 #[test]
2098 fn mob_reconcile_failure_stage_is_typed_wire_enum() {
2099 let failure = MobReconcileFailureWire {
2100 agent_identity: "worker-1".into(),
2101 stage: WireMobReconcileStage::Spawn,
2102 error: "spawn failed".into(),
2103 };
2104
2105 let json = serde_json::to_value(&failure).expect("serialize failure");
2106 assert_eq!(json["stage"], "spawn");
2107
2108 let round_trip: MobReconcileFailureWire =
2109 serde_json::from_value(json).expect("deserialize failure");
2110 assert_eq!(round_trip.stage, WireMobReconcileStage::Spawn);
2111
2112 let err = serde_json::from_value::<MobReconcileFailureWire>(serde_json::json!({
2113 "agent_identity": "worker-1",
2114 "stage": "restart",
2115 "error": "bad stage"
2116 }))
2117 .expect_err("unknown reconcile stage must be rejected");
2118 assert!(err.to_string().contains("unknown variant"));
2119 }
2120
2121 #[test]
2122 fn mob_lifecycle_params_reject_unknown_action_string() {
2123 let err = serde_json::from_value::<MobLifecycleParams>(serde_json::json!({
2124 "mob_id": "mob-1",
2125 "action": "explode"
2126 }))
2127 .expect_err("unknown lifecycle actions must fail at the typed wire boundary");
2128
2129 assert!(
2130 err.to_string().contains("unknown variant"),
2131 "unexpected error: {err}"
2132 );
2133 }
2134
2135 #[test]
2136 fn mob_lifecycle_result_round_trips_typed_action() {
2137 let result = MobLifecycleResult {
2138 mob_id: "mob-1".into(),
2139 action: WireMobLifecycleAction::Complete,
2140 ok: true,
2141 destroy_report: None,
2142 };
2143
2144 let json = serde_json::to_value(&result).expect("serialize lifecycle result");
2145 assert_eq!(json["action"], "complete");
2146
2147 let round_trip: MobLifecycleResult =
2148 serde_json::from_value(json).expect("deserialize lifecycle result");
2149 assert_eq!(round_trip.action, WireMobLifecycleAction::Complete);
2150 }
2151
2152 #[test]
2153 fn mob_spawn_many_result_entry_uses_typed_status_result_envelope() {
2154 let member_ref = WireMemberRef::encode("mob-1", "worker-1");
2155 let entry = MobSpawnManyResultEntry::spawned("worker-1", member_ref.clone());
2156
2157 let json = serde_json::to_value(&entry).expect("serialize typed spawn_many row");
2158 assert_eq!(json["status"], "spawned");
2159 assert_eq!(json["result"]["agent_identity"], "worker-1");
2160 assert_eq!(json["result"]["member_ref"], member_ref.as_str());
2161 assert!(json.get("ok").is_none());
2162 assert!(json.get("error").is_none());
2163
2164 let round_trip: MobSpawnManyResultEntry =
2165 serde_json::from_value(json).expect("deserialize typed spawn_many row");
2166 assert_eq!(round_trip, entry);
2167
2168 let failed = MobSpawnManyResultEntry::failed(
2169 MobSpawnManyFailureCause::ProfileNotFound,
2170 "profile missing",
2171 );
2172 let json = serde_json::to_value(&failed).expect("serialize typed failed spawn_many row");
2173 assert_eq!(json["status"], "failed");
2174 assert_eq!(json["result"]["cause"], "profile_not_found");
2175 assert_eq!(json["result"]["message"], "profile missing");
2176 assert!(json.get("ok").is_none());
2177 assert!(json.get("error").is_none());
2178
2179 let round_trip: MobSpawnManyResultEntry =
2180 serde_json::from_value(json).expect("deserialize typed failed spawn_many row");
2181 assert_eq!(round_trip, failed);
2182 }
2183
2184 #[test]
2185 fn mob_spawn_many_result_entry_rejects_legacy_or_malformed_envelopes() {
2186 let legacy = serde_json::json!({
2187 "ok": true,
2188 "agent_identity": "worker-1",
2189 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2190 });
2191 let err = serde_json::from_value::<MobSpawnManyResultEntry>(legacy)
2192 .expect_err("legacy ok carrier must not deserialize");
2193 assert!(
2194 err.to_string().contains("missing field `status`")
2195 || err.to_string().contains("unknown field"),
2196 "unexpected error: {err}"
2197 );
2198
2199 let missing_result = serde_json::json!({
2200 "status": "spawned"
2201 });
2202 let err = serde_json::from_value::<MobSpawnManyResultEntry>(missing_result)
2203 .expect_err("missing typed result must fail closed");
2204 assert!(
2205 err.to_string().contains("missing field `result`"),
2206 "unexpected error: {err}"
2207 );
2208
2209 let unknown_status = serde_json::json!({
2210 "status": "ok",
2211 "result": {
2212 "agent_identity": "worker-1",
2213 "member_ref": WireMemberRef::encode("mob-1", "worker-1"),
2214 }
2215 });
2216 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_status)
2217 .expect_err("unknown typed status must fail closed");
2218 assert!(
2219 err.to_string().contains("unknown variant"),
2220 "unexpected error: {err}"
2221 );
2222
2223 let mismatched = serde_json::json!({
2224 "status": "spawned",
2225 "result": {
2226 "cause": "profile_not_found",
2227 "message": "profile missing"
2228 }
2229 });
2230 let err = serde_json::from_value::<MobSpawnManyResultEntry>(mismatched)
2231 .expect_err("status/result mismatch must fail closed");
2232 assert!(
2233 err.to_string()
2234 .contains("status spawned requires spawned result"),
2235 "unexpected error: {err}"
2236 );
2237
2238 let message_only_failure = serde_json::json!({
2239 "status": "failed",
2240 "result": {
2241 "message": "profile missing"
2242 }
2243 });
2244 let err = serde_json::from_value::<MobSpawnManyResultEntry>(message_only_failure)
2245 .expect_err("string-only failure result must fail closed");
2246 assert!(
2247 err.to_string().contains("data did not match any variant")
2248 || err.to_string().contains("missing field `cause`"),
2249 "unexpected error: {err}"
2250 );
2251
2252 let unknown_failure_cause = serde_json::json!({
2253 "status": "failed",
2254 "result": {
2255 "cause": "future_failure",
2256 "message": "future failure"
2257 }
2258 });
2259 let err = serde_json::from_value::<MobSpawnManyResultEntry>(unknown_failure_cause)
2260 .expect_err("unknown failure cause must fail closed");
2261 assert!(
2262 err.to_string().contains("data did not match any variant")
2263 || err.to_string().contains("unknown variant"),
2264 "unexpected error: {err}"
2265 );
2266 }
2267
2268 #[test]
2269 fn mob_wire_params_reject_legacy_local_target_shape() {
2270 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2271 "mob_id": "mob-1",
2272 "local": "member-a",
2273 "target": { "local": "member-b" }
2274 }))
2275 .expect_err("legacy local/target shape must be rejected");
2276
2277 let msg = err.to_string();
2278 assert!(
2279 msg.contains("unknown field `local`") || msg.contains("missing field `member`"),
2280 "unexpected error: {msg}"
2281 );
2282 }
2283
2284 #[test]
2285 fn mob_wire_params_accept_canonical_external_peer_identity() {
2286 let params = serde_json::from_value::<MobWireParams>(serde_json::json!({
2287 "mob_id": "mob-1",
2288 "member": "member-a",
2289 "peer": {
2290 "external": {
2291 "name": "external-worker",
2292 "address": "inproc://external-worker",
2293 "identity": {
2294 "kind": "ed25519_public_key",
2295 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2296 }
2297 }
2298 }
2299 }))
2300 .expect("canonical external peer identity should deserialize");
2301
2302 let MobPeerTarget::External(spec) = params.peer else {
2303 panic!("expected external peer target");
2304 };
2305 assert_eq!(spec.name, "external-worker");
2306 }
2307
2308 #[test]
2309 fn mob_wire_params_reject_raw_external_peer_id_shape() {
2310 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2311 "mob_id": "mob-1",
2312 "member": "member-a",
2313 "peer": {
2314 "external": {
2315 "name": "external-worker",
2316 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2317 "address": "inproc://external-worker",
2318 "pubkey": vec![7u8; 32]
2319 }
2320 }
2321 }))
2322 .expect_err("raw peer_id/pubkey external peer shape must be rejected");
2323
2324 let msg = err.to_string();
2325 assert!(
2326 msg.contains("peer_id") || msg.contains("identity"),
2327 "unexpected error: {msg}"
2328 );
2329 }
2330
2331 #[test]
2332 fn mob_wire_params_reject_missing_external_peer_pubkey_material() {
2333 let err = serde_json::from_value::<MobWireParams>(serde_json::json!({
2334 "mob_id": "mob-1",
2335 "member": "member-a",
2336 "peer": {
2337 "external": {
2338 "name": "external-worker",
2339 "address": "inproc://external-worker",
2340 "identity": {
2341 "kind": "ed25519_public_key"
2342 }
2343 }
2344 }
2345 }))
2346 .expect_err("missing external peer pubkey material must fail closed");
2347
2348 let msg = err.to_string();
2349 assert!(
2350 msg.contains("public_key") || msg.contains("identity"),
2351 "unexpected error: {msg}"
2352 );
2353 }
2354
2355 #[test]
2356 fn runtime_binding_accepts_canonical_external_peer_identity() {
2357 let binding = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2358 "kind": "external",
2359 "address": "inproc://external-worker",
2360 "identity": {
2361 "kind": "ed25519_public_key",
2362 "public_key": "ed25519:BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc="
2363 }
2364 }))
2365 .expect("canonical external runtime binding identity should deserialize");
2366
2367 let WireRuntimeBinding::External {
2368 identity, address, ..
2369 } = binding
2370 else {
2371 panic!("expected external runtime binding");
2372 };
2373 assert_eq!(address, "inproc://external-worker");
2374 assert_eq!(
2375 identity.resolve().expect("identity resolves").pubkey,
2376 [7u8; 32]
2377 );
2378 }
2379
2380 #[test]
2381 fn runtime_binding_rejects_raw_external_peer_id_shape() {
2382 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2383 "kind": "external",
2384 "peer_id": meerkat_core::comms::PeerId::from_ed25519_pubkey(&[7u8; 32]).to_string(),
2385 "address": "inproc://external-worker",
2386 "pubkey": vec![7u8; 32]
2387 }))
2388 .expect_err("raw peer_id/pubkey external runtime binding shape must be rejected");
2389
2390 let msg = err.to_string();
2391 assert!(
2392 msg.contains("peer_id") || msg.contains("identity"),
2393 "unexpected error: {msg}"
2394 );
2395 }
2396
2397 #[test]
2398 fn runtime_binding_rejects_missing_external_peer_pubkey_material() {
2399 let err = serde_json::from_value::<WireRuntimeBinding>(serde_json::json!({
2400 "kind": "external",
2401 "address": "inproc://external-worker",
2402 "identity": {
2403 "kind": "ed25519_public_key"
2404 }
2405 }))
2406 .expect_err("missing external runtime binding pubkey material must fail closed");
2407
2408 let msg = err.to_string();
2409 assert!(
2410 msg.contains("public_key") || msg.contains("identity"),
2411 "unexpected error: {msg}"
2412 );
2413 }
2414
2415 #[test]
2416 fn mob_turn_start_params_capture_turn_override_fields() {
2417 let params = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2418 "mob_id": "mob-1",
2419 "agent_identity": "worker",
2420 "prompt": "continue",
2421 "output_schema": { "type": "object" },
2422 "structured_output_retries": 2
2423 }))
2424 .expect("turn_start should accept explicit turn override fields");
2425
2426 assert_eq!(params.mob_id, "mob-1");
2427 assert_eq!(params.agent_identity, "worker");
2428 assert_eq!(params.prompt, WireContentInput::Text("continue".into()));
2429 assert_eq!(
2430 params.output_schema,
2431 Some(serde_json::json!({ "type": "object" }))
2432 );
2433 assert_eq!(params.structured_output_retries, Some(2));
2434
2435 let err = serde_json::from_value::<MobTurnStartParams>(serde_json::json!({
2436 "mob_id": "mob-1",
2437 "agent_identity": "worker",
2438 "prompt": "continue",
2439 "unknown_override": true
2440 }))
2441 .expect_err("turn_start must reject unknown override fields");
2442 assert!(
2443 err.to_string().contains("unknown field"),
2444 "unexpected error: {err}"
2445 );
2446 }
2447
2448 #[test]
2449 fn mob_create_params_reject_reserved_runtime_lifecycle_fields() {
2450 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2451 "definition": {
2452 "id": "mob-1",
2453 "owner_runtime_binding": "runtime:worker:0",
2454 "profiles": {
2455 "worker": { "model": "claude-sonnet-4-6" }
2456 }
2457 }
2458 }))
2459 .expect_err("reserved runtime lifecycle fields must be rejected");
2460
2461 assert!(
2462 err.to_string()
2463 .contains("unknown field `owner_runtime_binding`"),
2464 "unexpected error: {err}"
2465 );
2466 }
2467
2468 #[test]
2469 fn mob_create_params_reject_reserved_runtime_bridge_owner_field() {
2470 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2471 "definition": {
2472 "id": "mob-1",
2473 "owner_transport_binding": "transport:worker:0",
2474 "profiles": {
2475 "worker": { "model": "claude-sonnet-4-6" }
2476 }
2477 }
2478 }))
2479 .expect_err("reserved runtime bridge owner field must be rejected");
2480
2481 assert!(
2482 err.to_string()
2483 .contains("unknown field `owner_transport_binding`"),
2484 "unexpected error: {err}"
2485 );
2486 }
2487
2488 #[test]
2489 fn mob_create_params_reject_internal_profile_tool_bundles() {
2490 let err = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2491 "definition": {
2492 "id": "mob-1",
2493 "profiles": {
2494 "worker": {
2495 "model": "claude-sonnet-4-6",
2496 "tools": {
2497 "rust_bundles": ["internal-only"]
2498 }
2499 }
2500 }
2501 }
2502 }))
2503 .expect_err("internal rust tool bundles must be rejected");
2504
2505 assert!(
2508 err.to_string().contains("did not match any variant")
2509 || err.to_string().contains("unknown field `rust_bundles`"),
2510 "unexpected error: {err}"
2511 );
2512 }
2513
2514 #[test]
2515 fn mob_create_params_accept_typed_nested_flow_definition() {
2516 let params = serde_json::from_value::<MobCreateParams>(serde_json::json!({
2517 "definition": {
2518 "id": "mob-1",
2519 "profiles": {
2520 "worker": { "model": "claude-sonnet-4-6" }
2521 },
2522 "flows": {
2523 "review": {
2524 "description": "review flow",
2525 "steps": {
2526 "draft": {
2527 "role": "worker",
2528 "message": "draft it"
2529 }
2530 }
2531 }
2532 }
2533 }
2534 }))
2535 .expect("typed nested flow definition should parse");
2536
2537 assert_eq!(
2538 params.definition.flows["review"].steps["draft"].role,
2539 "worker"
2540 );
2541 }
2542}