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