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 OrchestrationLoad,
663 OrchestrationSave,
665 OrchestrationCompile,
667 OrchestrationDecompile,
669 OrchestrationImport,
671 OrchestrationExport,
673 WorkflowLoad,
675}
676
677impl SdkOperation {
678 pub const ALL: [Self; 47] = [
680 Self::Discover,
681 Self::Load,
682 Self::Start,
683 Self::Resume,
684 Self::Input,
685 Self::Events,
686 Self::Interrupt,
687 Self::Steer,
688 Self::Respond,
689 Self::Export,
690 Self::JobsList,
691 Self::JobsGet,
692 Self::JobsCreate,
693 Self::JobsUpdate,
694 Self::JobsPause,
695 Self::JobsResume,
696 Self::JobsRun,
697 Self::JobsDelete,
698 Self::SessionsNew,
699 Self::SessionsReset,
700 Self::SessionsArchive,
701 Self::SessionsDelete,
702 Self::RunsList,
703 Self::RunsGet,
704 Self::Close,
705 Self::ProfilesList,
706 Self::ProfilesGet,
707 Self::ProfilesCreate,
708 Self::ProfilesDelete,
709 Self::SkillsList,
710 Self::SkillsInstall,
711 Self::SkillsRemove,
712 Self::MemoryShow,
713 Self::MemorySearch,
714 Self::ApprovalsList,
715 Self::ApprovalsResolve,
716 Self::ChannelsList,
717 Self::RoutesList,
718 Self::TriggersList,
719 Self::ChannelsStatus,
720 Self::OrchestrationLoad,
721 Self::OrchestrationSave,
722 Self::OrchestrationCompile,
723 Self::OrchestrationDecompile,
724 Self::OrchestrationImport,
725 Self::OrchestrationExport,
726 Self::WorkflowLoad,
727 ];
728
729 pub const fn method(self) -> Option<&'static str> {
732 match self {
733 Self::Discover => Some("harness.v1.sessions.discover"),
734 Self::Load => Some("harness.v1.sessions.load"),
735 Self::Start => Some("harness.v1.runtimes.start"),
736 Self::Resume => Some("harness.v1.runtimes.resume"),
737 Self::Input => Some("harness.v1.runtimes.send_input"),
738 Self::Events => None,
739 Self::Interrupt => Some("harness.v1.runtimes.interrupt"),
740 Self::Steer => Some("harness.v1.runtimes.steer"),
741 Self::Respond => Some("harness.v1.runtimes.respond"),
742 Self::Export => Some("harness.v1.sessions.export"),
743 Self::JobsList => Some("harness.v1.jobs.list"),
744 Self::JobsGet => Some("harness.v1.jobs.get"),
745 Self::JobsCreate => Some("harness.v1.jobs.create"),
746 Self::JobsUpdate => Some("harness.v1.jobs.update"),
747 Self::JobsPause => Some("harness.v1.jobs.pause"),
748 Self::JobsResume => Some("harness.v1.jobs.resume"),
749 Self::JobsRun => Some("harness.v1.jobs.run"),
750 Self::JobsDelete => Some("harness.v1.jobs.delete"),
751 Self::SessionsNew => Some("harness.v1.sessions.new"),
752 Self::SessionsReset => Some("harness.v1.sessions.reset"),
753 Self::SessionsArchive => Some("harness.v1.sessions.archive"),
754 Self::SessionsDelete => Some("harness.v1.sessions.delete"),
755 Self::RunsList => Some("harness.v1.runs.list"),
756 Self::RunsGet => Some("harness.v1.runs.get"),
757 Self::Close => Some("harness.v1.runtimes.close"),
758 Self::ProfilesList => Some("harness.v1.profiles.list"),
759 Self::ProfilesGet => Some("harness.v1.profiles.get"),
760 Self::ProfilesCreate => Some("harness.v1.profiles.create"),
761 Self::ProfilesDelete => Some("harness.v1.profiles.delete"),
762 Self::SkillsList => Some("harness.v1.skills.list"),
763 Self::SkillsInstall => Some("harness.v1.skills.install"),
764 Self::SkillsRemove => Some("harness.v1.skills.remove"),
765 Self::MemoryShow => Some("harness.v1.memory.show"),
766 Self::MemorySearch => Some("harness.v1.memory.search"),
767 Self::ApprovalsList => Some("harness.v1.approvals.list"),
768 Self::ApprovalsResolve => Some("harness.v1.approvals.resolve"),
769 Self::ChannelsList => Some("harness.v1.channels.list"),
770 Self::RoutesList => Some("harness.v1.routes.list"),
771 Self::TriggersList => Some("harness.v1.triggers.list"),
772 Self::ChannelsStatus => Some("harness.v1.channels.status"),
773 Self::OrchestrationLoad => Some("harness.v1.orchestration.load"),
774 Self::OrchestrationSave => Some("harness.v1.orchestration.save"),
775 Self::OrchestrationCompile => Some("harness.v1.orchestration.compile"),
776 Self::OrchestrationDecompile => Some("harness.v1.orchestration.decompile"),
777 Self::OrchestrationImport => Some("harness.v1.orchestration.import"),
778 Self::OrchestrationExport => Some("harness.v1.orchestration.export"),
779 Self::WorkflowLoad => Some("harness.v1.workflow.load"),
780 }
781 }
782
783 pub fn from_method(method: &str) -> Option<Self> {
785 Self::ALL
786 .into_iter()
787 .find(|operation| operation.method() == Some(method))
788 }
789
790 pub const fn action_name(self) -> &'static str {
792 match self {
793 Self::Discover => "discover",
794 Self::Load => "load",
795 Self::Start => "start",
796 Self::Resume => "resume",
797 Self::Input => "input",
798 Self::Events => "events",
799 Self::Interrupt => "interrupt",
800 Self::Steer => "steer",
801 Self::Respond => "respond",
802 Self::Export => "export",
803 Self::JobsList => "jobs_list",
804 Self::JobsGet => "jobs_get",
805 Self::JobsCreate => "jobs_create",
806 Self::JobsUpdate => "jobs_update",
807 Self::JobsPause => "jobs_pause",
808 Self::JobsResume => "jobs_resume",
809 Self::JobsRun => "jobs_run",
810 Self::JobsDelete => "jobs_delete",
811 Self::SessionsNew => "sessions_new",
812 Self::SessionsReset => "sessions_reset",
813 Self::SessionsArchive => "sessions_archive",
814 Self::SessionsDelete => "sessions_delete",
815 Self::RunsList => "runs_list",
816 Self::RunsGet => "runs_get",
817 Self::Close => "close",
818 Self::ProfilesList => "profiles_list",
819 Self::ProfilesGet => "profiles_get",
820 Self::ProfilesCreate => "profiles_create",
821 Self::ProfilesDelete => "profiles_delete",
822 Self::SkillsList => "skills_list",
823 Self::SkillsInstall => "skills_install",
824 Self::SkillsRemove => "skills_remove",
825 Self::MemoryShow => "memory_show",
826 Self::MemorySearch => "memory_search",
827 Self::ApprovalsList => "approvals_list",
828 Self::ApprovalsResolve => "approvals_resolve",
829 Self::ChannelsList => "channels_list",
830 Self::RoutesList => "routes_list",
831 Self::TriggersList => "triggers_list",
832 Self::ChannelsStatus => "channels_status",
833 Self::OrchestrationLoad => "orchestration_load",
834 Self::OrchestrationSave => "orchestration_save",
835 Self::OrchestrationCompile => "orchestration_compile",
836 Self::OrchestrationDecompile => "orchestration_decompile",
837 Self::OrchestrationImport => "orchestration_import",
838 Self::OrchestrationExport => "orchestration_export",
839 Self::WorkflowLoad => "workflow_load",
840 }
841 }
842
843 pub fn from_action_name(action: &str) -> Option<Self> {
845 Self::ALL
846 .into_iter()
847 .find(|operation| operation.action_name() == action)
848 }
849}
850
851#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
853pub struct SdkRequest {
854 pub operation: SdkOperation,
856 #[serde(default)]
858 pub params: Value,
859}
860
861#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
863#[serde(rename_all = "snake_case")]
864pub enum SdkErrorCode {
865 Unauthenticated,
867 Unauthorized,
869 ControllerRequired,
871 LeaseExpired,
873 InvalidArgument,
875 NotFound,
877 Busy,
879 UnsupportedAction,
881 Execution,
883 Transport,
885}
886
887#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
889pub enum RuntimeSubmitError {
890 #[error("a turn is already in progress")]
892 Busy,
893 #[error("turn interrupted")]
895 Interrupted,
896 #[error("{0}")]
898 Agent(String),
899}
900
901#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
903pub enum SdkError {
904 #[error("SDK runtime authentication required")]
906 Unauthenticated,
907 #[error("SDK runtime permission `{permission}` is required")]
909 Unauthorized {
910 permission: String,
912 },
913 #[error("controller lease required")]
916 ControllerRequired {
917 holder: Option<String>,
919 expires_at_ms: Option<u64>,
921 },
922 #[error("controller lease expired")]
924 LeaseExpired,
925 #[error("invalid SDK argument for {operation:?}: {message}")]
927 InvalidArgument {
928 operation: SdkOperation,
930 message: String,
932 },
933 #[error("SDK target for {operation:?} was not found: {message}")]
935 NotFound {
936 operation: SdkOperation,
938 message: String,
940 },
941 #[error("SDK action `{0}` is not supported by this runtime")]
943 UnsupportedAction(&'static str),
944 #[error("SDK operation `{0}` is not supported by this runtime")]
946 UnsupportedOperation(String),
947 #[error("SDK event stream lost {0} event(s); reattach for a fresh snapshot")]
949 ReplayGap(u64),
950 #[error("SDK runtime event stream closed")]
952 Closed,
953 #[error("SDK transport failed: {0}")]
955 Transport(String),
956 #[error("SDK request {0} is not pending")]
958 UnknownRequest(u64),
959 #[error("invalid SDK response: {0}")]
961 InvalidResponse(String),
962 #[error(transparent)]
964 Submit(#[from] RuntimeSubmitError),
965 #[error("SDK execution failed for {operation:?}: {message}")]
967 Execution {
968 operation: SdkOperation,
970 message: String,
972 },
973}
974
975impl SdkError {
976 pub fn new(code: SdkErrorCode, operation: SdkOperation, message: impl Into<String>) -> Self {
978 let message = message.into();
979 match code {
980 SdkErrorCode::Unauthenticated => Self::Unauthenticated,
981 SdkErrorCode::Unauthorized => Self::Unauthorized {
982 permission: message,
983 },
984 SdkErrorCode::ControllerRequired => Self::ControllerRequired {
985 holder: None,
986 expires_at_ms: None,
987 },
988 SdkErrorCode::LeaseExpired => Self::LeaseExpired,
989 SdkErrorCode::InvalidArgument => Self::InvalidArgument { operation, message },
990 SdkErrorCode::NotFound => Self::NotFound { operation, message },
991 SdkErrorCode::Busy => Self::Submit(RuntimeSubmitError::Busy),
992 SdkErrorCode::UnsupportedAction => Self::unsupported(operation),
993 SdkErrorCode::Execution => Self::Execution { operation, message },
994 SdkErrorCode::Transport => Self::Transport(message),
995 }
996 }
997
998 pub fn unsupported(operation: SdkOperation) -> Self {
1000 Self::UnsupportedAction(operation.action_name())
1001 }
1002
1003 pub fn code(&self) -> SdkErrorCode {
1005 match self {
1006 Self::Unauthenticated => SdkErrorCode::Unauthenticated,
1007 Self::Unauthorized { .. } => SdkErrorCode::Unauthorized,
1008 Self::ControllerRequired { .. } => SdkErrorCode::ControllerRequired,
1009 Self::LeaseExpired => SdkErrorCode::LeaseExpired,
1010 Self::InvalidArgument { .. } | Self::InvalidResponse(_) => {
1011 SdkErrorCode::InvalidArgument
1012 }
1013 Self::NotFound { .. } | Self::UnknownRequest(_) => SdkErrorCode::NotFound,
1014 Self::Submit(RuntimeSubmitError::Busy) => SdkErrorCode::Busy,
1015 Self::UnsupportedAction(_) | Self::UnsupportedOperation(_) => {
1016 SdkErrorCode::UnsupportedAction
1017 }
1018 Self::Transport(_) | Self::ReplayGap(_) | Self::Closed => SdkErrorCode::Transport,
1019 Self::Submit(_) | Self::Execution { .. } => SdkErrorCode::Execution,
1020 }
1021 }
1022
1023 pub fn operation(&self) -> Option<SdkOperation> {
1025 match self {
1026 Self::InvalidArgument { operation, .. }
1027 | Self::NotFound { operation, .. }
1028 | Self::Execution { operation, .. } => Some(*operation),
1029 Self::UnsupportedAction(action) => SdkOperation::from_action_name(action),
1030 Self::Unauthenticated
1031 | Self::Unauthorized { .. }
1032 | Self::ControllerRequired { .. }
1033 | Self::LeaseExpired => None,
1034 Self::UnknownRequest(_) | Self::InvalidResponse(_) => Some(SdkOperation::Respond),
1035 Self::Submit(_) => Some(SdkOperation::Input),
1036 Self::UnsupportedOperation(_)
1037 | Self::ReplayGap(_)
1038 | Self::Closed
1039 | Self::Transport(_) => None,
1040 }
1041 }
1042}
1043
1044#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1046pub struct SdkCapabilities {
1047 pub schema_version: String,
1049 pub operations: Vec<SdkOperation>,
1052 pub error_codes: Vec<SdkErrorCode>,
1054 pub opaque_events: bool,
1056}
1057
1058impl Default for SdkCapabilities {
1059 fn default() -> Self {
1060 Self {
1061 schema_version: SDK_SCHEMA_VERSION.into(),
1062 operations: SdkOperation::ALL.to_vec(),
1063 error_codes: vec![
1064 SdkErrorCode::Unauthenticated,
1065 SdkErrorCode::Unauthorized,
1066 SdkErrorCode::ControllerRequired,
1067 SdkErrorCode::LeaseExpired,
1068 SdkErrorCode::InvalidArgument,
1069 SdkErrorCode::NotFound,
1070 SdkErrorCode::Busy,
1071 SdkErrorCode::UnsupportedAction,
1072 SdkErrorCode::Execution,
1073 SdkErrorCode::Transport,
1074 ],
1075 opaque_events: true,
1076 }
1077 }
1078}
1079
1080#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1082pub struct SdkEvent {
1083 pub sequence: u64,
1085 pub kind: String,
1087 pub payload: Value,
1089}
1090
1091impl SdkEvent {
1092 pub(crate) fn new(sequence: u64, payload: Value) -> Self {
1093 let kind = payload
1094 .get("type")
1095 .or_else(|| payload.get("method"))
1096 .and_then(Value::as_str)
1097 .unwrap_or("unknown")
1098 .to_string();
1099 Self {
1100 sequence,
1101 kind,
1102 payload,
1103 }
1104 }
1105}
1106
1107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1112pub struct SdkRuntimeEvent {
1113 pub session_id: String,
1115 pub event: SdkEvent,
1117}
1118
1119#[async_trait]
1124pub trait SdkRuntime: Send + Sync {
1125 async fn describe(&self) -> Result<crate::frontend::FrontendRuntimeDescriptor, SdkError>;
1127 async fn attach(
1129 &self,
1130 history_limit: usize,
1131 ) -> Result<crate::frontend::FrontendAttachment, SdkError>;
1132 async fn send_input(self: Arc<Self>, prompt: String) -> Result<(), SdkError>;
1139 async fn send_input_with_images(
1143 self: Arc<Self>,
1144 prompt: String,
1145 image_urls: Vec<String>,
1146 ) -> Result<(), SdkError> {
1147 if image_urls.is_empty() {
1148 self.send_input(prompt).await
1149 } else {
1150 Err(SdkError::UnsupportedAction("send_input_attachments"))
1151 }
1152 }
1153 async fn submit(&self, prompt: String) -> Result<String, SdkError>;
1155 async fn submit_with_images(
1160 &self,
1161 prompt: String,
1162 image_urls: Vec<String>,
1163 ) -> Result<String, SdkError> {
1164 if image_urls.is_empty() {
1165 self.submit(prompt).await
1166 } else {
1167 Err(SdkError::UnsupportedAction("submit_attachments"))
1168 }
1169 }
1170 async fn interrupt(&self) -> Result<bool, SdkError>;
1172 async fn steer(&self, prompt: String) -> Result<(), SdkError>;
1174 async fn respond(&self, response: crate::frontend::FrontendResponse) -> Result<(), SdkError>;
1176 async fn invoke(
1178 &self,
1179 operation: crate::frontend::FrontendOperationInvocation,
1180 ) -> Result<crate::frontend::FrontendOperationResult, SdkError> {
1181 Err(SdkError::UnsupportedOperation(
1182 operation.operation_id().to_string(),
1183 ))
1184 }
1185 async fn lease_snapshot(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1187 Err(SdkError::UnsupportedOperation("runtime.lease".into()))
1188 }
1189 async fn take_control(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1192 Err(SdkError::UnsupportedOperation(
1193 "runtime.take_control".into(),
1194 ))
1195 }
1196 async fn heartbeat(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1198 Err(SdkError::UnsupportedOperation("runtime.heartbeat".into()))
1199 }
1200 async fn detach(&self) -> Result<crate::RuntimeLeaseSnapshot, SdkError> {
1203 Err(SdkError::UnsupportedOperation("runtime.detach".into()))
1204 }
1205 async fn close(&self) -> Result<(), SdkError> {
1209 Err(SdkError::unsupported(SdkOperation::Close))
1210 }
1211}
1212
1213#[async_trait]
1215pub trait SdkService: Send {
1216 fn capabilities(&self) -> SdkCapabilities {
1218 SdkCapabilities::default()
1219 }
1220
1221 async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError>;
1224
1225 async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError>;
1227}