1use std::collections::BTreeMap;
4use std::time::Duration;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::runtime::{
10 BigQuerySessionStoreAdapter, BigQuerySessionStoreError, ConsoleRestJsonRequest,
11 ConsoleRestJsonResponse, DeliveryHistoryRequest, DeliverySendError, DeliverySendRequest,
12 ElephantMemoryStoreError, GatingDecideError, GatingDecideRequest, GatingDecision,
13 GatingEvaluateRequest, GatingRiskTier, MemoryIndexError, MemoryIndexRequest,
14 MemoryQueryRequest, MobkitRuntimeHandle, ModuleRouteError, ModuleRouteRequest,
15 ROUTING_RETRY_MAX_CAP, RoutingResolveError, RoutingResolveRequest, RuntimeDecisionState,
16 RuntimeRoute, RuntimeRouteMutationError, ScheduleDefinition, ScheduleValidationError,
17 SessionPersistenceRow, SubscribeError, SubscribeRequest, SubscribeScope,
18 handle_console_rest_json_route, route_module_call, validate_schedules,
19};
20use crate::unified_runtime::{EventQuery, UnifiedRuntime};
21
22mod console_ingress;
23mod gating_methods;
24mod memory_methods;
25pub(crate) mod mob_methods;
26pub(crate) mod params;
27mod routing_delivery_methods;
28mod scheduling_methods;
29mod session_store_methods;
30mod subscribe_methods;
31
32pub use console_ingress::handle_console_ingress_json;
33
34use gating_methods::{
35 GatingParamsError, parse_gating_audit_params, parse_gating_decide_params,
36 parse_gating_evaluate_params, parse_gating_pending_params,
37};
38use memory_methods::{
39 MemoryParamsError, parse_memory_index_params, parse_memory_query_params,
40 parse_memory_stores_params,
41};
42use routing_delivery_methods::{
43 RoutingDeliveryParamsError, parse_delivery_history_params, parse_delivery_send_params,
44 parse_routing_resolve_params, parse_routing_route_add_params,
45 parse_routing_route_delete_params, parse_routing_routes_list_params,
46};
47use scheduling_methods::{format_schedule_validation_error, parse_scheduling_params};
48use session_store_methods::{
49 BigQuerySessionStoreRpcError, format_bigquery_store_error, parse_bigquery_session_store_params,
50 run_bigquery_session_store_request,
51};
52use subscribe_methods::{SubscribeParamsError, parse_subscribe_request};
53
54pub const JSONRPC_VERSION: &str = "2.0";
55pub const MOBKIT_CONTRACT_VERSION: &str = "0.3.0";
56pub const MAX_SCHEDULES_PER_REQUEST: usize = 256;
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum RpcCapabilitiesError {
60 InvalidJson,
61 InvalidSchema,
62 MissingContractVersion,
63 InvalidContractVersion,
64}
65
66impl std::fmt::Display for RpcCapabilitiesError {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Self::InvalidJson => write!(f, "invalid JSON"),
70 Self::InvalidSchema => write!(f, "invalid schema"),
71 Self::MissingContractVersion => write!(f, "missing contract version"),
72 Self::InvalidContractVersion => write!(f, "invalid contract version"),
73 }
74 }
75}
76
77impl std::error::Error for RpcCapabilitiesError {}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct RpcCapabilities {
81 pub contract_version: String,
82 #[serde(flatten)]
83 pub extra: BTreeMap<String, Value>,
84}
85
86pub fn parse_rpc_capabilities(line: &str) -> Result<RpcCapabilities, RpcCapabilitiesError> {
87 let raw: Value = serde_json::from_str(line).map_err(|_| RpcCapabilitiesError::InvalidJson)?;
88 let object = raw.as_object().ok_or(RpcCapabilitiesError::InvalidSchema)?;
89 let contract = object
90 .get("contract_version")
91 .ok_or(RpcCapabilitiesError::MissingContractVersion)?;
92 let contract_str = contract
93 .as_str()
94 .ok_or(RpcCapabilitiesError::InvalidContractVersion)?;
95 if contract_str.trim().is_empty() {
96 return Err(RpcCapabilitiesError::InvalidContractVersion);
97 }
98 serde_json::from_value(raw).map_err(|_| RpcCapabilitiesError::InvalidSchema)
99}
100
101pub const MOB_EVENTS_STALE_CURSOR_CODE: i64 = -32010;
108
109pub const MEMORY_BACKEND_UNAVAILABLE_CODE: i64 = -32012;
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct JsonRpcRequest {
119 pub jsonrpc: String,
120 #[serde(default)]
121 pub id: Option<Value>,
122 pub method: String,
123 #[serde(default)]
124 pub params: Value,
125}
126
127#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
128pub struct JsonRpcError {
129 pub code: i64,
130 pub message: String,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub data: Option<Value>,
137}
138
139impl JsonRpcError {
140 pub fn new(code: i64, message: impl Into<String>) -> Self {
141 Self {
142 code,
143 message: message.into(),
144 data: None,
145 }
146 }
147
148 #[must_use]
149 pub fn with_data(mut self, data: Value) -> Self {
150 self.data = Some(data);
151 self
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct JsonRpcResponse {
157 pub jsonrpc: String,
158 pub id: Value,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub result: Option<Value>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub error: Option<JsonRpcError>,
163}
164
165pub fn handle_mobkit_rpc_json(
166 runtime: &mut MobkitRuntimeHandle,
167 request_json: &str,
168 timeout: Duration,
169) -> String {
170 let raw_request: Value = match serde_json::from_str(request_json) {
171 Ok(raw_request) => raw_request,
172 Err(_) => {
173 return serialize_response(&JsonRpcResponse {
174 jsonrpc: JSONRPC_VERSION.to_string(),
175 id: Value::Null,
176 result: None,
177 error: Some(JsonRpcError {
178 code: -32700,
179 message: "Parse error".to_string(),
180 data: None,
181 }),
182 });
183 }
184 };
185 let response_id = raw_request
186 .as_object()
187 .and_then(|object| object.get("id"))
188 .cloned()
189 .unwrap_or(Value::Null);
190 let request: JsonRpcRequest = match serde_json::from_value(raw_request) {
191 Ok(request) => request,
192 Err(_) => {
193 return serialize_response(&JsonRpcResponse {
194 jsonrpc: JSONRPC_VERSION.to_string(),
195 id: response_id,
196 result: None,
197 error: Some(JsonRpcError {
198 code: -32600,
199 message: "Invalid Request".to_string(),
200 data: None,
201 }),
202 });
203 }
204 };
205 let is_notification = request.id.is_none();
206 let response_id = request.id.clone().unwrap_or(Value::Null);
207
208 if request.jsonrpc != "2.0" {
209 let response = JsonRpcResponse {
210 jsonrpc: JSONRPC_VERSION.to_string(),
211 id: response_id,
212 result: None,
213 error: Some(JsonRpcError {
214 code: -32600,
215 message: "Invalid Request".to_string(),
216 data: None,
217 }),
218 };
219 return if is_notification {
220 String::new()
221 } else {
222 serialize_response(&response)
223 };
224 }
225
226 let response = match request.method.as_str() {
227 "mobkit/status" => JsonRpcResponse {
228 jsonrpc: JSONRPC_VERSION.to_string(),
229 id: response_id,
230 result: Some(serde_json::json!({
231 "contract_version": MOBKIT_CONTRACT_VERSION,
232 "running": runtime.is_running(),
233 "loaded_modules": runtime.loaded_modules(),
234 })),
235 error: None,
236 },
237 "mobkit/capabilities" => JsonRpcResponse {
238 jsonrpc: JSONRPC_VERSION.to_string(),
239 id: response_id,
240 result: Some(serde_json::json!({
241 "contract_version": MOBKIT_CONTRACT_VERSION,
242 "methods": [
243 "mobkit/status",
244 "mobkit/capabilities",
245 "mobkit/reconcile",
246 "mobkit/spawn_member",
247 "mobkit/scheduling/evaluate",
248 "mobkit/scheduling/dispatch",
249 "mobkit/routing/resolve",
250 "mobkit/routing/routes/list",
251 "mobkit/routing/routes/add",
252 "mobkit/routing/routes/delete",
253 "mobkit/delivery/send",
254 "mobkit/delivery/history",
255 "mobkit/events/subscribe",
256 "mobkit/memory/stores",
257 "mobkit/memory/index",
258 "mobkit/memory/query",
259 "mobkit/session_store/bigquery",
260 "mobkit/gating/evaluate",
261 "mobkit/gating/pending",
262 "mobkit/gating/decide",
263 "mobkit/gating/audit",
264 "mobkit/call_tool",
265 "mobkit/models/catalog"
266 ],
267 "loaded_modules": runtime.loaded_modules(),
268 "runtime_capabilities": {
269 "can_spawn_members": false,
270 "can_send_messages": false,
271 "can_wire_members": false,
272 "can_retire_members": false,
273 "available_spawn_modes": ["module"],
274 }
275 })),
276 error: None,
277 },
278 "mobkit/models/catalog" => JsonRpcResponse {
279 jsonrpc: JSONRPC_VERSION.to_string(),
280 id: response_id,
281 result: Some(build_models_catalog_result()),
282 error: None,
283 },
284 "mobkit/reconcile" => {
285 let modules = match params::required_string_array(&request.params, "modules") {
286 Ok(m) => m,
287 Err(reason) => {
288 return serialize_response(&JsonRpcResponse {
289 jsonrpc: JSONRPC_VERSION.to_string(),
290 id: response_id,
291 result: None,
292 error: Some(JsonRpcError {
293 code: -32602,
294 message: format!("Invalid params: {reason}"),
295 data: None,
296 }),
297 });
298 }
299 };
300
301 match runtime.reconcile_modules(modules.clone(), timeout) {
302 Ok(added) => JsonRpcResponse {
303 jsonrpc: JSONRPC_VERSION.to_string(),
304 id: response_id,
305 result: Some(serde_json::json!({
306 "accepted": true,
307 "reconciled_modules": modules,
308 "added": added
309 })),
310 error: None,
311 },
312 Err(err) => JsonRpcResponse {
313 jsonrpc: JSONRPC_VERSION.to_string(),
314 id: response_id,
315 result: None,
316 error: Some(JsonRpcError {
317 code: -32602,
318 message: format!("Invalid params: {err:?}"),
319 data: None,
320 }),
321 },
322 }
323 }
324 "mobkit/spawn_member" => {
325 let module_id = request
326 .params
327 .get("module_id")
328 .and_then(Value::as_str)
329 .unwrap_or_default()
330 .to_string();
331 if module_id.is_empty() {
332 JsonRpcResponse {
333 jsonrpc: JSONRPC_VERSION.to_string(),
334 id: response_id,
335 result: None,
336 error: Some(JsonRpcError {
337 code: -32602,
338 message: "Invalid params: module_id required".to_string(),
339 data: None,
340 }),
341 }
342 } else {
343 match runtime.spawn_member(&module_id, timeout) {
344 Ok(()) => JsonRpcResponse {
345 jsonrpc: JSONRPC_VERSION.to_string(),
346 id: response_id,
347 result: Some(serde_json::json!({
348 "accepted": true,
349 "module_id": module_id
350 })),
351 error: None,
352 },
353 Err(err) => JsonRpcResponse {
354 jsonrpc: JSONRPC_VERSION.to_string(),
355 id: response_id,
356 result: None,
357 error: Some(JsonRpcError {
358 code: -32602,
359 message: format!("Invalid params: {err:?}"),
360 data: None,
361 }),
362 },
363 }
364 }
365 }
366 "mobkit/scheduling/evaluate" => match parse_scheduling_params(&request.params) {
367 Ok((schedules, tick_ms)) => match runtime.evaluate_schedule_tick(&schedules, tick_ms) {
368 Ok(evaluation) => JsonRpcResponse {
369 jsonrpc: JSONRPC_VERSION.to_string(),
370 id: response_id,
371 result: Some(serde_json::to_value(evaluation).unwrap_or(Value::Null)),
372 error: None,
373 },
374 Err(err) => JsonRpcResponse {
375 jsonrpc: JSONRPC_VERSION.to_string(),
376 id: response_id,
377 result: None,
378 error: Some(JsonRpcError {
379 code: -32602,
380 message: format!(
381 "Invalid params: {}",
382 format_schedule_validation_error(err)
383 ),
384 data: None,
385 }),
386 },
387 },
388 Err(message) => JsonRpcResponse {
389 jsonrpc: JSONRPC_VERSION.to_string(),
390 id: response_id,
391 result: None,
392 error: Some(JsonRpcError {
393 code: -32602,
394 message: format!("Invalid params: {message}"),
395 data: None,
396 }),
397 },
398 },
399 "mobkit/scheduling/dispatch" => match parse_scheduling_params(&request.params) {
400 Ok((schedules, tick_ms)) => match runtime.dispatch_schedule_tick(&schedules, tick_ms) {
401 Ok(dispatch) => JsonRpcResponse {
402 jsonrpc: JSONRPC_VERSION.to_string(),
403 id: response_id,
404 result: Some(serde_json::to_value(dispatch).unwrap_or(Value::Null)),
405 error: None,
406 },
407 Err(err) => JsonRpcResponse {
408 jsonrpc: JSONRPC_VERSION.to_string(),
409 id: response_id,
410 result: None,
411 error: Some(JsonRpcError {
412 code: -32602,
413 message: format!(
414 "Invalid params: {}",
415 format_schedule_validation_error(err)
416 ),
417 data: None,
418 }),
419 },
420 },
421 Err(message) => JsonRpcResponse {
422 jsonrpc: JSONRPC_VERSION.to_string(),
423 id: response_id,
424 result: None,
425 error: Some(JsonRpcError {
426 code: -32602,
427 message: format!("Invalid params: {message}"),
428 data: None,
429 }),
430 },
431 },
432 "mobkit/routing/resolve" => {
433 match parse_routing_resolve_params(&request.params).and_then(|resolve_request| {
434 runtime
435 .resolve_routing(resolve_request)
436 .map_err(RoutingDeliveryParamsError::Routing)
437 }) {
438 Ok(resolution) => JsonRpcResponse {
439 jsonrpc: JSONRPC_VERSION.to_string(),
440 id: response_id,
441 result: Some(serde_json::to_value(resolution).unwrap_or(Value::Null)),
442 error: None,
443 },
444 Err(err) => JsonRpcResponse {
445 jsonrpc: JSONRPC_VERSION.to_string(),
446 id: response_id,
447 result: None,
448 error: Some(JsonRpcError {
449 code: -32602,
450 message: format!("Invalid params: {}", err.message()),
451 data: None,
452 }),
453 },
454 }
455 }
456 "mobkit/routing/routes/list" => match parse_routing_routes_list_params(&request.params) {
457 Ok(()) => JsonRpcResponse {
458 jsonrpc: JSONRPC_VERSION.to_string(),
459 id: response_id,
460 result: Some(serde_json::json!({
461 "routes": runtime.list_runtime_routes()
462 })),
463 error: None,
464 },
465 Err(err) => JsonRpcResponse {
466 jsonrpc: JSONRPC_VERSION.to_string(),
467 id: response_id,
468 result: None,
469 error: Some(JsonRpcError {
470 code: -32602,
471 message: format!("Invalid params: {}", err.message()),
472 data: None,
473 }),
474 },
475 },
476 "mobkit/routing/routes/add" => match parse_routing_route_add_params(&request.params)
477 .and_then(|route| {
478 runtime
479 .add_runtime_route(route)
480 .map_err(RoutingDeliveryParamsError::RouteMutation)
481 }) {
482 Ok(route) => JsonRpcResponse {
483 jsonrpc: JSONRPC_VERSION.to_string(),
484 id: response_id,
485 result: Some(serde_json::json!({ "route": route })),
486 error: None,
487 },
488 Err(err) => JsonRpcResponse {
489 jsonrpc: JSONRPC_VERSION.to_string(),
490 id: response_id,
491 result: None,
492 error: Some(JsonRpcError {
493 code: -32602,
494 message: format!("Invalid params: {}", err.message()),
495 data: None,
496 }),
497 },
498 },
499 "mobkit/routing/routes/delete" => match parse_routing_route_delete_params(&request.params)
500 .and_then(|route_key| {
501 runtime
502 .delete_runtime_route(&route_key)
503 .map_err(RoutingDeliveryParamsError::RouteMutation)
504 }) {
505 Ok(route) => JsonRpcResponse {
506 jsonrpc: JSONRPC_VERSION.to_string(),
507 id: response_id,
508 result: Some(serde_json::json!({ "deleted": route })),
509 error: None,
510 },
511 Err(err) => JsonRpcResponse {
512 jsonrpc: JSONRPC_VERSION.to_string(),
513 id: response_id,
514 result: None,
515 error: Some(JsonRpcError {
516 code: -32602,
517 message: format!("Invalid params: {}", err.message()),
518 data: None,
519 }),
520 },
521 },
522 "mobkit/delivery/send" => {
523 match parse_delivery_send_params(&request.params).and_then(|send_request| {
524 runtime
525 .send_delivery(send_request)
526 .map_err(RoutingDeliveryParamsError::Delivery)
527 }) {
528 Ok(record) => JsonRpcResponse {
529 jsonrpc: JSONRPC_VERSION.to_string(),
530 id: response_id,
531 result: Some(serde_json::to_value(record).unwrap_or(Value::Null)),
532 error: None,
533 },
534 Err(err) => JsonRpcResponse {
535 jsonrpc: JSONRPC_VERSION.to_string(),
536 id: response_id,
537 result: None,
538 error: Some(JsonRpcError {
539 code: -32602,
540 message: format!("Invalid params: {}", err.message()),
541 data: None,
542 }),
543 },
544 }
545 }
546 "mobkit/delivery/history" => match parse_delivery_history_params(&request.params) {
547 Ok(history_request) => JsonRpcResponse {
548 jsonrpc: JSONRPC_VERSION.to_string(),
549 id: response_id,
550 result: Some(
551 serde_json::to_value(runtime.delivery_history(history_request))
552 .unwrap_or(Value::Null),
553 ),
554 error: None,
555 },
556 Err(err) => JsonRpcResponse {
557 jsonrpc: JSONRPC_VERSION.to_string(),
558 id: response_id,
559 result: None,
560 error: Some(JsonRpcError {
561 code: -32602,
562 message: format!("Invalid params: {}", err.message()),
563 data: None,
564 }),
565 },
566 },
567 "mobkit/events/subscribe" => {
568 match parse_subscribe_request(&request.params).and_then(|subscribe_request| {
569 runtime
570 .subscribe_events(subscribe_request)
571 .map_err(SubscribeParamsError::Runtime)
572 }) {
573 Ok(subscribe_result) => JsonRpcResponse {
574 jsonrpc: JSONRPC_VERSION.to_string(),
575 id: response_id,
576 result: Some(serde_json::to_value(subscribe_result).unwrap_or(Value::Null)),
577 error: None,
578 },
579 Err(err) => JsonRpcResponse {
580 jsonrpc: JSONRPC_VERSION.to_string(),
581 id: response_id,
582 result: None,
583 error: Some(JsonRpcError {
584 code: -32602,
585 message: format!("Invalid params: {}", err.message()),
586 data: None,
587 }),
588 },
589 }
590 }
591 "mobkit/memory/stores" => match parse_memory_stores_params(&request.params) {
592 Ok(()) => JsonRpcResponse {
593 jsonrpc: JSONRPC_VERSION.to_string(),
594 id: response_id,
595 result: Some(serde_json::json!({
596 "stores": runtime.memory_stores(),
597 })),
598 error: None,
599 },
600 Err(err) => JsonRpcResponse {
601 jsonrpc: JSONRPC_VERSION.to_string(),
602 id: response_id,
603 result: None,
604 error: Some(JsonRpcError {
605 code: -32602,
606 message: format!("Invalid params: {}", err.message()),
607 data: None,
608 }),
609 },
610 },
611 "mobkit/memory/index" => match parse_memory_index_params(&request.params) {
612 Ok(index_request) => match runtime.memory_index(index_request) {
613 Ok(indexed) => JsonRpcResponse {
614 jsonrpc: JSONRPC_VERSION.to_string(),
615 id: response_id,
616 result: Some(serde_json::to_value(indexed).unwrap_or(Value::Null)),
617 error: None,
618 },
619 Err(MemoryIndexError::BackendPersistFailed(error)) => JsonRpcResponse {
620 jsonrpc: JSONRPC_VERSION.to_string(),
621 id: response_id,
622 result: None,
623 error: Some(JsonRpcError {
624 code: MEMORY_BACKEND_UNAVAILABLE_CODE,
625 message: format!(
626 "Memory backend unavailable: {}",
627 MemoryParamsError::backend_message(&error)
628 ),
629 data: None,
630 }),
631 },
632 Err(err) => JsonRpcResponse {
633 jsonrpc: JSONRPC_VERSION.to_string(),
634 id: response_id,
635 result: None,
636 error: Some(JsonRpcError {
637 code: -32602,
638 message: format!(
639 "Invalid params: {}",
640 MemoryParamsError::Index(err).message()
641 ),
642 data: None,
643 }),
644 },
645 },
646 Err(err) => JsonRpcResponse {
647 jsonrpc: JSONRPC_VERSION.to_string(),
648 id: response_id,
649 result: None,
650 error: Some(JsonRpcError {
651 code: -32602,
652 message: format!("Invalid params: {}", err.message()),
653 data: None,
654 }),
655 },
656 },
657 "mobkit/memory/query" => match parse_memory_query_params(&request.params) {
658 Ok(query_request) => JsonRpcResponse {
659 jsonrpc: JSONRPC_VERSION.to_string(),
660 id: response_id,
661 result: Some(
662 serde_json::to_value(runtime.memory_query(query_request))
663 .unwrap_or(Value::Null),
664 ),
665 error: None,
666 },
667 Err(err) => JsonRpcResponse {
668 jsonrpc: JSONRPC_VERSION.to_string(),
669 id: response_id,
670 result: None,
671 error: Some(JsonRpcError {
672 code: -32602,
673 message: format!("Invalid params: {}", err.message()),
674 data: None,
675 }),
676 },
677 },
678 "mobkit/session_store/bigquery" => {
679 match parse_bigquery_session_store_params(&request.params)
680 .and_then(run_bigquery_session_store_request)
681 {
682 Ok(result) => JsonRpcResponse {
683 jsonrpc: JSONRPC_VERSION.to_string(),
684 id: response_id,
685 result: Some(result),
686 error: None,
687 },
688 Err(BigQuerySessionStoreRpcError::Params(message)) => JsonRpcResponse {
689 jsonrpc: JSONRPC_VERSION.to_string(),
690 id: response_id,
691 result: None,
692 error: Some(JsonRpcError {
693 code: -32602,
694 message: format!("Invalid params: {message}"),
695 data: None,
696 }),
697 },
698 Err(BigQuerySessionStoreRpcError::Store(error)) => JsonRpcResponse {
699 jsonrpc: JSONRPC_VERSION.to_string(),
700 id: response_id,
701 result: None,
702 error: Some(JsonRpcError {
703 code: -32011,
704 message: format!(
705 "BigQuery session store request failed: {}",
706 format_bigquery_store_error(&error)
707 ),
708 data: None,
709 }),
710 },
711 }
712 }
713 "mobkit/gating/evaluate" => match parse_gating_evaluate_params(&request.params) {
714 Ok(gating_request) => JsonRpcResponse {
715 jsonrpc: JSONRPC_VERSION.to_string(),
716 id: response_id,
717 result: Some(
718 serde_json::to_value(runtime.evaluate_gating_action(gating_request))
719 .unwrap_or(Value::Null),
720 ),
721 error: None,
722 },
723 Err(err) => JsonRpcResponse {
724 jsonrpc: JSONRPC_VERSION.to_string(),
725 id: response_id,
726 result: None,
727 error: Some(JsonRpcError {
728 code: -32602,
729 message: format!("Invalid params: {}", err.message()),
730 data: None,
731 }),
732 },
733 },
734 "mobkit/gating/pending" => match parse_gating_pending_params(&request.params) {
735 Ok(()) => JsonRpcResponse {
736 jsonrpc: JSONRPC_VERSION.to_string(),
737 id: response_id,
738 result: Some(serde_json::json!({
739 "pending": runtime.list_gating_pending(),
740 })),
741 error: None,
742 },
743 Err(err) => JsonRpcResponse {
744 jsonrpc: JSONRPC_VERSION.to_string(),
745 id: response_id,
746 result: None,
747 error: Some(JsonRpcError {
748 code: -32602,
749 message: format!("Invalid params: {}", err.message()),
750 data: None,
751 }),
752 },
753 },
754 "mobkit/gating/decide" => {
755 match parse_gating_decide_params(&request.params).and_then(|decide_request| {
756 runtime
757 .decide_gating_action(decide_request)
758 .map_err(GatingParamsError::Decision)
759 }) {
760 Ok(result) => JsonRpcResponse {
761 jsonrpc: JSONRPC_VERSION.to_string(),
762 id: response_id,
763 result: Some(serde_json::to_value(result).unwrap_or(Value::Null)),
764 error: None,
765 },
766 Err(err) => JsonRpcResponse {
767 jsonrpc: JSONRPC_VERSION.to_string(),
768 id: response_id,
769 result: None,
770 error: Some(JsonRpcError {
771 code: -32602,
772 message: format!("Invalid params: {}", err.message()),
773 data: None,
774 }),
775 },
776 }
777 }
778 "mobkit/gating/audit" => match parse_gating_audit_params(&request.params) {
779 Ok(limit) => JsonRpcResponse {
780 jsonrpc: JSONRPC_VERSION.to_string(),
781 id: response_id,
782 result: Some(serde_json::json!({
783 "entries": runtime.gating_audit_entries(limit),
784 })),
785 error: None,
786 },
787 Err(err) => JsonRpcResponse {
788 jsonrpc: JSONRPC_VERSION.to_string(),
789 id: response_id,
790 result: None,
791 error: Some(JsonRpcError {
792 code: -32602,
793 message: format!("Invalid params: {}", err.message()),
794 data: None,
795 }),
796 },
797 },
798 "mobkit/call_tool" => {
799 let module_id = request.params.get("module_id").and_then(Value::as_str);
800 let tool = request.params.get("tool").and_then(Value::as_str);
801 let arguments = request
802 .params
803 .get("arguments")
804 .cloned()
805 .unwrap_or(serde_json::json!({}));
806
807 match (module_id, tool) {
808 (Some(module_id), Some(tool)) if !module_id.is_empty() && !tool.is_empty() => {
809 let route = route_module_call(
810 runtime,
811 &ModuleRouteRequest {
812 module_id: module_id.to_string(),
813 method: tool.to_string(),
814 params: arguments,
815 },
816 timeout,
817 );
818 match route {
819 Ok(response) => JsonRpcResponse {
820 jsonrpc: JSONRPC_VERSION.to_string(),
821 id: response_id,
822 result: Some(serde_json::json!({
823 "module_id": response.module_id,
824 "tool": response.method,
825 "result": response.payload
826 })),
827 error: None,
828 },
829 Err(ModuleRouteError::UnloadedModule(mid)) => JsonRpcResponse {
830 jsonrpc: JSONRPC_VERSION.to_string(),
831 id: response_id,
832 result: None,
833 error: Some(JsonRpcError {
834 code: -32601,
835 message: format!("Module '{mid}' not loaded"),
836 data: None,
837 }),
838 },
839 Err(err) => JsonRpcResponse {
840 jsonrpc: JSONRPC_VERSION.to_string(),
841 id: response_id,
842 result: None,
843 error: Some(JsonRpcError {
844 code: -32000,
845 message: format!("Tool call failed: {err:?}"),
846 data: None,
847 }),
848 },
849 }
850 }
851 _ => JsonRpcResponse {
852 jsonrpc: JSONRPC_VERSION.to_string(),
853 id: response_id,
854 result: None,
855 error: Some(JsonRpcError {
856 code: -32602,
857 message: "Invalid params: module_id and tool required".to_string(),
858 data: None,
859 }),
860 },
861 }
862 }
863 method if method.contains('/') && !method.starts_with("mobkit/") => {
864 let module_id = method
865 .split('/')
866 .next()
867 .map(ToString::to_string)
868 .unwrap_or_default();
869 let route = route_module_call(
870 runtime,
871 &ModuleRouteRequest {
872 module_id,
873 method: method.to_string(),
874 params: request.params,
875 },
876 timeout,
877 );
878 match route {
879 Ok(response) => JsonRpcResponse {
880 jsonrpc: JSONRPC_VERSION.to_string(),
881 id: response_id,
882 result: Some(serde_json::json!({
883 "module_id": response.module_id,
884 "method": response.method,
885 "payload": response.payload
886 })),
887 error: None,
888 },
889 Err(ModuleRouteError::UnloadedModule(module_id)) => JsonRpcResponse {
890 jsonrpc: JSONRPC_VERSION.to_string(),
891 id: response_id,
892 result: None,
893 error: Some(JsonRpcError {
894 code: -32601,
895 message: format!("Module '{module_id}' not loaded"),
896 data: None,
897 }),
898 },
899 Err(err) => JsonRpcResponse {
900 jsonrpc: JSONRPC_VERSION.to_string(),
901 id: response_id,
902 result: None,
903 error: Some(JsonRpcError {
904 code: -32000,
905 message: format!("Module route failed: {err:?}"),
906 data: None,
907 }),
908 },
909 }
910 }
911 _ => JsonRpcResponse {
912 jsonrpc: JSONRPC_VERSION.to_string(),
913 id: response_id,
914 result: None,
915 error: Some(JsonRpcError {
916 code: -32601,
917 message: "Method not found".to_string(),
918 data: None,
919 }),
920 },
921 };
922 if is_notification {
923 String::new()
924 } else {
925 serialize_response(&response)
926 }
927}
928
929pub struct IdentityFirstContext {
931 pub runtime: std::sync::Arc<crate::identity_first::IdentityRuntime>,
932 pub roster_provider: std::sync::Arc<dyn crate::identity_first::contracts::RosterProvider>,
933 pub topology_provider:
934 Option<std::sync::Arc<dyn crate::identity_first::contracts::TopologyProvider>>,
935 pub customizer: Option<std::sync::Arc<dyn crate::identity_first::contracts::AgentCustomizer>>,
936}
937
938pub async fn handle_unified_rpc_json(
939 runtime: &UnifiedRuntime,
940 request_json: &str,
941 timeout: Duration,
942 http_base_url: Option<&str>,
943 identity_ctx: Option<&IdentityFirstContext>,
944) -> String {
945 let raw_request: Value = match serde_json::from_str(request_json) {
946 Ok(raw_request) => raw_request,
947 Err(_) => {
948 return serialize_response(&JsonRpcResponse {
949 jsonrpc: JSONRPC_VERSION.to_string(),
950 id: Value::Null,
951 result: None,
952 error: Some(JsonRpcError {
953 code: -32700,
954 message: "Parse error".to_string(),
955 data: None,
956 }),
957 });
958 }
959 };
960 let response_id = raw_request
961 .as_object()
962 .and_then(|object| object.get("id"))
963 .cloned()
964 .unwrap_or(Value::Null);
965 let request: JsonRpcRequest = match serde_json::from_value(raw_request) {
966 Ok(request) => request,
967 Err(_) => {
968 return serialize_response(&JsonRpcResponse {
969 jsonrpc: JSONRPC_VERSION.to_string(),
970 id: response_id,
971 result: None,
972 error: Some(JsonRpcError {
973 code: -32600,
974 message: "Invalid Request".to_string(),
975 data: None,
976 }),
977 });
978 }
979 };
980 let is_notification = request.id.is_none();
981 let response_id = request.id.clone().unwrap_or(Value::Null);
982
983 if request.jsonrpc != "2.0" {
984 let response = JsonRpcResponse {
985 jsonrpc: JSONRPC_VERSION.to_string(),
986 id: response_id,
987 result: None,
988 error: Some(JsonRpcError {
989 code: -32600,
990 message: "Invalid Request".to_string(),
991 data: None,
992 }),
993 };
994 return if is_notification {
995 String::new()
996 } else {
997 serialize_response(&response)
998 };
999 }
1000
1001 let response = match request.method.as_str() {
1002 "mobkit/status" => {
1003 let mob_state = runtime.mob_handle().status().await.ok();
1004 let is_running = runtime.module_is_running().await;
1005 let loaded = runtime.loaded_modules().await;
1006 let mut result = serde_json::json!({
1007 "contract_version": MOBKIT_CONTRACT_VERSION,
1008 "running": is_running,
1009 "loaded_modules": loaded,
1010 "mob_state": format!("{mob_state:?}"),
1011 });
1012 if let Some(url) = http_base_url {
1013 result["http_base_url"] = Value::String(url.to_string());
1014 }
1015 JsonRpcResponse {
1016 jsonrpc: JSONRPC_VERSION.to_string(),
1017 id: response_id,
1018 result: Some(result),
1019 error: None,
1020 }
1021 }
1022 "mobkit/capabilities" => {
1023 let loaded = runtime.loaded_modules().await;
1024 let mut methods = vec![
1025 "mobkit/init",
1026 "mobkit/status",
1027 "mobkit/capabilities",
1028 "mobkit/reconcile",
1029 "mobkit/spawn_member",
1030 "mobkit/scheduling/evaluate",
1031 "mobkit/scheduling/dispatch",
1032 "mobkit/routing/resolve",
1033 "mobkit/routing/routes/list",
1034 "mobkit/routing/routes/add",
1035 "mobkit/routing/routes/delete",
1036 "mobkit/delivery/send",
1037 "mobkit/delivery/history",
1038 "mobkit/events/subscribe",
1039 "mobkit/query_events",
1040 "mobkit/memory/stores",
1041 "mobkit/memory/index",
1042 "mobkit/memory/query",
1043 "mobkit/session_store/bigquery",
1044 "mobkit/gating/evaluate",
1045 "mobkit/gating/pending",
1046 "mobkit/gating/decide",
1047 "mobkit/gating/audit",
1048 "mobkit/call_tool",
1049 "mobkit/models/catalog",
1050 "mobkit/blob/get",
1051 "mobkit/send_message",
1052 "mobkit/find_members",
1053 "mobkit/ensure_member",
1054 "mobkit/list_members",
1055 "mobkit/get_member",
1056 "mobkit/retire_member",
1057 "mobkit/respawn_member",
1058 "mobkit/reconcile_edges",
1059 "mobkit/rediscover",
1060 "mobkit/mob_events/query",
1061 "mobkit/mob_events/subscribe",
1062 "mobkit/cross_mob/peer_info",
1064 "mobkit/cross_mob/wire_local",
1065 "mobkit/cross_mob/unwire_local",
1066 "mobkit/peer_pubkey",
1067 "mobkit/member_status",
1068 "mobkit/force_cancel_member",
1069 "mobkit/spawn_helper",
1070 "mobkit/fork_helper",
1071 "mobkit/attach_existing_session",
1072 "mobkit/cancel_flow",
1073 "mobkit/flow_status",
1074 "mobkit/list_flows",
1075 "mobkit/list_runs",
1076 "mobkit/run_flow",
1077 "mobkit/collect_completed",
1078 "mobkit/wait_ready",
1079 "mobkit/mob_labels/set",
1080 "mobkit/mob_labels/get",
1081 "mobkit/mob_labels/delete",
1082 "mobkit/run_labels/set",
1083 "mobkit/run_labels/get",
1084 "mobkit/run_labels/delete",
1085 ];
1086 if identity_ctx.is_some() {
1087 methods.extend_from_slice(&[
1088 "mobkit/send",
1089 "mobkit/interact",
1090 "mobkit/dispatch",
1091 "mobkit/subscribe",
1092 "mobkit/status_identity",
1093 "mobkit/respawn",
1094 "mobkit/retire",
1095 "mobkit/reset",
1096 "mobkit/delete_identity",
1097 "mobkit/inspect_identity",
1098 "mobkit/reconcile_identity",
1099 ]);
1100 }
1101 if runtime.has_contact_directory() {
1103 methods.push("mobkit/cross_mob/directory");
1104 }
1105 if runtime.has_peer_mob_handles().await && runtime.has_inproc_contacts() {
1109 methods.extend_from_slice(&[
1110 "mobkit/cross_mob/wire",
1111 "mobkit/cross_mob/unwire",
1112 "mobkit/cross_mob/send",
1113 ]);
1114 }
1115 JsonRpcResponse {
1116 jsonrpc: JSONRPC_VERSION.to_string(),
1117 id: response_id,
1118 result: Some(serde_json::json!({
1119 "contract_version": MOBKIT_CONTRACT_VERSION,
1120 "runtime_type": "unified",
1121 "methods": methods,
1122 "loaded_modules": loaded,
1123 "runtime_capabilities": {
1124 "can_spawn_members": true,
1125 "can_send_messages": true,
1126 "can_wire_members": true,
1127 "can_retire_members": true,
1128 "available_spawn_modes": ["module", "profile"],
1129 }
1130 })),
1131 error: None,
1132 }
1133 }
1134 "mobkit/reconcile" => {
1135 let modules = match params::required_string_array(&request.params, "modules") {
1136 Ok(m) => m,
1137 Err(reason) => {
1138 return serialize_response(&JsonRpcResponse {
1139 jsonrpc: JSONRPC_VERSION.to_string(),
1140 id: response_id,
1141 result: None,
1142 error: Some(JsonRpcError {
1143 code: -32602,
1144 message: format!("Invalid params: {reason}"),
1145 data: None,
1146 }),
1147 });
1148 }
1149 };
1150
1151 match runtime.reconcile_modules(modules.clone(), timeout).await {
1152 Ok(added) => JsonRpcResponse {
1153 jsonrpc: JSONRPC_VERSION.to_string(),
1154 id: response_id,
1155 result: Some(serde_json::json!({
1156 "accepted": true,
1157 "reconciled_modules": modules,
1158 "added": added
1159 })),
1160 error: None,
1161 },
1162 Err(err) => JsonRpcResponse {
1163 jsonrpc: JSONRPC_VERSION.to_string(),
1164 id: response_id,
1165 result: None,
1166 error: Some(JsonRpcError {
1167 code: -32602,
1168 message: format!("Invalid params: {err:?}"),
1169 data: None,
1170 }),
1171 },
1172 }
1173 }
1174 "mobkit/spawn_member" => {
1175 let module_id = request.params.get("module_id").and_then(Value::as_str);
1177 let profile = request.params.get("profile").and_then(Value::as_str);
1178 let meerkat_id = request.params.get("meerkat_id").and_then(Value::as_str);
1179
1180 if let Some(module_id) = module_id {
1181 if module_id.is_empty() {
1183 JsonRpcResponse {
1184 jsonrpc: JSONRPC_VERSION.to_string(),
1185 id: response_id,
1186 result: None,
1187 error: Some(JsonRpcError {
1188 code: -32602,
1189 message: "Invalid params: module_id required".to_string(),
1190 data: None,
1191 }),
1192 }
1193 } else {
1194 match runtime.spawn_member(module_id, timeout).await {
1195 Ok(()) => JsonRpcResponse {
1196 jsonrpc: JSONRPC_VERSION.to_string(),
1197 id: response_id,
1198 result: Some(serde_json::json!({
1199 "accepted": true,
1200 "module_id": module_id
1201 })),
1202 error: None,
1203 },
1204 Err(err) => JsonRpcResponse {
1205 jsonrpc: JSONRPC_VERSION.to_string(),
1206 id: response_id,
1207 result: None,
1208 error: Some(JsonRpcError {
1209 code: -32602,
1210 message: format!("Invalid params: {err:?}"),
1211 data: None,
1212 }),
1213 },
1214 }
1215 }
1216 } else if let (Some(profile), Some(meerkat_id)) = (profile, meerkat_id) {
1217 let spec = meerkat_mob::SpawnMemberSpec::from_wire(
1219 profile.to_string(),
1220 meerkat_id.to_string(),
1221 request
1222 .params
1223 .get("initial_message")
1224 .and_then(Value::as_str)
1225 .map(|s| meerkat_core::ContentInput::from(s.to_string())),
1226 None,
1227 None,
1228 );
1229 match runtime.spawn(spec).await {
1230 Ok(_member_ref) => JsonRpcResponse {
1231 jsonrpc: JSONRPC_VERSION.to_string(),
1232 id: response_id,
1233 result: Some(serde_json::json!({
1234 "accepted": true,
1235 "meerkat_id": meerkat_id
1236 })),
1237 error: None,
1238 },
1239 Err(err) => JsonRpcResponse {
1240 jsonrpc: JSONRPC_VERSION.to_string(),
1241 id: response_id,
1242 result: None,
1243 error: Some(JsonRpcError {
1244 code: -32602,
1245 message: format!("Invalid params: {err}"),
1246 data: None,
1247 }),
1248 },
1249 }
1250 } else {
1251 JsonRpcResponse {
1252 jsonrpc: JSONRPC_VERSION.to_string(),
1253 id: response_id,
1254 result: None,
1255 error: Some(JsonRpcError {
1256 code: -32602,
1257 message: "Invalid params: module_id or (profile + meerkat_id) required"
1258 .to_string(),
1259 data: None,
1260 }),
1261 }
1262 }
1263 }
1264 "mobkit/scheduling/evaluate" => match parse_scheduling_params(&request.params) {
1265 Ok((schedules, tick_ms)) => {
1266 match runtime.evaluate_schedule_tick(&schedules, tick_ms).await {
1267 Ok(evaluation) => JsonRpcResponse {
1268 jsonrpc: JSONRPC_VERSION.to_string(),
1269 id: response_id,
1270 result: Some(serde_json::to_value(evaluation).unwrap_or(Value::Null)),
1271 error: None,
1272 },
1273 Err(err) => JsonRpcResponse {
1274 jsonrpc: JSONRPC_VERSION.to_string(),
1275 id: response_id,
1276 result: None,
1277 error: Some(JsonRpcError {
1278 code: -32602,
1279 message: format!(
1280 "Invalid params: {}",
1281 format_schedule_validation_error(err)
1282 ),
1283 data: None,
1284 }),
1285 },
1286 }
1287 }
1288 Err(message) => JsonRpcResponse {
1289 jsonrpc: JSONRPC_VERSION.to_string(),
1290 id: response_id,
1291 result: None,
1292 error: Some(JsonRpcError {
1293 code: -32602,
1294 message: format!("Invalid params: {message}"),
1295 data: None,
1296 }),
1297 },
1298 },
1299 "mobkit/scheduling/dispatch" => match parse_scheduling_params(&request.params) {
1300 Ok((schedules, tick_ms)) => {
1301 match runtime.dispatch_schedule_tick(&schedules, tick_ms).await {
1302 Ok(dispatch) => JsonRpcResponse {
1303 jsonrpc: JSONRPC_VERSION.to_string(),
1304 id: response_id,
1305 result: Some(serde_json::to_value(dispatch).unwrap_or(Value::Null)),
1306 error: None,
1307 },
1308 Err(err) => JsonRpcResponse {
1309 jsonrpc: JSONRPC_VERSION.to_string(),
1310 id: response_id,
1311 result: None,
1312 error: Some(JsonRpcError {
1313 code: -32602,
1314 message: format!("Invalid params: {err}"),
1315 data: None,
1316 }),
1317 },
1318 }
1319 }
1320 Err(message) => JsonRpcResponse {
1321 jsonrpc: JSONRPC_VERSION.to_string(),
1322 id: response_id,
1323 result: None,
1324 error: Some(JsonRpcError {
1325 code: -32602,
1326 message: format!("Invalid params: {message}"),
1327 data: None,
1328 }),
1329 },
1330 },
1331 "mobkit/routing/resolve" => {
1332 let resolve_result = match parse_routing_resolve_params(&request.params) {
1333 Ok(resolve_request) => runtime
1334 .resolve_routing(resolve_request)
1335 .await
1336 .map_err(RoutingDeliveryParamsError::Routing),
1337 Err(e) => Err(e),
1338 };
1339 match resolve_result {
1340 Ok(resolution) => JsonRpcResponse {
1341 jsonrpc: JSONRPC_VERSION.to_string(),
1342 id: response_id,
1343 result: Some(serde_json::to_value(resolution).unwrap_or(Value::Null)),
1344 error: None,
1345 },
1346 Err(err) => JsonRpcResponse {
1347 jsonrpc: JSONRPC_VERSION.to_string(),
1348 id: response_id,
1349 result: None,
1350 error: Some(JsonRpcError {
1351 code: -32602,
1352 message: format!("Invalid params: {}", err.message()),
1353 data: None,
1354 }),
1355 },
1356 }
1357 }
1358 "mobkit/routing/routes/list" => match parse_routing_routes_list_params(&request.params) {
1359 Ok(()) => {
1360 let routes = runtime.list_runtime_routes().await;
1361 JsonRpcResponse {
1362 jsonrpc: JSONRPC_VERSION.to_string(),
1363 id: response_id,
1364 result: Some(serde_json::json!({
1365 "routes": routes
1366 })),
1367 error: None,
1368 }
1369 }
1370 Err(err) => JsonRpcResponse {
1371 jsonrpc: JSONRPC_VERSION.to_string(),
1372 id: response_id,
1373 result: None,
1374 error: Some(JsonRpcError {
1375 code: -32602,
1376 message: format!("Invalid params: {}", err.message()),
1377 data: None,
1378 }),
1379 },
1380 },
1381 "mobkit/routing/routes/add" => {
1382 let add_result = match parse_routing_route_add_params(&request.params) {
1383 Ok(route) => runtime
1384 .add_runtime_route(route)
1385 .await
1386 .map_err(RoutingDeliveryParamsError::RouteMutation),
1387 Err(e) => Err(e),
1388 };
1389 match add_result {
1390 Ok(route) => JsonRpcResponse {
1391 jsonrpc: JSONRPC_VERSION.to_string(),
1392 id: response_id,
1393 result: Some(serde_json::json!({ "route": route })),
1394 error: None,
1395 },
1396 Err(err) => JsonRpcResponse {
1397 jsonrpc: JSONRPC_VERSION.to_string(),
1398 id: response_id,
1399 result: None,
1400 error: Some(JsonRpcError {
1401 code: -32602,
1402 message: format!("Invalid params: {}", err.message()),
1403 data: None,
1404 }),
1405 },
1406 }
1407 }
1408 "mobkit/routing/routes/delete" => {
1409 let delete_result = match parse_routing_route_delete_params(&request.params) {
1410 Ok(route_key) => runtime
1411 .delete_runtime_route(&route_key)
1412 .await
1413 .map_err(RoutingDeliveryParamsError::RouteMutation),
1414 Err(e) => Err(e),
1415 };
1416 match delete_result {
1417 Ok(route) => JsonRpcResponse {
1418 jsonrpc: JSONRPC_VERSION.to_string(),
1419 id: response_id,
1420 result: Some(serde_json::json!({ "deleted": route })),
1421 error: None,
1422 },
1423 Err(err) => JsonRpcResponse {
1424 jsonrpc: JSONRPC_VERSION.to_string(),
1425 id: response_id,
1426 result: None,
1427 error: Some(JsonRpcError {
1428 code: -32602,
1429 message: format!("Invalid params: {}", err.message()),
1430 data: None,
1431 }),
1432 },
1433 }
1434 }
1435 "mobkit/delivery/send" => {
1436 let send_result = match parse_delivery_send_params(&request.params) {
1437 Ok(send_request) => runtime
1438 .send_delivery(send_request)
1439 .await
1440 .map_err(RoutingDeliveryParamsError::Delivery),
1441 Err(e) => Err(e),
1442 };
1443 match send_result {
1444 Ok(record) => JsonRpcResponse {
1445 jsonrpc: JSONRPC_VERSION.to_string(),
1446 id: response_id,
1447 result: Some(serde_json::to_value(record).unwrap_or(Value::Null)),
1448 error: None,
1449 },
1450 Err(err) => JsonRpcResponse {
1451 jsonrpc: JSONRPC_VERSION.to_string(),
1452 id: response_id,
1453 result: None,
1454 error: Some(JsonRpcError {
1455 code: -32602,
1456 message: format!("Invalid params: {}", err.message()),
1457 data: None,
1458 }),
1459 },
1460 }
1461 }
1462 "mobkit/delivery/history" => match parse_delivery_history_params(&request.params) {
1463 Ok(history_request) => {
1464 let history = runtime.delivery_history(history_request).await;
1465 JsonRpcResponse {
1466 jsonrpc: JSONRPC_VERSION.to_string(),
1467 id: response_id,
1468 result: Some(serde_json::to_value(history).unwrap_or(Value::Null)),
1469 error: None,
1470 }
1471 }
1472 Err(err) => JsonRpcResponse {
1473 jsonrpc: JSONRPC_VERSION.to_string(),
1474 id: response_id,
1475 result: None,
1476 error: Some(JsonRpcError {
1477 code: -32602,
1478 message: format!("Invalid params: {}", err.message()),
1479 data: None,
1480 }),
1481 },
1482 },
1483 "mobkit/events/subscribe" => match parse_subscribe_request(&request.params) {
1484 Ok(subscribe_request) => match runtime.subscribe_events(subscribe_request).await {
1485 Ok(subscribe_result) => JsonRpcResponse {
1486 jsonrpc: JSONRPC_VERSION.to_string(),
1487 id: response_id,
1488 result: Some(serde_json::to_value(subscribe_result).unwrap_or(Value::Null)),
1489 error: None,
1490 },
1491 Err(err) => JsonRpcResponse {
1492 jsonrpc: JSONRPC_VERSION.to_string(),
1493 id: response_id,
1494 result: None,
1495 error: Some(JsonRpcError {
1496 code: -32602,
1497 message: format!("Invalid params: {err}"),
1498 data: None,
1499 }),
1500 },
1501 },
1502 Err(err) => JsonRpcResponse {
1503 jsonrpc: JSONRPC_VERSION.to_string(),
1504 id: response_id,
1505 result: None,
1506 error: Some(JsonRpcError {
1507 code: -32602,
1508 message: format!("Invalid params: {}", err.message()),
1509 data: None,
1510 }),
1511 },
1512 },
1513 "mobkit/query_events" => {
1514 let query: EventQuery = if request.params.is_null() {
1515 EventQuery::default()
1516 } else {
1517 match serde_json::from_value(request.params.clone()) {
1518 Ok(query) => query,
1519 Err(err) => {
1520 return serde_json::to_string(&JsonRpcResponse {
1521 jsonrpc: JSONRPC_VERSION.to_string(),
1522 id: response_id,
1523 result: None,
1524 error: Some(JsonRpcError {
1525 code: -32602,
1526 message: format!("Invalid params: invalid query params: {err}"),
1527 data: None,
1528 }),
1529 })
1530 .unwrap_or_else(|_| r#"{"error":"serialization failed"}"#.to_string());
1531 }
1532 }
1533 };
1534 match runtime.event_log_store() {
1535 Some(store) => match store.query(query).await {
1536 Ok(events) => JsonRpcResponse {
1537 jsonrpc: JSONRPC_VERSION.to_string(),
1538 id: response_id,
1539 result: Some(serde_json::to_value(events).unwrap_or(Value::Null)),
1540 error: None,
1541 },
1542 Err(err) => JsonRpcResponse {
1543 jsonrpc: JSONRPC_VERSION.to_string(),
1544 id: response_id,
1545 result: None,
1546 error: Some(JsonRpcError {
1547 code: -32603,
1548 message: format!("query_events failed: {err}"),
1549 data: None,
1550 }),
1551 },
1552 },
1553 None => JsonRpcResponse {
1554 jsonrpc: JSONRPC_VERSION.to_string(),
1555 id: response_id,
1556 result: Some(serde_json::json!({
1557 "status": "no_event_log_configured",
1558 "events": [],
1559 })),
1560 error: None,
1561 },
1562 }
1563 }
1564 "mobkit/memory/stores" => match parse_memory_stores_params(&request.params) {
1565 Ok(()) => {
1566 let stores = runtime.memory_stores().await;
1567 JsonRpcResponse {
1568 jsonrpc: JSONRPC_VERSION.to_string(),
1569 id: response_id,
1570 result: Some(serde_json::json!({
1571 "stores": stores,
1572 })),
1573 error: None,
1574 }
1575 }
1576 Err(err) => JsonRpcResponse {
1577 jsonrpc: JSONRPC_VERSION.to_string(),
1578 id: response_id,
1579 result: None,
1580 error: Some(JsonRpcError {
1581 code: -32602,
1582 message: format!("Invalid params: {}", err.message()),
1583 data: None,
1584 }),
1585 },
1586 },
1587 "mobkit/memory/index" => match parse_memory_index_params(&request.params) {
1588 Ok(index_request) => match runtime.memory_index(index_request).await {
1589 Ok(indexed) => JsonRpcResponse {
1590 jsonrpc: JSONRPC_VERSION.to_string(),
1591 id: response_id,
1592 result: Some(serde_json::to_value(indexed).unwrap_or(Value::Null)),
1593 error: None,
1594 },
1595 Err(MemoryIndexError::BackendPersistFailed(error)) => JsonRpcResponse {
1596 jsonrpc: JSONRPC_VERSION.to_string(),
1597 id: response_id,
1598 result: None,
1599 error: Some(JsonRpcError {
1600 code: MEMORY_BACKEND_UNAVAILABLE_CODE,
1601 message: format!(
1602 "Memory backend unavailable: {}",
1603 MemoryParamsError::backend_message(&error)
1604 ),
1605 data: None,
1606 }),
1607 },
1608 Err(err) => JsonRpcResponse {
1609 jsonrpc: JSONRPC_VERSION.to_string(),
1610 id: response_id,
1611 result: None,
1612 error: Some(JsonRpcError {
1613 code: -32602,
1614 message: format!(
1615 "Invalid params: {}",
1616 MemoryParamsError::Index(err).message()
1617 ),
1618 data: None,
1619 }),
1620 },
1621 },
1622 Err(err) => JsonRpcResponse {
1623 jsonrpc: JSONRPC_VERSION.to_string(),
1624 id: response_id,
1625 result: None,
1626 error: Some(JsonRpcError {
1627 code: -32602,
1628 message: format!("Invalid params: {}", err.message()),
1629 data: None,
1630 }),
1631 },
1632 },
1633 "mobkit/memory/query" => match parse_memory_query_params(&request.params) {
1634 Ok(query_request) => {
1635 let query_result = runtime.memory_query(query_request).await;
1636 JsonRpcResponse {
1637 jsonrpc: JSONRPC_VERSION.to_string(),
1638 id: response_id,
1639 result: Some(serde_json::to_value(query_result).unwrap_or(Value::Null)),
1640 error: None,
1641 }
1642 }
1643 Err(err) => JsonRpcResponse {
1644 jsonrpc: JSONRPC_VERSION.to_string(),
1645 id: response_id,
1646 result: None,
1647 error: Some(JsonRpcError {
1648 code: -32602,
1649 message: format!("Invalid params: {}", err.message()),
1650 data: None,
1651 }),
1652 },
1653 },
1654 "mobkit/session_store/bigquery" => {
1655 match parse_bigquery_session_store_params(&request.params)
1656 .and_then(run_bigquery_session_store_request)
1657 {
1658 Ok(result) => JsonRpcResponse {
1659 jsonrpc: JSONRPC_VERSION.to_string(),
1660 id: response_id,
1661 result: Some(result),
1662 error: None,
1663 },
1664 Err(BigQuerySessionStoreRpcError::Params(message)) => JsonRpcResponse {
1665 jsonrpc: JSONRPC_VERSION.to_string(),
1666 id: response_id,
1667 result: None,
1668 error: Some(JsonRpcError {
1669 code: -32602,
1670 message: format!("Invalid params: {message}"),
1671 data: None,
1672 }),
1673 },
1674 Err(BigQuerySessionStoreRpcError::Store(error)) => JsonRpcResponse {
1675 jsonrpc: JSONRPC_VERSION.to_string(),
1676 id: response_id,
1677 result: None,
1678 error: Some(JsonRpcError {
1679 code: -32011,
1680 message: format!(
1681 "BigQuery session store request failed: {}",
1682 format_bigquery_store_error(&error)
1683 ),
1684 data: None,
1685 }),
1686 },
1687 }
1688 }
1689 "mobkit/gating/evaluate" => match parse_gating_evaluate_params(&request.params) {
1690 Ok(gating_request) => {
1691 let gating_result = runtime.evaluate_gating_action(gating_request).await;
1692 JsonRpcResponse {
1693 jsonrpc: JSONRPC_VERSION.to_string(),
1694 id: response_id,
1695 result: Some(serde_json::to_value(gating_result).unwrap_or(Value::Null)),
1696 error: None,
1697 }
1698 }
1699 Err(err) => JsonRpcResponse {
1700 jsonrpc: JSONRPC_VERSION.to_string(),
1701 id: response_id,
1702 result: None,
1703 error: Some(JsonRpcError {
1704 code: -32602,
1705 message: format!("Invalid params: {}", err.message()),
1706 data: None,
1707 }),
1708 },
1709 },
1710 "mobkit/gating/pending" => match parse_gating_pending_params(&request.params) {
1711 Ok(()) => {
1712 let pending = runtime.list_gating_pending().await;
1713 JsonRpcResponse {
1714 jsonrpc: JSONRPC_VERSION.to_string(),
1715 id: response_id,
1716 result: Some(serde_json::json!({
1717 "pending": pending,
1718 })),
1719 error: None,
1720 }
1721 }
1722 Err(err) => JsonRpcResponse {
1723 jsonrpc: JSONRPC_VERSION.to_string(),
1724 id: response_id,
1725 result: None,
1726 error: Some(JsonRpcError {
1727 code: -32602,
1728 message: format!("Invalid params: {}", err.message()),
1729 data: None,
1730 }),
1731 },
1732 },
1733 "mobkit/gating/decide" => {
1734 let decide_result = match parse_gating_decide_params(&request.params) {
1735 Ok(decide_request) => runtime
1736 .decide_gating_action(decide_request)
1737 .await
1738 .map_err(GatingParamsError::Decision),
1739 Err(e) => Err(e),
1740 };
1741 match decide_result {
1742 Ok(result) => JsonRpcResponse {
1743 jsonrpc: JSONRPC_VERSION.to_string(),
1744 id: response_id,
1745 result: Some(serde_json::to_value(result).unwrap_or(Value::Null)),
1746 error: None,
1747 },
1748 Err(err) => JsonRpcResponse {
1749 jsonrpc: JSONRPC_VERSION.to_string(),
1750 id: response_id,
1751 result: None,
1752 error: Some(JsonRpcError {
1753 code: -32602,
1754 message: format!("Invalid params: {}", err.message()),
1755 data: None,
1756 }),
1757 },
1758 }
1759 }
1760 "mobkit/gating/audit" => match parse_gating_audit_params(&request.params) {
1761 Ok(limit) => {
1762 let entries = runtime.gating_audit_entries(limit).await;
1763 JsonRpcResponse {
1764 jsonrpc: JSONRPC_VERSION.to_string(),
1765 id: response_id,
1766 result: Some(serde_json::json!({
1767 "entries": entries,
1768 })),
1769 error: None,
1770 }
1771 }
1772 Err(err) => JsonRpcResponse {
1773 jsonrpc: JSONRPC_VERSION.to_string(),
1774 id: response_id,
1775 result: None,
1776 error: Some(JsonRpcError {
1777 code: -32602,
1778 message: format!("Invalid params: {}", err.message()),
1779 data: None,
1780 }),
1781 },
1782 },
1783 "mobkit/call_tool" => {
1784 let module_id = request.params.get("module_id").and_then(Value::as_str);
1785 let tool = request.params.get("tool").and_then(Value::as_str);
1786 let arguments = request
1787 .params
1788 .get("arguments")
1789 .cloned()
1790 .unwrap_or(serde_json::json!({}));
1791
1792 match (module_id, tool) {
1793 (Some(module_id), Some(tool)) if !module_id.is_empty() && !tool.is_empty() => {
1794 let route = runtime
1795 .route_module_call(
1796 &ModuleRouteRequest {
1797 module_id: module_id.to_string(),
1798 method: tool.to_string(),
1799 params: arguments,
1800 },
1801 timeout,
1802 )
1803 .await;
1804 match route {
1805 Ok(response) => JsonRpcResponse {
1806 jsonrpc: JSONRPC_VERSION.to_string(),
1807 id: response_id,
1808 result: Some(serde_json::json!({
1809 "module_id": response.module_id,
1810 "tool": response.method,
1811 "result": response.payload
1812 })),
1813 error: None,
1814 },
1815 Err(ModuleRouteError::UnloadedModule(mid)) => JsonRpcResponse {
1816 jsonrpc: JSONRPC_VERSION.to_string(),
1817 id: response_id,
1818 result: None,
1819 error: Some(JsonRpcError {
1820 code: -32601,
1821 message: format!("Module '{mid}' not loaded"),
1822 data: None,
1823 }),
1824 },
1825 Err(err) => JsonRpcResponse {
1826 jsonrpc: JSONRPC_VERSION.to_string(),
1827 id: response_id,
1828 result: None,
1829 error: Some(JsonRpcError {
1830 code: -32000,
1831 message: format!("Tool call failed: {err:?}"),
1832 data: None,
1833 }),
1834 },
1835 }
1836 }
1837 _ => JsonRpcResponse {
1838 jsonrpc: JSONRPC_VERSION.to_string(),
1839 id: response_id,
1840 result: None,
1841 error: Some(JsonRpcError {
1842 code: -32602,
1843 message: "Invalid params: module_id and tool required".to_string(),
1844 data: None,
1845 }),
1846 },
1847 }
1848 }
1849 "mobkit/models/catalog" => JsonRpcResponse {
1850 jsonrpc: JSONRPC_VERSION.to_string(),
1851 id: response_id,
1852 result: Some(build_models_catalog_result()),
1853 error: None,
1854 },
1855 "mobkit/blob/get" => {
1856 mob_methods::handle_blob_get(runtime, response_id, &request.params).await
1857 }
1858 "mobkit/send_message" => {
1859 mob_methods::handle_send_message(runtime, response_id, &request.params).await
1860 }
1861 "mobkit/find_members" => {
1862 mob_methods::handle_find_members(runtime, response_id, &request.params).await
1863 }
1864 "mobkit/ensure_member" => {
1865 mob_methods::handle_ensure_member(runtime, response_id, &request.params).await
1866 }
1867 "mobkit/list_members" => mob_methods::handle_list_members(runtime, response_id).await,
1868 "mobkit/get_member" => {
1869 mob_methods::handle_get_member(runtime, response_id, &request.params).await
1870 }
1871 "mobkit/retire_member" => {
1872 mob_methods::handle_retire_member(runtime, response_id, &request.params).await
1873 }
1874 "mobkit/respawn_member" => {
1875 mob_methods::handle_respawn_member(runtime, response_id, &request.params).await
1876 }
1877 "mobkit/reconcile_edges" => mob_methods::handle_reconcile_edges(runtime, response_id).await,
1878 "mobkit/rediscover" => mob_methods::handle_rediscover(runtime, response_id).await,
1879 "mobkit/mob_events/query" => {
1880 mob_methods::handle_mob_events_query(runtime, response_id, request.params).await
1881 }
1882 "mobkit/mob_events/subscribe" => {
1883 mob_methods::handle_mob_events_subscribe(runtime, response_id, request.params).await
1884 }
1885 "mobkit/cross_mob/wire" => {
1886 mob_methods::handle_cross_mob_wire(runtime, response_id, &request.params).await
1887 }
1888 "mobkit/cross_mob/unwire" => {
1889 mob_methods::handle_cross_mob_unwire(runtime, response_id, &request.params).await
1890 }
1891 "mobkit/cross_mob/send" => {
1892 mob_methods::handle_cross_mob_send(runtime, response_id, &request.params).await
1893 }
1894 "mobkit/cross_mob/directory" => {
1895 mob_methods::handle_cross_mob_directory(runtime, response_id).await
1896 }
1897 "mobkit/cross_mob/peer_info" => {
1898 mob_methods::handle_cross_mob_peer_info(runtime, response_id, &request.params).await
1899 }
1900 "mobkit/cross_mob/wire_local" => {
1901 mob_methods::handle_cross_mob_wire_local(runtime, response_id, &request.params).await
1902 }
1903 "mobkit/cross_mob/unwire_local" => {
1904 mob_methods::handle_cross_mob_unwire_local(runtime, response_id, &request.params).await
1905 }
1906 "mobkit/peer_pubkey" => mob_methods::handle_peer_pubkey(runtime, response_id).await,
1907 "mobkit/member_status" => {
1908 mob_methods::handle_member_status(runtime, response_id, &request.params).await
1909 }
1910 "mobkit/force_cancel_member" => {
1911 mob_methods::handle_force_cancel_member(runtime, response_id, &request.params).await
1912 }
1913 "mobkit/spawn_helper" => {
1914 mob_methods::handle_spawn_helper(runtime, response_id, &request.params).await
1915 }
1916 "mobkit/fork_helper" => {
1917 mob_methods::handle_fork_helper(runtime, response_id, &request.params).await
1918 }
1919 "mobkit/attach_existing_session" => {
1920 mob_methods::handle_attach_existing_session(runtime, response_id, &request.params).await
1921 }
1922 "mobkit/cancel_flow" => {
1923 mob_methods::handle_cancel_flow(runtime, response_id, &request.params).await
1924 }
1925 "mobkit/flow_status" => {
1926 mob_methods::handle_flow_status(runtime, response_id, &request.params).await
1927 }
1928 "mobkit/list_flows" => mob_methods::handle_list_flows(runtime, response_id).await,
1929 "mobkit/list_runs" => {
1930 mob_methods::handle_list_runs(runtime, response_id, &request.params).await
1931 }
1932 "mobkit/run_flow" => {
1933 mob_methods::handle_run_flow(runtime, response_id, &request.params).await
1934 }
1935 "mobkit/collect_completed" => {
1936 mob_methods::handle_collect_completed(runtime, response_id).await
1937 }
1938 "mobkit/wait_ready" => {
1939 mob_methods::handle_wait_ready(runtime, response_id, &request.params).await
1940 }
1941 "mobkit/mob_labels/set" => {
1942 mob_methods::handle_mob_labels_set(runtime, response_id, &request.params).await
1943 }
1944 "mobkit/mob_labels/get" => mob_methods::handle_mob_labels_get(runtime, response_id).await,
1945 "mobkit/mob_labels/delete" => {
1946 mob_methods::handle_mob_labels_delete(runtime, response_id).await
1947 }
1948 "mobkit/run_labels/set" => {
1949 mob_methods::handle_run_labels_set(runtime, response_id, &request.params).await
1950 }
1951 "mobkit/run_labels/get" => {
1952 mob_methods::handle_run_labels_get(runtime, response_id, &request.params).await
1953 }
1954 "mobkit/run_labels/delete" => {
1955 mob_methods::handle_run_labels_delete(runtime, response_id, &request.params).await
1956 }
1957 "mobkit/send" => {
1959 let identity_rt = match identity_ctx {
1960 Some(ctx) => &*ctx.runtime,
1961 None => return identity_not_configured(response_id),
1962 };
1963 let identity_str = request
1964 .params
1965 .get("identity")
1966 .and_then(|v| v.as_str())
1967 .unwrap_or("");
1968 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
1969 Ok(id) => id,
1970 Err(e) => {
1971 return error_response(response_id, -32602, format!("invalid identity: {e}"));
1972 }
1973 };
1974 let content_val = request
1975 .params
1976 .get("content")
1977 .cloned()
1978 .unwrap_or(Value::Null);
1979 let content = match serde_json::from_value::<meerkat_core::ContentInput>(content_val) {
1980 Ok(content) => content,
1981 Err(err) => {
1982 return error_response(response_id, -32602, format!("invalid content: {err}"));
1983 }
1984 };
1985 match identity_rt.send(&identity, &content).await {
1986 Ok(token) => JsonRpcResponse {
1987 jsonrpc: JSONRPC_VERSION.to_string(),
1988 id: response_id,
1989 result: Some(serde_json::json!({ "fencing_token": token.get() })),
1990 error: None,
1991 },
1992 Err(e) => identity_error_response(response_id, &e),
1993 }
1994 }
1995 "mobkit/interact" => {
1996 let identity_rt = match identity_ctx {
1997 Some(ctx) => &*ctx.runtime,
1998 None => return identity_not_configured(response_id),
1999 };
2000 let identity_str = request
2001 .params
2002 .get("identity")
2003 .and_then(|v| v.as_str())
2004 .unwrap_or("");
2005 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2006 Ok(id) => id,
2007 Err(e) => {
2008 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2009 }
2010 };
2011 let content_val = request
2012 .params
2013 .get("content")
2014 .cloned()
2015 .unwrap_or(Value::Null);
2016 let content =
2017 match serde_json::from_value::<meerkat_core::ContentInput>(content_val.clone()) {
2018 Ok(content) => content,
2019 Err(err) => {
2020 return error_response(
2021 response_id,
2022 -32602,
2023 format!("invalid content: {err}"),
2024 );
2025 }
2026 };
2027 let origin = request
2028 .params
2029 .get("origin")
2030 .and_then(|v| v.as_str())
2031 .unwrap_or("console");
2032 let interaction_id = request
2033 .params
2034 .get("interaction_id")
2035 .and_then(|v| v.as_str())
2036 .map(ToString::to_string)
2037 .unwrap_or_else(|| meerkat_core::types::SessionId::new().to_string());
2038 let runtime_member_id = identity_rt
2039 .status(&identity)
2040 .await
2041 .ok()
2042 .and_then(|status| status.agent_runtime_id.map(|id| id.as_str().to_string()));
2043
2044 if let Err(err) = runtime
2045 .reserve_identity_interaction(
2046 identity.as_str(),
2047 runtime_member_id.as_deref(),
2048 &interaction_id,
2049 origin,
2050 content_val,
2051 )
2052 .await
2053 {
2054 return error_response(
2055 response_id,
2056 -32003,
2057 format!("failed to reserve interaction: {err}"),
2058 );
2059 }
2060
2061 match identity_rt.send(&identity, &content).await {
2062 Ok(token) => JsonRpcResponse {
2063 jsonrpc: JSONRPC_VERSION.to_string(),
2064 id: response_id,
2065 result: Some(serde_json::json!({
2066 "interaction_id": interaction_id,
2067 "fencing_token": token.get(),
2068 "stream": {
2069 "route": format!("/console/identity/{}/stream", identity.as_str()),
2070 "identity": identity.as_str(),
2071 }
2072 })),
2073 error: None,
2074 },
2075 Err(e) => {
2076 runtime
2077 .record_console_lifecycle(
2078 identity.as_str(),
2079 "interaction_failed",
2080 serde_json::json!({
2081 "interaction_id": interaction_id,
2082 "origin": origin,
2083 "error": e.to_string(),
2084 }),
2085 )
2086 .await;
2087 identity_error_response(response_id, &e)
2088 }
2089 }
2090 }
2091 "mobkit/dispatch" => {
2092 let identity_rt = match identity_ctx {
2093 Some(ctx) => &*ctx.runtime,
2094 None => return identity_not_configured(response_id),
2095 };
2096 let identity_str = request
2097 .params
2098 .get("identity")
2099 .and_then(|v| v.as_str())
2100 .unwrap_or("");
2101 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2102 Ok(id) => id,
2103 Err(e) => {
2104 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2105 }
2106 };
2107 let di_val = request
2108 .params
2109 .get("dispatch_input")
2110 .cloned()
2111 .unwrap_or(Value::Null);
2112 let content_val = di_val
2113 .get("content")
2114 .cloned()
2115 .unwrap_or_else(|| Value::String(String::new()));
2116 let content = match serde_json::from_value::<meerkat_core::ContentInput>(content_val) {
2117 Ok(content) => content,
2118 Err(err) => {
2119 return error_response(
2120 response_id,
2121 -32602,
2122 format!("invalid dispatch_input.content: {err}"),
2123 );
2124 }
2125 };
2126 let origin_str = di_val
2127 .get("origin")
2128 .and_then(|v| v.as_str())
2129 .unwrap_or("system");
2130 let origin = match origin_str {
2131 "connector" => crate::identity_first::DispatchOrigin::Connector,
2132 "scheduler" => crate::identity_first::DispatchOrigin::Scheduler,
2133 "policy" => crate::identity_first::DispatchOrigin::Policy,
2134 "flow" => crate::identity_first::DispatchOrigin::Flow,
2135 _ => crate::identity_first::DispatchOrigin::System,
2136 };
2137 let correlation_id = di_val
2138 .get("correlation_id")
2139 .and_then(|v| v.as_str())
2140 .map(crate::identity_first::CorrelationId::new);
2141 let idempotency_key = di_val
2142 .get("idempotency_key")
2143 .and_then(|v| v.as_str())
2144 .map(crate::identity_first::DispatchIdempotencyKey::new);
2145 let dispatch_input = crate::identity_first::DispatchInput {
2146 content,
2147 origin,
2148 correlation_id,
2149 idempotency_key,
2150 };
2151 match identity_rt.dispatch(&identity, &dispatch_input).await {
2152 Ok((token, durable)) => JsonRpcResponse {
2153 jsonrpc: JSONRPC_VERSION.to_string(),
2154 id: response_id,
2155 result: Some(
2156 serde_json::json!({ "fencing_token": token.get(), "durable": durable }),
2157 ),
2158 error: None,
2159 },
2160 Err(e) => identity_error_response(response_id, &e),
2161 }
2162 }
2163 "mobkit/subscribe" => {
2164 let identity_rt = match identity_ctx {
2165 Some(ctx) => &*ctx.runtime,
2166 None => return identity_not_configured(response_id),
2167 };
2168 let identity_str = request
2169 .params
2170 .get("identity")
2171 .and_then(|v| v.as_str())
2172 .unwrap_or("");
2173 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2174 Ok(id) => id,
2175 Err(e) => {
2176 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2177 }
2178 };
2179 match identity_rt.subscribe(&identity).await {
2180 Ok(_receiver) => JsonRpcResponse {
2181 jsonrpc: JSONRPC_VERSION.to_string(),
2182 id: response_id,
2183 result: Some(serde_json::json!({
2184 "identity": identity.as_str(),
2185 "stream_id": identity.as_str(),
2186 "subscribed": true,
2187 })),
2188 error: None,
2189 },
2190 Err(e) => identity_error_response(response_id, &e),
2191 }
2192 }
2193 "mobkit/status_identity" => {
2194 let identity_rt = match identity_ctx {
2195 Some(ctx) => &*ctx.runtime,
2196 None => return identity_not_configured(response_id),
2197 };
2198 let identity_str = request
2199 .params
2200 .get("identity")
2201 .and_then(|v| v.as_str())
2202 .unwrap_or("");
2203 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2204 Ok(id) => id,
2205 Err(e) => {
2206 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2207 }
2208 };
2209 match identity_rt.status(&identity).await {
2210 Ok(status) => {
2211 let result = serde_json::json!({
2212 "state": format!("{:?}", status.state),
2213 "identity": identity_str,
2214 "agent_runtime_id": status.agent_runtime_id.as_ref().map(super::identity_first::AgentRuntimeId::as_str),
2215 "session_id": status.session_id.as_ref().map(ToString::to_string),
2216 "profile": status.profile.as_ref().map(meerkat_mob::ProfileName::as_str),
2217 "addressability": addressability_json(status.addressability),
2218 "display_name": status.display_name.as_ref().map(super::identity_first::DisplayName::as_str),
2219 "labels": status.labels,
2220 "generation": status.generation.map(super::identity_first::ContinuityGeneration::get),
2221 "checkpoint_version": status.checkpoint_version.map(super::identity_first::CheckpointVersion::get),
2222 "lease_healthy": status.lease.as_ref().map(|lease| lease.healthy),
2223 "lease": status.lease.as_ref().map(|lease| serde_json::json!({
2224 "fencing_token": lease.fencing_token.get(),
2225 "ttl_remaining_ms": lease.ttl_remaining.as_millis() as u64,
2226 "healthy": lease.healthy,
2227 })),
2228 });
2229 JsonRpcResponse {
2230 jsonrpc: JSONRPC_VERSION.to_string(),
2231 id: response_id,
2232 result: Some(result),
2233 error: None,
2234 }
2235 }
2236 Err(e) => identity_error_response(response_id, &e),
2237 }
2238 }
2239 "mobkit/respawn" => {
2240 let identity_rt = match identity_ctx {
2241 Some(ctx) => &*ctx.runtime,
2242 None => return identity_not_configured(response_id),
2243 };
2244 let identity_str = request
2245 .params
2246 .get("identity")
2247 .and_then(|v| v.as_str())
2248 .unwrap_or("");
2249 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2250 Ok(id) => id,
2251 Err(e) => {
2252 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2253 }
2254 };
2255 match identity_rt.respawn(&identity).await {
2256 Ok(record) => {
2257 runtime
2258 .record_console_lifecycle(
2259 identity.as_str(),
2260 "identity_respawned",
2261 serde_json::json!({
2262 "generation": record.generation.get(),
2263 "checkpoint_version": record.checkpoint_version.get(),
2264 }),
2265 )
2266 .await;
2267 JsonRpcResponse {
2268 jsonrpc: JSONRPC_VERSION.to_string(),
2269 id: response_id,
2270 result: Some(serde_json::json!({
2271 "identity": record.identity.as_str(),
2272 "agent_runtime_id": record.agent_runtime_id.as_str(),
2273 "session_id": record.session_id.to_string(),
2274 "generation": record.generation.get(),
2275 "checkpoint_version": record.checkpoint_version.get(),
2276 })),
2277 error: None,
2278 }
2279 }
2280 Err(e) => identity_error_response(response_id, &e),
2281 }
2282 }
2283 "mobkit/retire" => {
2284 let identity_rt = match identity_ctx {
2285 Some(ctx) => &*ctx.runtime,
2286 None => return identity_not_configured(response_id),
2287 };
2288 let identity_str = request
2289 .params
2290 .get("identity")
2291 .and_then(|v| v.as_str())
2292 .unwrap_or("");
2293 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2294 Ok(id) => id,
2295 Err(e) => {
2296 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2297 }
2298 };
2299 match identity_rt.retire(&identity).await {
2300 Ok(token) => {
2301 runtime
2302 .record_console_lifecycle(
2303 identity.as_str(),
2304 "identity_retired",
2305 serde_json::json!({ "fencing_token": token.get() }),
2306 )
2307 .await;
2308 JsonRpcResponse {
2309 jsonrpc: JSONRPC_VERSION.to_string(),
2310 id: response_id,
2311 result: Some(serde_json::json!({ "fencing_token": token.get() })),
2312 error: None,
2313 }
2314 }
2315 Err(e) => identity_error_response(response_id, &e),
2316 }
2317 }
2318 "mobkit/reset" => {
2319 let identity_rt = match identity_ctx {
2320 Some(ctx) => &*ctx.runtime,
2321 None => return identity_not_configured(response_id),
2322 };
2323 let identity_str = request
2324 .params
2325 .get("identity")
2326 .and_then(|v| v.as_str())
2327 .unwrap_or("");
2328 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2329 Ok(id) => id,
2330 Err(e) => {
2331 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2332 }
2333 };
2334 match identity_rt.reset(&identity).await {
2335 Ok(record) => {
2336 runtime
2337 .record_console_lifecycle(
2338 identity.as_str(),
2339 "identity_reset",
2340 serde_json::json!({
2341 "generation": record.generation.get(),
2342 "checkpoint_version": record.checkpoint_version.get(),
2343 }),
2344 )
2345 .await;
2346 JsonRpcResponse {
2347 jsonrpc: JSONRPC_VERSION.to_string(),
2348 id: response_id,
2349 result: Some(serde_json::json!({
2350 "identity": record.identity.as_str(),
2351 "agent_runtime_id": record.agent_runtime_id.as_str(),
2352 "session_id": record.session_id.to_string(),
2353 "generation": record.generation.get(),
2354 "checkpoint_version": record.checkpoint_version.get(),
2355 })),
2356 error: None,
2357 }
2358 }
2359 Err(e) => identity_error_response(response_id, &e),
2360 }
2361 }
2362 "mobkit/delete_identity" => {
2363 let identity_rt = match identity_ctx {
2364 Some(ctx) => &*ctx.runtime,
2365 None => return identity_not_configured(response_id),
2366 };
2367 let identity_str = request
2368 .params
2369 .get("identity")
2370 .and_then(|v| v.as_str())
2371 .unwrap_or("");
2372 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2373 Ok(id) => id,
2374 Err(e) => {
2375 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2376 }
2377 };
2378 match identity_rt.delete_identity(&identity).await {
2379 Ok(()) => JsonRpcResponse {
2380 jsonrpc: JSONRPC_VERSION.to_string(),
2381 id: response_id,
2382 result: Some(serde_json::json!({})),
2383 error: None,
2384 },
2385 Err(e) => identity_error_response(response_id, &e),
2386 }
2387 }
2388 "mobkit/inspect_identity" => {
2389 let identity_rt = match identity_ctx {
2390 Some(ctx) => &*ctx.runtime,
2391 None => return identity_not_configured(response_id),
2392 };
2393 let identity_str = request
2394 .params
2395 .get("identity")
2396 .and_then(|v| v.as_str())
2397 .unwrap_or("");
2398 let identity = match crate::identity_first::AgentIdentity::parse(identity_str) {
2399 Ok(id) => id,
2400 Err(e) => {
2401 return error_response(response_id, -32602, format!("invalid identity: {e}"));
2402 }
2403 };
2404 let status = identity_rt.status(&identity).await;
2405 match identity_rt.inspect(&identity).await {
2406 Ok(inspection) => {
2407 let status = status.ok();
2408 JsonRpcResponse {
2409 jsonrpc: JSONRPC_VERSION.to_string(),
2410 id: response_id,
2411 result: Some(serde_json::json!({
2412 "identity": identity_str,
2413 "state": status.as_ref().map(|status| format!("{:?}", status.state)),
2414 "profile": status.as_ref().and_then(|status| status.profile.as_ref().map(meerkat_mob::ProfileName::as_str)),
2415 "addressability": status.as_ref().map(|status| addressability_json(status.addressability)),
2416 "display_name": status.as_ref().and_then(|status| status.display_name.as_ref().map(super::identity_first::DisplayName::as_str)),
2417 "labels": status.as_ref().map(|status| status.labels.clone()).unwrap_or_default(),
2418 "generation": status.as_ref().and_then(|status| status.generation.map(super::identity_first::ContinuityGeneration::get)),
2419 "checkpoint_version": status.as_ref().and_then(|status| status.checkpoint_version.map(super::identity_first::CheckpointVersion::get)),
2420 "lease_healthy": status.as_ref().and_then(|status| status.lease.as_ref().map(|lease| lease.healthy)),
2421 "continuity": status.as_ref().map(|status| serde_json::json!({
2422 "generation": status.generation.map(super::identity_first::ContinuityGeneration::get),
2423 "checkpoint_version": status.checkpoint_version.map(super::identity_first::CheckpointVersion::get),
2424 "session_id": status.session_id.as_ref().map(ToString::to_string),
2425 "agent_runtime_id": status.agent_runtime_id.as_ref().map(super::identity_first::AgentRuntimeId::as_str),
2426 })).unwrap_or_else(|| serde_json::json!({})),
2427 "lease": status.as_ref().and_then(|status| status.lease.as_ref().map(|lease| serde_json::json!({
2428 "fencing_token": lease.fencing_token.get(),
2429 "ttl_remaining_ms": lease.ttl_remaining.as_millis() as u64,
2430 "healthy": lease.healthy,
2431 }))),
2432 "output_preview": inspection.output_preview,
2433 "is_final": inspection.is_final,
2434 "peer_reachable_count": inspection.peer_reachable_count,
2435 })),
2436 error: None,
2437 }
2438 }
2439 Err(e) => identity_error_response(response_id, &e),
2440 }
2441 }
2442 "mobkit/reconcile_identity" => {
2443 let ctx = match identity_ctx {
2444 Some(ctx) => ctx,
2445 None => return identity_not_configured(response_id),
2446 };
2447 let roster_specs = match ctx
2449 .roster_provider
2450 .roster(&crate::identity_first::RosterContext {
2451 mob_definition: None,
2452 previous_identities: Vec::new(),
2453 })
2454 .await
2455 {
2456 Ok(specs) => specs,
2457 Err(e) => {
2458 return error_response(
2459 response_id,
2460 -32603,
2461 format!("roster provider failed: {e}"),
2462 );
2463 }
2464 };
2465 match crate::identity_first::restore_flow(
2466 &ctx.runtime,
2467 &roster_specs,
2468 ctx.topology_provider.as_deref(),
2469 ctx.customizer.as_deref(),
2470 )
2471 .await
2472 {
2473 Ok(result) => {
2474 let outcomes: serde_json::Map<String, Value> = result
2475 .outcomes
2476 .iter()
2477 .map(|(id, outcome)| {
2478 let val = match outcome {
2479 crate::identity_first::RestoreOutcome::Created {
2480 record, ..
2481 } => {
2482 serde_json::json!({
2483 "outcome": "created",
2484 "identity": record.identity.as_str(),
2485 "agent_runtime_id": record.agent_runtime_id.as_str(),
2486 "session_id": record.session_id.to_string(),
2487 "generation": record.generation.get(),
2488 })
2489 }
2490 crate::identity_first::RestoreOutcome::Resumed {
2491 record, ..
2492 } => {
2493 serde_json::json!({
2494 "outcome": "resumed",
2495 "identity": record.identity.as_str(),
2496 "agent_runtime_id": record.agent_runtime_id.as_str(),
2497 "session_id": record.session_id.to_string(),
2498 "generation": record.generation.get(),
2499 })
2500 }
2501 crate::identity_first::RestoreOutcome::Broken(failure) => {
2502 serde_json::json!({
2503 "outcome": "broken",
2504 "identity": failure.identity.as_str(),
2505 "detail": failure.detail,
2506 })
2507 }
2508 };
2509 (id.to_string(), val)
2510 })
2511 .collect();
2512 JsonRpcResponse {
2513 jsonrpc: JSONRPC_VERSION.to_string(),
2514 id: response_id,
2515 result: Some(serde_json::json!({
2516 "outcomes": outcomes,
2517 "managed_edges": result.managed_edges.len(),
2518 })),
2519 error: None,
2520 }
2521 }
2522 Err(e) => identity_error_response(response_id, &e),
2523 }
2524 }
2525 method if method.contains('/') && !method.starts_with("mobkit/") => {
2526 let module_id = method
2527 .split('/')
2528 .next()
2529 .map(ToString::to_string)
2530 .unwrap_or_default();
2531 let route = runtime
2532 .route_module_call(
2533 &ModuleRouteRequest {
2534 module_id: module_id.clone(),
2535 method: method.to_string(),
2536 params: request.params,
2537 },
2538 timeout,
2539 )
2540 .await;
2541 match route {
2542 Ok(response) => JsonRpcResponse {
2543 jsonrpc: JSONRPC_VERSION.to_string(),
2544 id: response_id,
2545 result: Some(serde_json::json!({
2546 "module_id": response.module_id,
2547 "method": response.method,
2548 "payload": response.payload
2549 })),
2550 error: None,
2551 },
2552 Err(ModuleRouteError::UnloadedModule(module_id)) => JsonRpcResponse {
2553 jsonrpc: JSONRPC_VERSION.to_string(),
2554 id: response_id,
2555 result: None,
2556 error: Some(JsonRpcError {
2557 code: -32601,
2558 message: format!("Module '{module_id}' not loaded"),
2559 data: None,
2560 }),
2561 },
2562 Err(err) => JsonRpcResponse {
2563 jsonrpc: JSONRPC_VERSION.to_string(),
2564 id: response_id,
2565 result: None,
2566 error: Some(JsonRpcError {
2567 code: -32000,
2568 message: format!("Module route failed: {err:?}"),
2569 data: None,
2570 }),
2571 },
2572 }
2573 }
2574 _ => JsonRpcResponse {
2575 jsonrpc: JSONRPC_VERSION.to_string(),
2576 id: response_id,
2577 result: None,
2578 error: Some(JsonRpcError {
2579 code: -32601,
2580 message: "Method not found".to_string(),
2581 data: None,
2582 }),
2583 },
2584 };
2585 if is_notification {
2586 String::new()
2587 } else {
2588 serialize_response(&response)
2589 }
2590}
2591
2592fn build_models_catalog_result() -> Value {
2593 let entries: Vec<Value> = meerkat_models::catalog()
2594 .iter()
2595 .filter_map(|e| {
2596 let mut val = serde_json::to_value(e).ok()?;
2597 if let Some(provider) = meerkat_core::Provider::parse_strict(e.provider)
2598 && let Some(profile) = meerkat_models::profile_for(provider, e.id)
2599 && let Ok(p) = serde_json::to_value(&profile)
2600 {
2601 val["profile"] = p;
2602 }
2603 Some(val)
2604 })
2605 .collect();
2606 let defaults: Vec<Value> = meerkat_models::provider_defaults()
2607 .iter()
2608 .filter_map(|d| serde_json::to_value(d).ok())
2609 .collect();
2610 serde_json::json!({
2611 "models": entries,
2612 "provider_defaults": defaults,
2613 })
2614}
2615
2616fn identity_not_configured(response_id: Value) -> String {
2617 error_response(response_id, -32601, "identity-first runtime not configured")
2618}
2619
2620fn addressability_json(addressability: crate::identity_first::AgentAddressability) -> &'static str {
2621 match addressability {
2622 crate::identity_first::AgentAddressability::Addressable => "addressable",
2623 crate::identity_first::AgentAddressability::InternalOnly => "internal_only",
2624 }
2625}
2626
2627fn identity_error_response(
2628 response_id: Value,
2629 err: &crate::identity_first::IdentityRuntimeError,
2630) -> JsonRpcResponse {
2631 use crate::identity_first::IdentityRuntimeError;
2632 let (code, message) = match err {
2633 IdentityRuntimeError::UnknownIdentity(id) => (-32001, format!("unknown identity: {id}")),
2634 IdentityRuntimeError::NotAddressable(na) => {
2635 (-32002, format!("not addressable: {}", na.identity))
2636 }
2637 IdentityRuntimeError::NoActiveLease(id) => (-32003, format!("no active lease: {id}")),
2638 IdentityRuntimeError::LeaseLost(id) => (-32004, format!("lease lost: {id}")),
2639 _ => (-32603, format!("{err}")),
2640 };
2641 JsonRpcResponse {
2642 jsonrpc: JSONRPC_VERSION.to_string(),
2643 id: response_id,
2644 result: None,
2645 error: Some(JsonRpcError {
2646 code,
2647 message,
2648 data: None,
2649 }),
2650 }
2651}
2652
2653fn error_response(response_id: Value, code: i64, message: impl Into<String>) -> String {
2654 serialize_response(&JsonRpcResponse {
2655 jsonrpc: JSONRPC_VERSION.to_string(),
2656 id: response_id,
2657 result: None,
2658 error: Some(JsonRpcError {
2659 code,
2660 message: message.into(),
2661 data: None,
2662 }),
2663 })
2664}
2665
2666fn serialize_response(response: &JsonRpcResponse) -> String {
2667 serde_json::to_string(response).unwrap_or_else(|_| {
2668 r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Internal error"}}"#
2669 .to_string()
2670 })
2671}