1use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::path::Path;
12use std::sync::Arc;
13
14use crate::{
15 Agent, Config, DiscoveryPage, DiscoveryQuery, Fidelity, HarnessCatalog, Result as CoreResult,
16 Session, SessionDescriptor, SessionLocator,
17};
18
19#[async_trait]
24pub trait SdkPromptSource: Send + Sync {
25 async fn render(&self, args: std::collections::BTreeMap<String, String>) -> CoreResult<String>;
27 fn arg_names(&self) -> &[String];
29}
30
31pub const SDK_SCHEMA_VERSION: &str = "supercode.sdk.v1";
33
34pub fn discover_sessions(query: &DiscoveryQuery) -> CoreResult<Vec<SessionDescriptor>> {
36 Ok(HarnessCatalog::new().discover(query)?)
37}
38
39pub fn discover_session_page(query: &DiscoveryQuery) -> CoreResult<DiscoveryPage> {
41 Ok(HarnessCatalog::new().discover_page(query)?)
42}
43
44pub fn load_session(locator: &SessionLocator) -> CoreResult<Session> {
46 Ok(HarnessCatalog::new().load(locator)?)
47}
48
49pub fn load_session_with_fidelity(
56 locator: &SessionLocator,
57 fidelity: Fidelity,
58) -> CoreResult<Session> {
59 Ok(HarnessCatalog::new().load_with_fidelity(locator, fidelity)?)
60}
61
62pub fn load_session_path(path: &Path, opencode_session: Option<&str>) -> CoreResult<Session> {
65 if opencode_session.is_some() {
66 return Ok(Session::from_opencode_sqlite(path, opencode_session)?);
67 }
68 if let Some(session) = load_native_store_family(path)? {
69 return Ok(session);
70 }
71 Ok(Session::load(path)?)
72}
73
74pub(crate) fn load_native_store_family(path: &Path) -> CoreResult<Option<Session>> {
75 Ok(supercode_interchange::load_native_store_family(path)?)
76}
77
78pub struct SdkAgent(Agent);
86
87impl SdkAgent {
88 pub(crate) fn from_agent(agent: Agent) -> Self {
89 Self(agent)
90 }
91
92 pub(crate) fn inner(&self) -> &Agent {
93 &self.0
94 }
95
96 pub(crate) fn inner_mut(&mut self) -> &mut Agent {
97 &mut self.0
98 }
99
100 pub fn config(&self) -> &Config {
102 self.0.config()
103 }
104
105 pub fn set_recorder(&mut self, writer: crate::sidecar::SidecarWriter) {
107 self.0.set_recorder(writer);
108 }
109
110 pub fn set_journal(&mut self, journal: crate::session_journal::SessionJournal) {
115 self.0.set_journal(journal);
116 }
117
118 pub fn has_journal(&self) -> bool {
120 self.0.has_journal()
121 }
122
123 pub fn journal_checkpoint(&self, messages: usize) {
125 self.0.journal_checkpoint(messages);
126 }
127
128 pub fn session_tree(&self) -> Option<&crate::session_tree::SessionTree> {
130 self.0.session_tree()
131 }
132
133 pub fn set_session_tree(&mut self, tree: crate::session_tree::SessionTree) {
135 self.0.set_session_tree(tree);
136 }
137
138 pub fn rebuild_session_tree_from_history(&mut self) {
140 self.0.rebuild_session_tree_from_history();
141 }
142
143 pub fn rewind_conversation(&mut self, keep: usize) -> crate::agent::RewindOutcome {
146 self.0.rewind_conversation(keep)
147 }
148
149 pub fn undo_rewind(&mut self) -> bool {
151 self.0.undo_rewind()
152 }
153
154 pub fn undoable_rewinds(&self) -> usize {
156 self.0.undoable_rewinds()
157 }
158
159 pub fn restore_rewind_undo(&mut self, stack: Vec<Vec<crate::ChatMessage>>) {
161 self.0.restore_rewind_undo(stack);
162 }
163
164 pub fn restore_queues(&mut self, steer: &[String], follow_up: &[String]) {
166 self.0.restore_queues(steer, follow_up);
167 }
168
169 pub fn append_recovered_messages(&mut self, messages: &[crate::ChatMessage]) {
171 self.0.append_recovered_messages(messages);
172 }
173
174 pub fn plan(&self) -> Vec<crate::session_journal::PlanEntry> {
176 self.0.plan()
177 }
178
179 pub fn set_plan(&mut self, steps: Vec<crate::session_journal::PlanEntry>) {
181 self.0.set_plan(steps);
182 }
183
184 pub fn arm_session_journal(
187 &mut self,
188 store: &crate::store::SessionStore,
189 name: &str,
190 ) -> crate::session_journal::RestoreReport {
191 crate::session_journal::arm(&mut self.0, store, name)
192 }
193
194 pub fn checkpoint_session_journal(
196 &self,
197 store: &crate::store::SessionStore,
198 name: &str,
199 messages: usize,
200 ) {
201 crate::session_journal::checkpoint(&self.0, store, name, messages);
202 }
203
204 pub fn plan_mode(&self) -> &std::sync::Arc<crate::tools::PlanModeState> {
208 self.0.plan_mode()
209 }
210
211 pub fn set_reduction_policy(&mut self, policy: crate::reduce::ReductionPolicy) {
213 self.0.set_reduction_policy(policy);
214 }
215
216 pub fn reduction_policy(&self) -> Option<&crate::reduce::ReductionPolicy> {
218 self.0.reduction_policy()
219 }
220
221 pub fn set_reduction_log(&mut self, log: crate::reduce::ReductionLog) {
223 self.0.set_reduction_log(log);
224 }
225
226 pub fn reduction_log(&self) -> &crate::reduce::ReductionLog {
228 self.0.reduction_log()
229 }
230
231 pub fn prepare_cleared_turns_summary(
233 &self,
234 messages: &[crate::ChatMessage],
235 policy: &crate::reduce::ReductionPolicy,
236 prior: &crate::reduce::ReductionLog,
237 ) -> Option<crate::reduce::PreparedClearSummary> {
238 self.0
239 .prepare_cleared_turns_summary(messages, policy, prior)
240 }
241
242 pub fn set_span_summarizer(
244 &mut self,
245 summarizer: impl crate::reduce::summarize::SpanSummarizer + Send + Sync + 'static,
246 ) {
247 self.0.set_span_summarizer(summarizer);
248 }
249
250 pub fn set_session_titler(
252 &mut self,
253 titler: impl crate::session_title::SessionTitler + Send + Sync + 'static,
254 ) {
255 self.0.set_session_titler(titler);
256 }
257
258 pub fn auto_title(&self) -> Option<String> {
260 self.0.auto_title()
261 }
262
263 pub fn set_subagent_store(
265 &mut self,
266 store: std::sync::Arc<crate::SessionStore>,
267 session_name: impl Into<String>,
268 ) {
269 self.0.set_subagent_store(store, session_name);
270 }
271
272 pub fn set_claude_runtime_manifest(
274 &mut self,
275 manifest: crate::claude_runtime_state::ClaudeRuntimeManifest,
276 ) {
277 self.0.set_claude_runtime_manifest(manifest);
278 }
279
280 pub fn claude_runtime_manifest(
282 &self,
283 ) -> Option<&crate::claude_runtime_state::ClaudeRuntimeManifest> {
284 self.0.claude_runtime_manifest()
285 }
286
287 pub fn claude_runtime_manifest_mut(
289 &mut self,
290 ) -> Option<&mut crate::claude_runtime_state::ClaudeRuntimeManifest> {
291 self.0.claude_runtime_manifest_mut()
292 }
293
294 pub fn restore_claude_project_agents(&mut self) -> CoreResult<usize> {
296 self.0.restore_claude_project_agents()
297 }
298
299 pub fn load_session(&mut self, session: Session) {
301 self.0.load_session(session);
302 }
303
304 pub fn load_transcript(&mut self, path: impl AsRef<Path>) -> CoreResult<()> {
306 self.0.load_transcript(path)
307 }
308
309 pub fn save_transcript(&self, path: impl AsRef<Path>) -> CoreResult<()> {
311 self.0.save_transcript(path)
312 }
313
314 pub fn history(&self) -> &[crate::ChatMessage] {
316 self.0.history()
317 }
318
319 pub fn rewind_to(&mut self, checkpoint: usize) {
321 self.0.rewind_to(checkpoint);
322 }
323
324 pub fn compact_now(&mut self, focus: Option<&str>) -> bool {
327 self.0.compact_now(focus)
328 }
329
330 pub fn context_usage(&self) -> crate::ContextUsage {
333 self.0.context_usage()
334 }
335
336 pub fn new_context(&mut self, objective: &str, keep_recent: Option<usize>) -> usize {
339 self.0.new_context(objective, keep_recent)
340 }
341
342 pub fn inject_context_block(
345 &mut self,
346 name: impl Into<String>,
347 content: impl Into<String>,
348 ) -> bool {
349 self.0.inject_context_block(name, content)
350 }
351
352 pub fn refresh_env_context(&mut self) -> bool {
355 self.0.refresh_env_context()
356 }
357
358 pub fn append_system_note(&mut self, text: &str) {
360 self.0.append_system_note(text);
361 }
362
363 pub fn register_tool(&mut self, tool: impl crate::Tool + 'static) {
365 self.0.register_tool(tool);
366 }
367
368 pub fn register_mcp_prompt(
370 &mut self,
371 command_name: impl Into<String>,
372 source: impl SdkPromptSource + 'static,
373 ) {
374 self.0.register_mcp_prompt(command_name, source);
375 }
376
377 pub fn tool_schemas(&self) -> Vec<crate::ToolSchema> {
379 self.0.tool_schemas()
380 }
381
382 pub fn set_context_limit(&mut self, limit: u64) {
384 self.0.set_context_limit(limit);
385 }
386
387 pub fn context_limit(&self) -> Option<u64> {
389 self.0.context_limit()
390 }
391
392 pub fn set_model(&mut self, model: impl Into<String>) {
394 self.0.set_model(model);
395 }
396
397 pub fn set_service_tier(&mut self, tier: Option<String>) {
401 self.0.set_service_tier(tier);
402 }
403
404 pub fn model(&self) -> &str {
406 self.0.model()
407 }
408
409 pub fn switch_model(&mut self, model: impl Into<String>) {
415 self.0.switch_model(model);
416 }
417
418 pub fn request_issued(&self) -> bool {
420 self.0.request_issued()
421 }
422
423 pub fn session_name(&self) -> Option<&str> {
425 self.0.session_name()
426 }
427
428 pub fn session_persist(&self) -> bool {
430 self.0.session_persist()
431 }
432
433 pub fn git_metadata(&self) -> Option<&crate::git_metadata::GitMetadataRecord> {
435 self.0.git_metadata()
436 }
437
438 pub fn save_git_metadata(&self, store: &crate::SessionStore, name: &str) -> CoreResult<()> {
440 self.0.save_git_metadata(store, name)
441 }
442
443 pub fn save_usage_log(&self, store: &crate::SessionStore, name: &str) -> CoreResult<()> {
445 self.0.save_usage_log(store, name)
446 }
447
448 pub fn turn_records(&self) -> &[crate::turn_record::TurnRecord] {
450 self.0.turn_records()
451 }
452
453 pub fn save_turn_records(&self, store: &crate::SessionStore, name: &str) -> CoreResult<()> {
455 self.0.save_turn_records(store, name)
456 }
457
458 pub fn note_abort(&mut self, source: &str) {
460 self.0.note_abort(source);
461 }
462
463 pub fn total_cost_usd(&self) -> f64 {
465 self.0.total_cost_usd()
466 }
467
468 pub fn model_priced(&self) -> bool {
470 self.0.model_priced()
471 }
472
473 pub fn total_steps(&self) -> usize {
475 self.0.total_steps()
476 }
477
478 pub fn set_goal(&mut self, objective: impl Into<String>) -> bool {
480 self.0.set_goal(objective)
481 }
482
483 pub fn goal(&self) -> Option<&crate::goals::GoalRecord> {
485 self.0.goal()
486 }
487
488 pub fn clear_goal(&mut self) -> bool {
490 self.0.clear_goal()
491 }
492
493 pub fn restore_goal(&mut self, goal: Option<crate::goals::GoalRecord>) {
495 self.0.restore_goal(goal);
496 }
497
498 pub fn save_goal(&self, store: &crate::SessionStore, name: &str) -> CoreResult<()> {
500 self.0.save_goal(store, name)
501 }
502
503 pub fn effort(&self) -> Option<&str> {
505 self.0.effort()
506 }
507
508 pub fn set_effort(&mut self, effort: Option<String>) -> Option<String> {
510 self.0.set_effort(effort)
511 }
512
513 pub fn review_prompt(&self, args: &str) -> Option<String> {
515 self.0.review_prompt(args)
516 }
517
518 pub async fn side_question(&self, question: &str) -> CoreResult<String> {
521 self.0.side_question(question).await
522 }
523
524 pub fn turn_count(&self) -> usize {
526 self.0.turn_count()
527 }
528
529 pub fn total_output_tokens(&self) -> u64 {
531 self.0.total_output_tokens()
532 }
533}
534
535impl From<Agent> for SdkAgent {
536 fn from(agent: Agent) -> Self {
537 Self::from_agent(agent)
538 }
539}
540
541pub fn create_agent(config: Config) -> CoreResult<SdkAgent> {
543 Agent::new(config).map(SdkAgent::from_agent)
544}
545
546pub fn resume_agent(config: Config, session: Session) -> CoreResult<SdkAgent> {
548 Agent::resume(config, session).map(SdkAgent::from_agent)
549}
550
551pub async fn submit_agent(agent: &mut SdkAgent, prompt: &str) -> CoreResult<String> {
553 agent.0.send(prompt).await
554}
555
556pub async fn show_model_input(agent: &mut SdkAgent, prompt: &str) -> serde_json::Value {
564 let req = agent.0.model_input_for(prompt).await;
565 Agent::render_model_input(&req)
566}
567
568pub async fn submit_agent_with_images(
570 agent: &mut SdkAgent,
571 prompt: &str,
572 image_urls: &[String],
573) -> CoreResult<String> {
574 agent.0.send_with_images(prompt, image_urls).await
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
579#[serde(rename_all = "snake_case")]
580pub enum SdkOperation {
581 Discover,
583 Load,
585 Start,
587 Resume,
589 Input,
591 Events,
593 Interrupt,
595 Steer,
597 Respond,
599 Export,
601 JobsList,
603 JobsGet,
605 JobsCreate,
607 JobsUpdate,
609 JobsPause,
611 JobsResume,
613 JobsRun,
615 JobsDelete,
617 SessionsNew,
619 SessionsReset,
621 SessionsArchive,
623 SessionsDelete,
625 RunsList,
627 RunsGet,
629 Close,
631 ProfilesList,
633 ProfilesGet,
635 ProfilesCreate,
637 ProfilesDelete,
639 SkillsList,
641 SkillsInstall,
643 SkillsRemove,
645 MemoryShow,
647 MemorySearch,
649 ApprovalsList,
651 ApprovalsResolve,
653 ChannelsList,
655 RoutesList,
657 TriggersList,
659 ChannelsStatus,
661 WorldLoad,
663 WorldSave,
665 WorldCompile,
667 WorldDecompile,
669 WorldImport,
671 WorldExport,
673}
674
675impl SdkOperation {
676 pub const ALL: [Self; 46] = [
678 Self::Discover,
679 Self::Load,
680 Self::Start,
681 Self::Resume,
682 Self::Input,
683 Self::Events,
684 Self::Interrupt,
685 Self::Steer,
686 Self::Respond,
687 Self::Export,
688 Self::JobsList,
689 Self::JobsGet,
690 Self::JobsCreate,
691 Self::JobsUpdate,
692 Self::JobsPause,
693 Self::JobsResume,
694 Self::JobsRun,
695 Self::JobsDelete,
696 Self::SessionsNew,
697 Self::SessionsReset,
698 Self::SessionsArchive,
699 Self::SessionsDelete,
700 Self::RunsList,
701 Self::RunsGet,
702 Self::Close,
703 Self::ProfilesList,
704 Self::ProfilesGet,
705 Self::ProfilesCreate,
706 Self::ProfilesDelete,
707 Self::SkillsList,
708 Self::SkillsInstall,
709 Self::SkillsRemove,
710 Self::MemoryShow,
711 Self::MemorySearch,
712 Self::ApprovalsList,
713 Self::ApprovalsResolve,
714 Self::ChannelsList,
715 Self::RoutesList,
716 Self::TriggersList,
717 Self::ChannelsStatus,
718 Self::WorldLoad,
719 Self::WorldSave,
720 Self::WorldCompile,
721 Self::WorldDecompile,
722 Self::WorldImport,
723 Self::WorldExport,
724 ];
725
726 pub const fn method(self) -> Option<&'static str> {
729 match self {
730 Self::Discover => Some("harness.v1.sessions.discover"),
731 Self::Load => Some("harness.v1.sessions.load"),
732 Self::Start => Some("harness.v1.runtimes.start"),
733 Self::Resume => Some("harness.v1.runtimes.resume"),
734 Self::Input => Some("harness.v1.runtimes.send_input"),
735 Self::Events => None,
736 Self::Interrupt => Some("harness.v1.runtimes.interrupt"),
737 Self::Steer => Some("harness.v1.runtimes.steer"),
738 Self::Respond => Some("harness.v1.runtimes.respond"),
739 Self::Export => Some("harness.v1.sessions.export"),
740 Self::JobsList => Some("harness.v1.jobs.list"),
741 Self::JobsGet => Some("harness.v1.jobs.get"),
742 Self::JobsCreate => Some("harness.v1.jobs.create"),
743 Self::JobsUpdate => Some("harness.v1.jobs.update"),
744 Self::JobsPause => Some("harness.v1.jobs.pause"),
745 Self::JobsResume => Some("harness.v1.jobs.resume"),
746 Self::JobsRun => Some("harness.v1.jobs.run"),
747 Self::JobsDelete => Some("harness.v1.jobs.delete"),
748 Self::SessionsNew => Some("harness.v1.sessions.new"),
749 Self::SessionsReset => Some("harness.v1.sessions.reset"),
750 Self::SessionsArchive => Some("harness.v1.sessions.archive"),
751 Self::SessionsDelete => Some("harness.v1.sessions.delete"),
752 Self::RunsList => Some("harness.v1.runs.list"),
753 Self::RunsGet => Some("harness.v1.runs.get"),
754 Self::Close => Some("harness.v1.runtimes.close"),
755 Self::ProfilesList => Some("harness.v1.profiles.list"),
756 Self::ProfilesGet => Some("harness.v1.profiles.get"),
757 Self::ProfilesCreate => Some("harness.v1.profiles.create"),
758 Self::ProfilesDelete => Some("harness.v1.profiles.delete"),
759 Self::SkillsList => Some("harness.v1.skills.list"),
760 Self::SkillsInstall => Some("harness.v1.skills.install"),
761 Self::SkillsRemove => Some("harness.v1.skills.remove"),
762 Self::MemoryShow => Some("harness.v1.memory.show"),
763 Self::MemorySearch => Some("harness.v1.memory.search"),
764 Self::ApprovalsList => Some("harness.v1.approvals.list"),
765 Self::ApprovalsResolve => Some("harness.v1.approvals.resolve"),
766 Self::ChannelsList => Some("harness.v1.channels.list"),
767 Self::RoutesList => Some("harness.v1.routes.list"),
768 Self::TriggersList => Some("harness.v1.triggers.list"),
769 Self::ChannelsStatus => Some("harness.v1.channels.status"),
770 Self::WorldLoad => Some("harness.v1.world.load"),
771 Self::WorldSave => Some("harness.v1.world.save"),
772 Self::WorldCompile => Some("harness.v1.world.compile"),
773 Self::WorldDecompile => Some("harness.v1.world.decompile"),
774 Self::WorldImport => Some("harness.v1.world.import"),
775 Self::WorldExport => Some("harness.v1.world.export"),
776 }
777 }
778
779 pub fn from_method(method: &str) -> Option<Self> {
781 Self::ALL
782 .into_iter()
783 .find(|operation| operation.method() == Some(method))
784 }
785
786 pub const fn action_name(self) -> &'static str {
788 match self {
789 Self::Discover => "discover",
790 Self::Load => "load",
791 Self::Start => "start",
792 Self::Resume => "resume",
793 Self::Input => "input",
794 Self::Events => "events",
795 Self::Interrupt => "interrupt",
796 Self::Steer => "steer",
797 Self::Respond => "respond",
798 Self::Export => "export",
799 Self::JobsList => "jobs_list",
800 Self::JobsGet => "jobs_get",
801 Self::JobsCreate => "jobs_create",
802 Self::JobsUpdate => "jobs_update",
803 Self::JobsPause => "jobs_pause",
804 Self::JobsResume => "jobs_resume",
805 Self::JobsRun => "jobs_run",
806 Self::JobsDelete => "jobs_delete",
807 Self::SessionsNew => "sessions_new",
808 Self::SessionsReset => "sessions_reset",
809 Self::SessionsArchive => "sessions_archive",
810 Self::SessionsDelete => "sessions_delete",
811 Self::RunsList => "runs_list",
812 Self::RunsGet => "runs_get",
813 Self::Close => "close",
814 Self::ProfilesList => "profiles_list",
815 Self::ProfilesGet => "profiles_get",
816 Self::ProfilesCreate => "profiles_create",
817 Self::ProfilesDelete => "profiles_delete",
818 Self::SkillsList => "skills_list",
819 Self::SkillsInstall => "skills_install",
820 Self::SkillsRemove => "skills_remove",
821 Self::MemoryShow => "memory_show",
822 Self::MemorySearch => "memory_search",
823 Self::ApprovalsList => "approvals_list",
824 Self::ApprovalsResolve => "approvals_resolve",
825 Self::ChannelsList => "channels_list",
826 Self::RoutesList => "routes_list",
827 Self::TriggersList => "triggers_list",
828 Self::ChannelsStatus => "channels_status",
829 Self::WorldLoad => "world_load",
830 Self::WorldSave => "world_save",
831 Self::WorldCompile => "world_compile",
832 Self::WorldDecompile => "world_decompile",
833 Self::WorldImport => "world_import",
834 Self::WorldExport => "world_export",
835 }
836 }
837
838 pub fn from_action_name(action: &str) -> Option<Self> {
840 Self::ALL
841 .into_iter()
842 .find(|operation| operation.action_name() == action)
843 }
844}
845
846#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
848pub struct SdkRequest {
849 pub operation: SdkOperation,
851 #[serde(default)]
853 pub params: Value,
854}
855
856#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
858#[serde(rename_all = "snake_case")]
859pub enum SdkErrorCode {
860 Unauthenticated,
862 Unauthorized,
864 ControllerRequired,
866 LeaseExpired,
868 InvalidArgument,
870 NotFound,
872 Busy,
874 UnsupportedAction,
876 Execution,
878 Transport,
880}
881
882#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
884pub enum RuntimeSubmitError {
885 #[error("a turn is already in progress")]
887 Busy,
888 #[error("turn interrupted")]
890 Interrupted,
891 #[error("{0}")]
893 Agent(String),
894}
895
896#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
898pub enum SdkError {
899 #[error("SDK runtime authentication required")]
901 Unauthenticated,
902 #[error("SDK runtime permission `{permission}` is required")]
904 Unauthorized {
905 permission: String,
907 },
908 #[error("controller lease required")]
911 ControllerRequired {
912 holder: Option<String>,
914 expires_at_ms: Option<u64>,
916 },
917 #[error("controller lease expired")]
919 LeaseExpired,
920 #[error("invalid SDK argument for {operation:?}: {message}")]
922 InvalidArgument {
923 operation: SdkOperation,
925 message: String,
927 },
928 #[error("SDK target for {operation:?} was not found: {message}")]
930 NotFound {
931 operation: SdkOperation,
933 message: String,
935 },
936 #[error("SDK action `{0}` is not supported by this runtime")]
938 UnsupportedAction(&'static str),
939 #[error("SDK operation `{0}` is not supported by this runtime")]
941 UnsupportedOperation(String),
942 #[error("SDK event stream lost {0} event(s); reattach for a fresh snapshot")]
944 ReplayGap(u64),
945 #[error("SDK runtime event stream closed")]
947 Closed,
948 #[error("SDK transport failed: {0}")]
950 Transport(String),
951 #[error("SDK request {0} is not pending")]
953 UnknownRequest(u64),
954 #[error("invalid SDK response: {0}")]
956 InvalidResponse(String),
957 #[error(transparent)]
959 Submit(#[from] RuntimeSubmitError),
960 #[error("SDK execution failed for {operation:?}: {message}")]
962 Execution {
963 operation: SdkOperation,
965 message: String,
967 },
968}
969
970impl SdkError {
971 pub fn new(code: SdkErrorCode, operation: SdkOperation, message: impl Into<String>) -> Self {
973 let message = message.into();
974 match code {
975 SdkErrorCode::Unauthenticated => Self::Unauthenticated,
976 SdkErrorCode::Unauthorized => Self::Unauthorized {
977 permission: message,
978 },
979 SdkErrorCode::ControllerRequired => Self::ControllerRequired {
980 holder: None,
981 expires_at_ms: None,
982 },
983 SdkErrorCode::LeaseExpired => Self::LeaseExpired,
984 SdkErrorCode::InvalidArgument => Self::InvalidArgument { operation, message },
985 SdkErrorCode::NotFound => Self::NotFound { operation, message },
986 SdkErrorCode::Busy => Self::Submit(RuntimeSubmitError::Busy),
987 SdkErrorCode::UnsupportedAction => Self::unsupported(operation),
988 SdkErrorCode::Execution => Self::Execution { operation, message },
989 SdkErrorCode::Transport => Self::Transport(message),
990 }
991 }
992
993 pub fn unsupported(operation: SdkOperation) -> Self {
995 Self::UnsupportedAction(operation.action_name())
996 }
997
998 pub fn code(&self) -> SdkErrorCode {
1000 match self {
1001 Self::Unauthenticated => SdkErrorCode::Unauthenticated,
1002 Self::Unauthorized { .. } => SdkErrorCode::Unauthorized,
1003 Self::ControllerRequired { .. } => SdkErrorCode::ControllerRequired,
1004 Self::LeaseExpired => SdkErrorCode::LeaseExpired,
1005 Self::InvalidArgument { .. } | Self::InvalidResponse(_) => {
1006 SdkErrorCode::InvalidArgument
1007 }
1008 Self::NotFound { .. } | Self::UnknownRequest(_) => SdkErrorCode::NotFound,
1009 Self::Submit(RuntimeSubmitError::Busy) => SdkErrorCode::Busy,
1010 Self::UnsupportedAction(_) | Self::UnsupportedOperation(_) => {
1011 SdkErrorCode::UnsupportedAction
1012 }
1013 Self::Transport(_) | Self::ReplayGap(_) | Self::Closed => SdkErrorCode::Transport,
1014 Self::Submit(_) | Self::Execution { .. } => SdkErrorCode::Execution,
1015 }
1016 }
1017
1018 pub fn operation(&self) -> Option<SdkOperation> {
1020 match self {
1021 Self::InvalidArgument { operation, .. }
1022 | Self::NotFound { operation, .. }
1023 | Self::Execution { operation, .. } => Some(*operation),
1024 Self::UnsupportedAction(action) => SdkOperation::from_action_name(action),
1025 Self::Unauthenticated
1026 | Self::Unauthorized { .. }
1027 | Self::ControllerRequired { .. }
1028 | Self::LeaseExpired => None,
1029 Self::UnknownRequest(_) | Self::InvalidResponse(_) => Some(SdkOperation::Respond),
1030 Self::Submit(_) => Some(SdkOperation::Input),
1031 Self::UnsupportedOperation(_)
1032 | Self::ReplayGap(_)
1033 | Self::Closed
1034 | Self::Transport(_) => None,
1035 }
1036 }
1037}
1038
1039#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1041pub struct SdkCapabilities {
1042 pub schema_version: String,
1044 pub operations: Vec<SdkOperation>,
1047 pub error_codes: Vec<SdkErrorCode>,
1049 pub opaque_events: bool,
1051}
1052
1053impl Default for SdkCapabilities {
1054 fn default() -> Self {
1055 Self {
1056 schema_version: SDK_SCHEMA_VERSION.into(),
1057 operations: SdkOperation::ALL.to_vec(),
1058 error_codes: vec![
1059 SdkErrorCode::Unauthenticated,
1060 SdkErrorCode::Unauthorized,
1061 SdkErrorCode::ControllerRequired,
1062 SdkErrorCode::LeaseExpired,
1063 SdkErrorCode::InvalidArgument,
1064 SdkErrorCode::NotFound,
1065 SdkErrorCode::Busy,
1066 SdkErrorCode::UnsupportedAction,
1067 SdkErrorCode::Execution,
1068 SdkErrorCode::Transport,
1069 ],
1070 opaque_events: true,
1071 }
1072 }
1073}
1074
1075#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1077pub struct SdkEvent {
1078 pub sequence: u64,
1080 pub kind: String,
1082 pub payload: Value,
1084}
1085
1086impl SdkEvent {
1087 pub(crate) fn new(sequence: u64, payload: Value) -> Self {
1088 let kind = payload
1089 .get("type")
1090 .or_else(|| payload.get("method"))
1091 .and_then(Value::as_str)
1092 .unwrap_or("unknown")
1093 .to_string();
1094 Self {
1095 sequence,
1096 kind,
1097 payload,
1098 }
1099 }
1100}
1101
1102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1107pub struct SdkRuntimeEvent {
1108 pub session_id: String,
1110 pub event: SdkEvent,
1112}
1113
1114#[async_trait]
1119pub trait SdkRuntime: Send + Sync {
1120 async fn describe(&self) -> Result<crate::frontend::FrontendRuntimeDescriptor, SdkError>;
1122 async fn attach(
1124 &self,
1125 history_limit: usize,
1126 ) -> Result<crate::frontend::FrontendAttachment, SdkError>;
1127 async fn send_input(self: Arc<Self>, prompt: String) -> Result<(), SdkError>;
1134 async fn send_input_with_images(
1138 self: Arc<Self>,
1139 prompt: String,
1140 image_urls: Vec<String>,
1141 ) -> Result<(), SdkError> {
1142 if image_urls.is_empty() {
1143 self.send_input(prompt).await
1144 } else {
1145 Err(SdkError::UnsupportedAction("send_input_attachments"))
1146 }
1147 }
1148 async fn submit(&self, prompt: String) -> Result<String, SdkError>;
1150 async fn submit_with_images(
1155 &self,
1156 prompt: String,
1157 image_urls: Vec<String>,
1158 ) -> Result<String, SdkError> {
1159 if image_urls.is_empty() {
1160 self.submit(prompt).await
1161 } else {
1162 Err(SdkError::UnsupportedAction("submit_attachments"))
1163 }
1164 }
1165 async fn interrupt(&self) -> Result<bool, SdkError>;
1167 async fn steer(&self, prompt: String) -> Result<(), SdkError>;
1169 async fn respond(&self, response: crate::frontend::FrontendResponse) -> Result<(), SdkError>;
1171 async fn invoke(
1173 &self,
1174 operation: crate::frontend::FrontendOperationInvocation,
1175 ) -> Result<crate::frontend::FrontendOperationResult, SdkError> {
1176 Err(SdkError::UnsupportedOperation(
1177 operation.operation_id().to_string(),
1178 ))
1179 }
1180 async fn lease_snapshot(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1182 Err(SdkError::UnsupportedOperation("runtime.lease".into()))
1183 }
1184 async fn take_control(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1187 Err(SdkError::UnsupportedOperation(
1188 "runtime.take_control".into(),
1189 ))
1190 }
1191 async fn heartbeat(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1193 Err(SdkError::UnsupportedOperation("runtime.heartbeat".into()))
1194 }
1195 async fn detach(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1198 Err(SdkError::UnsupportedOperation("runtime.detach".into()))
1199 }
1200 async fn close(&self) -> Result<(), SdkError> {
1204 Err(SdkError::unsupported(SdkOperation::Close))
1205 }
1206}
1207
1208#[async_trait]
1210pub trait SdkService: Send {
1211 fn capabilities(&self) -> SdkCapabilities {
1213 SdkCapabilities::default()
1214 }
1215
1216 async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError>;
1219
1220 async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError>;
1222}