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 JobsNotepad,
619 JobsNotepadSet,
621 JobsNotepadDelete,
623 SessionsNew,
625 SessionsReset,
627 SessionsArchive,
629 SessionsDelete,
631 RunsList,
633 RunsGet,
635 Close,
637 ProfilesList,
639 ProfilesGet,
641 ProfilesCreate,
643 ProfilesDelete,
645 SkillsList,
647 SkillsInstall,
649 SkillsRemove,
651 MemoryShow,
653 MemorySearch,
655 ApprovalsList,
657 ApprovalsResolve,
659 ChannelsList,
661 RoutesList,
663 TriggersList,
665 ChannelsStatus,
667 OrchestrationLoad,
669 OrchestrationSave,
671 OrchestrationCompile,
673 OrchestrationDecompile,
675 OrchestrationImport,
677 OrchestrationExport,
679 WorkflowLoad,
681}
682
683impl SdkOperation {
684 pub const ALL: [Self; 50] = [
686 Self::Discover,
687 Self::Load,
688 Self::Start,
689 Self::Resume,
690 Self::Input,
691 Self::Events,
692 Self::Interrupt,
693 Self::Steer,
694 Self::Respond,
695 Self::Export,
696 Self::JobsList,
697 Self::JobsGet,
698 Self::JobsCreate,
699 Self::JobsUpdate,
700 Self::JobsPause,
701 Self::JobsResume,
702 Self::JobsRun,
703 Self::JobsDelete,
704 Self::JobsNotepad,
705 Self::JobsNotepadSet,
706 Self::JobsNotepadDelete,
707 Self::SessionsNew,
708 Self::SessionsReset,
709 Self::SessionsArchive,
710 Self::SessionsDelete,
711 Self::RunsList,
712 Self::RunsGet,
713 Self::Close,
714 Self::ProfilesList,
715 Self::ProfilesGet,
716 Self::ProfilesCreate,
717 Self::ProfilesDelete,
718 Self::SkillsList,
719 Self::SkillsInstall,
720 Self::SkillsRemove,
721 Self::MemoryShow,
722 Self::MemorySearch,
723 Self::ApprovalsList,
724 Self::ApprovalsResolve,
725 Self::ChannelsList,
726 Self::RoutesList,
727 Self::TriggersList,
728 Self::ChannelsStatus,
729 Self::OrchestrationLoad,
730 Self::OrchestrationSave,
731 Self::OrchestrationCompile,
732 Self::OrchestrationDecompile,
733 Self::OrchestrationImport,
734 Self::OrchestrationExport,
735 Self::WorkflowLoad,
736 ];
737
738 pub const fn method(self) -> Option<&'static str> {
741 match self {
742 Self::Discover => Some("harness.v1.sessions.discover"),
743 Self::Load => Some("harness.v1.sessions.load"),
744 Self::Start => Some("harness.v1.runtimes.start"),
745 Self::Resume => Some("harness.v1.runtimes.resume"),
746 Self::Input => Some("harness.v1.runtimes.send_input"),
747 Self::Events => None,
748 Self::Interrupt => Some("harness.v1.runtimes.interrupt"),
749 Self::Steer => Some("harness.v1.runtimes.steer"),
750 Self::Respond => Some("harness.v1.runtimes.respond"),
751 Self::Export => Some("harness.v1.sessions.export"),
752 Self::JobsList => Some("harness.v1.jobs.list"),
753 Self::JobsGet => Some("harness.v1.jobs.get"),
754 Self::JobsCreate => Some("harness.v1.jobs.create"),
755 Self::JobsUpdate => Some("harness.v1.jobs.update"),
756 Self::JobsPause => Some("harness.v1.jobs.pause"),
757 Self::JobsResume => Some("harness.v1.jobs.resume"),
758 Self::JobsRun => Some("harness.v1.jobs.run"),
759 Self::JobsDelete => Some("harness.v1.jobs.delete"),
760 Self::JobsNotepad => Some("harness.v1.jobs.notepad"),
761 Self::JobsNotepadSet => Some("harness.v1.jobs.notepad_set"),
762 Self::JobsNotepadDelete => Some("harness.v1.jobs.notepad_delete"),
763 Self::SessionsNew => Some("harness.v1.sessions.new"),
764 Self::SessionsReset => Some("harness.v1.sessions.reset"),
765 Self::SessionsArchive => Some("harness.v1.sessions.archive"),
766 Self::SessionsDelete => Some("harness.v1.sessions.delete"),
767 Self::RunsList => Some("harness.v1.runs.list"),
768 Self::RunsGet => Some("harness.v1.runs.get"),
769 Self::Close => Some("harness.v1.runtimes.close"),
770 Self::ProfilesList => Some("harness.v1.profiles.list"),
771 Self::ProfilesGet => Some("harness.v1.profiles.get"),
772 Self::ProfilesCreate => Some("harness.v1.profiles.create"),
773 Self::ProfilesDelete => Some("harness.v1.profiles.delete"),
774 Self::SkillsList => Some("harness.v1.skills.list"),
775 Self::SkillsInstall => Some("harness.v1.skills.install"),
776 Self::SkillsRemove => Some("harness.v1.skills.remove"),
777 Self::MemoryShow => Some("harness.v1.memory.show"),
778 Self::MemorySearch => Some("harness.v1.memory.search"),
779 Self::ApprovalsList => Some("harness.v1.approvals.list"),
780 Self::ApprovalsResolve => Some("harness.v1.approvals.resolve"),
781 Self::ChannelsList => Some("harness.v1.channels.list"),
782 Self::RoutesList => Some("harness.v1.routes.list"),
783 Self::TriggersList => Some("harness.v1.triggers.list"),
784 Self::ChannelsStatus => Some("harness.v1.channels.status"),
785 Self::OrchestrationLoad => Some("harness.v1.orchestration.load"),
786 Self::OrchestrationSave => Some("harness.v1.orchestration.save"),
787 Self::OrchestrationCompile => Some("harness.v1.orchestration.compile"),
788 Self::OrchestrationDecompile => Some("harness.v1.orchestration.decompile"),
789 Self::OrchestrationImport => Some("harness.v1.orchestration.import"),
790 Self::OrchestrationExport => Some("harness.v1.orchestration.export"),
791 Self::WorkflowLoad => Some("harness.v1.workflow.load"),
792 }
793 }
794
795 pub fn from_method(method: &str) -> Option<Self> {
797 Self::ALL
798 .into_iter()
799 .find(|operation| operation.method() == Some(method))
800 }
801
802 pub const fn action_name(self) -> &'static str {
804 match self {
805 Self::Discover => "discover",
806 Self::Load => "load",
807 Self::Start => "start",
808 Self::Resume => "resume",
809 Self::Input => "input",
810 Self::Events => "events",
811 Self::Interrupt => "interrupt",
812 Self::Steer => "steer",
813 Self::Respond => "respond",
814 Self::Export => "export",
815 Self::JobsList => "jobs_list",
816 Self::JobsGet => "jobs_get",
817 Self::JobsCreate => "jobs_create",
818 Self::JobsUpdate => "jobs_update",
819 Self::JobsPause => "jobs_pause",
820 Self::JobsResume => "jobs_resume",
821 Self::JobsRun => "jobs_run",
822 Self::JobsDelete => "jobs_delete",
823 Self::JobsNotepad => "jobs_notepad",
824 Self::JobsNotepadSet => "jobs_notepad_set",
825 Self::JobsNotepadDelete => "jobs_notepad_delete",
826 Self::SessionsNew => "sessions_new",
827 Self::SessionsReset => "sessions_reset",
828 Self::SessionsArchive => "sessions_archive",
829 Self::SessionsDelete => "sessions_delete",
830 Self::RunsList => "runs_list",
831 Self::RunsGet => "runs_get",
832 Self::Close => "close",
833 Self::ProfilesList => "profiles_list",
834 Self::ProfilesGet => "profiles_get",
835 Self::ProfilesCreate => "profiles_create",
836 Self::ProfilesDelete => "profiles_delete",
837 Self::SkillsList => "skills_list",
838 Self::SkillsInstall => "skills_install",
839 Self::SkillsRemove => "skills_remove",
840 Self::MemoryShow => "memory_show",
841 Self::MemorySearch => "memory_search",
842 Self::ApprovalsList => "approvals_list",
843 Self::ApprovalsResolve => "approvals_resolve",
844 Self::ChannelsList => "channels_list",
845 Self::RoutesList => "routes_list",
846 Self::TriggersList => "triggers_list",
847 Self::ChannelsStatus => "channels_status",
848 Self::OrchestrationLoad => "orchestration_load",
849 Self::OrchestrationSave => "orchestration_save",
850 Self::OrchestrationCompile => "orchestration_compile",
851 Self::OrchestrationDecompile => "orchestration_decompile",
852 Self::OrchestrationImport => "orchestration_import",
853 Self::OrchestrationExport => "orchestration_export",
854 Self::WorkflowLoad => "workflow_load",
855 }
856 }
857
858 pub fn from_action_name(action: &str) -> Option<Self> {
860 Self::ALL
861 .into_iter()
862 .find(|operation| operation.action_name() == action)
863 }
864}
865
866#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
868pub struct SdkRequest {
869 pub operation: SdkOperation,
871 #[serde(default)]
873 pub params: Value,
874}
875
876#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
878#[serde(rename_all = "snake_case")]
879pub enum SdkErrorCode {
880 Unauthenticated,
882 Unauthorized,
884 ControllerRequired,
886 LeaseExpired,
888 InvalidArgument,
890 NotFound,
892 Busy,
894 UnsupportedAction,
896 Execution,
898 Transport,
900}
901
902#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
904pub enum RuntimeSubmitError {
905 #[error("a turn is already in progress")]
907 Busy,
908 #[error("turn interrupted")]
910 Interrupted,
911 #[error("{0}")]
913 Agent(String),
914}
915
916#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
918pub enum SdkError {
919 #[error("SDK runtime authentication required")]
921 Unauthenticated,
922 #[error("SDK runtime permission `{permission}` is required")]
924 Unauthorized {
925 permission: String,
927 },
928 #[error("controller lease required")]
931 ControllerRequired {
932 holder: Option<String>,
934 expires_at_ms: Option<u64>,
936 },
937 #[error("controller lease expired")]
939 LeaseExpired,
940 #[error("invalid SDK argument for {operation:?}: {message}")]
942 InvalidArgument {
943 operation: SdkOperation,
945 message: String,
947 },
948 #[error("SDK target for {operation:?} was not found: {message}")]
950 NotFound {
951 operation: SdkOperation,
953 message: String,
955 },
956 #[error("SDK action `{0}` is not supported by this runtime")]
958 UnsupportedAction(&'static str),
959 #[error("SDK operation `{0}` is not supported by this runtime")]
961 UnsupportedOperation(String),
962 #[error("SDK event stream lost {0} event(s); reattach for a fresh snapshot")]
964 ReplayGap(u64),
965 #[error("SDK runtime event stream closed")]
967 Closed,
968 #[error("SDK transport failed: {0}")]
970 Transport(String),
971 #[error("SDK request {0} is not pending")]
973 UnknownRequest(u64),
974 #[error("invalid SDK response: {0}")]
976 InvalidResponse(String),
977 #[error(transparent)]
979 Submit(#[from] RuntimeSubmitError),
980 #[error("SDK execution failed for {operation:?}: {message}")]
982 Execution {
983 operation: SdkOperation,
985 message: String,
987 },
988}
989
990impl SdkError {
991 pub fn new(code: SdkErrorCode, operation: SdkOperation, message: impl Into<String>) -> Self {
993 let message = message.into();
994 match code {
995 SdkErrorCode::Unauthenticated => Self::Unauthenticated,
996 SdkErrorCode::Unauthorized => Self::Unauthorized {
997 permission: message,
998 },
999 SdkErrorCode::ControllerRequired => Self::ControllerRequired {
1000 holder: None,
1001 expires_at_ms: None,
1002 },
1003 SdkErrorCode::LeaseExpired => Self::LeaseExpired,
1004 SdkErrorCode::InvalidArgument => Self::InvalidArgument { operation, message },
1005 SdkErrorCode::NotFound => Self::NotFound { operation, message },
1006 SdkErrorCode::Busy => Self::Submit(RuntimeSubmitError::Busy),
1007 SdkErrorCode::UnsupportedAction => Self::unsupported(operation),
1008 SdkErrorCode::Execution => Self::Execution { operation, message },
1009 SdkErrorCode::Transport => Self::Transport(message),
1010 }
1011 }
1012
1013 pub fn unsupported(operation: SdkOperation) -> Self {
1015 Self::UnsupportedAction(operation.action_name())
1016 }
1017
1018 pub fn code(&self) -> SdkErrorCode {
1020 match self {
1021 Self::Unauthenticated => SdkErrorCode::Unauthenticated,
1022 Self::Unauthorized { .. } => SdkErrorCode::Unauthorized,
1023 Self::ControllerRequired { .. } => SdkErrorCode::ControllerRequired,
1024 Self::LeaseExpired => SdkErrorCode::LeaseExpired,
1025 Self::InvalidArgument { .. } | Self::InvalidResponse(_) => {
1026 SdkErrorCode::InvalidArgument
1027 }
1028 Self::NotFound { .. } | Self::UnknownRequest(_) => SdkErrorCode::NotFound,
1029 Self::Submit(RuntimeSubmitError::Busy) => SdkErrorCode::Busy,
1030 Self::UnsupportedAction(_) | Self::UnsupportedOperation(_) => {
1031 SdkErrorCode::UnsupportedAction
1032 }
1033 Self::Transport(_) | Self::ReplayGap(_) | Self::Closed => SdkErrorCode::Transport,
1034 Self::Submit(_) | Self::Execution { .. } => SdkErrorCode::Execution,
1035 }
1036 }
1037
1038 pub fn operation(&self) -> Option<SdkOperation> {
1040 match self {
1041 Self::InvalidArgument { operation, .. }
1042 | Self::NotFound { operation, .. }
1043 | Self::Execution { operation, .. } => Some(*operation),
1044 Self::UnsupportedAction(action) => SdkOperation::from_action_name(action),
1045 Self::Unauthenticated
1046 | Self::Unauthorized { .. }
1047 | Self::ControllerRequired { .. }
1048 | Self::LeaseExpired => None,
1049 Self::UnknownRequest(_) | Self::InvalidResponse(_) => Some(SdkOperation::Respond),
1050 Self::Submit(_) => Some(SdkOperation::Input),
1051 Self::UnsupportedOperation(_)
1052 | Self::ReplayGap(_)
1053 | Self::Closed
1054 | Self::Transport(_) => None,
1055 }
1056 }
1057}
1058
1059#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1061pub struct SdkCapabilities {
1062 pub schema_version: String,
1064 pub operations: Vec<SdkOperation>,
1067 pub error_codes: Vec<SdkErrorCode>,
1069 pub opaque_events: bool,
1071}
1072
1073impl Default for SdkCapabilities {
1074 fn default() -> Self {
1075 Self {
1076 schema_version: SDK_SCHEMA_VERSION.into(),
1077 operations: SdkOperation::ALL.to_vec(),
1078 error_codes: vec![
1079 SdkErrorCode::Unauthenticated,
1080 SdkErrorCode::Unauthorized,
1081 SdkErrorCode::ControllerRequired,
1082 SdkErrorCode::LeaseExpired,
1083 SdkErrorCode::InvalidArgument,
1084 SdkErrorCode::NotFound,
1085 SdkErrorCode::Busy,
1086 SdkErrorCode::UnsupportedAction,
1087 SdkErrorCode::Execution,
1088 SdkErrorCode::Transport,
1089 ],
1090 opaque_events: true,
1091 }
1092 }
1093}
1094
1095#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1097pub struct SdkEvent {
1098 pub sequence: u64,
1100 pub kind: String,
1102 pub payload: Value,
1104}
1105
1106impl SdkEvent {
1107 pub(crate) fn new(sequence: u64, payload: Value) -> Self {
1108 let kind = payload
1109 .get("type")
1110 .or_else(|| payload.get("method"))
1111 .and_then(Value::as_str)
1112 .unwrap_or("unknown")
1113 .to_string();
1114 Self {
1115 sequence,
1116 kind,
1117 payload,
1118 }
1119 }
1120}
1121
1122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1127pub struct SdkRuntimeEvent {
1128 pub session_id: String,
1130 pub event: SdkEvent,
1132}
1133
1134#[async_trait]
1139pub trait SdkRuntime: Send + Sync {
1140 async fn describe(&self) -> Result<crate::frontend::FrontendRuntimeDescriptor, SdkError>;
1142 async fn attach(
1144 &self,
1145 history_limit: usize,
1146 ) -> Result<crate::frontend::FrontendAttachment, SdkError>;
1147 async fn send_input(self: Arc<Self>, prompt: String) -> Result<(), SdkError>;
1154 async fn send_input_with_images(
1158 self: Arc<Self>,
1159 prompt: String,
1160 image_urls: Vec<String>,
1161 ) -> Result<(), SdkError> {
1162 if image_urls.is_empty() {
1163 self.send_input(prompt).await
1164 } else {
1165 Err(SdkError::UnsupportedAction("send_input_attachments"))
1166 }
1167 }
1168 async fn submit(&self, prompt: String) -> Result<String, SdkError>;
1170 async fn submit_with_images(
1175 &self,
1176 prompt: String,
1177 image_urls: Vec<String>,
1178 ) -> Result<String, SdkError> {
1179 if image_urls.is_empty() {
1180 self.submit(prompt).await
1181 } else {
1182 Err(SdkError::UnsupportedAction("submit_attachments"))
1183 }
1184 }
1185 async fn interrupt(&self) -> Result<bool, SdkError>;
1187 async fn steer(&self, prompt: String) -> Result<(), SdkError>;
1189 async fn respond(&self, response: crate::frontend::FrontendResponse) -> Result<(), SdkError>;
1191 async fn invoke(
1193 &self,
1194 operation: crate::frontend::FrontendOperationInvocation,
1195 ) -> Result<crate::frontend::FrontendOperationResult, SdkError> {
1196 Err(SdkError::UnsupportedOperation(
1197 operation.operation_id().to_string(),
1198 ))
1199 }
1200 async fn lease_snapshot(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1202 Err(SdkError::UnsupportedOperation("runtime.lease".into()))
1203 }
1204 async fn take_control(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1207 Err(SdkError::UnsupportedOperation(
1208 "runtime.take_control".into(),
1209 ))
1210 }
1211 async fn acquire_control(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1213 Err(SdkError::UnsupportedOperation(
1214 "runtime.acquire_control".into(),
1215 ))
1216 }
1217 async fn heartbeat(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1219 Err(SdkError::UnsupportedOperation("runtime.heartbeat".into()))
1220 }
1221 async fn detach(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1224 Err(SdkError::UnsupportedOperation("runtime.detach".into()))
1225 }
1226 async fn close(&self) -> Result<(), SdkError> {
1230 Err(SdkError::unsupported(SdkOperation::Close))
1231 }
1232}
1233
1234#[async_trait]
1236pub trait SdkService: Send {
1237 fn capabilities(&self) -> SdkCapabilities {
1239 SdkCapabilities::default()
1240 }
1241
1242 async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError>;
1245
1246 async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError>;
1248}