sdkClient.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /**
  2. * OpenCode SDK client instance
  3. * Configured to connect to the OpenCode server at the default location.
  4. *
  5. * When `window.__OPENCODE_SERVER_URL__` is set (e.g. injected by the VS Code
  6. * gui-only plugin), all API requests target that absolute URL. When absent,
  7. * relative URLs are used — identical to the original behaviour.
  8. */
  9. import { createOpencodeClient, type Provider } from "@opencode-ai/sdk/client"
  10. export const serverBase: string =
  11. ((globalThis as any).__OPENCODE_SERVER_URL__ as string | undefined)?.replace(/\/$/, "") || ""
  12. const baseClient = createOpencodeClient({ baseUrl: serverBase || "/" })
  13. interface ProvidersResponse {
  14. providers: Provider[]
  15. default: Record<string, string>
  16. }
  17. interface ModelEntry {
  18. providerID: string
  19. modelID: string
  20. }
  21. interface ModelPreferences {
  22. recent: ModelEntry[]
  23. favorite: ModelEntry[]
  24. variant?: Record<string, string>
  25. }
  26. interface PathResponse {
  27. state: string
  28. config: string
  29. worktree: string
  30. directory: string
  31. }
  32. /**
  33. * Extended SDK client with state management methods
  34. * TODO: Remove once SDK is regenerated with Stainless
  35. */
  36. export const sdk = {
  37. ...baseClient,
  38. session: Object.assign(baseClient.session, {
  39. retry: async (options: { path: { sessionID: string } }) => {
  40. try {
  41. const response = await fetch(`${serverBase}/app/api/session/${options.path.sessionID}/retry`, {
  42. method: "POST",
  43. headers: { "Content-Type": "application/json" },
  44. })
  45. if (!response.ok) {
  46. return { error: { message: "Failed to retry session" }, data: null }
  47. }
  48. const data = await response.json()
  49. return { data, error: null }
  50. } catch (error) {
  51. return {
  52. error: { message: error instanceof Error ? error.message : "Unknown error" },
  53. data: null,
  54. }
  55. }
  56. },
  57. }) as (typeof baseClient.session) & {
  58. retry: (options: { path: { sessionID: string } }) => Promise<any>
  59. },
  60. config: {
  61. get: baseClient.config.get.bind(baseClient.config),
  62. update: baseClient.config.update.bind(baseClient.config),
  63. providers: baseClient.config.providers.bind(baseClient.config),
  64. allProviders: async () => {
  65. try {
  66. const response = await fetch(`${serverBase}/app/api/config/providers`, {
  67. method: "GET",
  68. headers: { "Content-Type": "application/json" },
  69. })
  70. if (!response.ok) {
  71. return { error: { message: "Failed to load providers" }, data: null as ProvidersResponse | null }
  72. }
  73. const data = (await response.json()) as ProvidersResponse
  74. return { data, error: null as { message: string } | null }
  75. } catch (error) {
  76. return {
  77. error: { message: error instanceof Error ? error.message : "Unknown error" },
  78. data: null as ProvidersResponse | null,
  79. }
  80. }
  81. },
  82. },
  83. path: {
  84. get: async () => {
  85. try {
  86. const response = await fetch(`${serverBase}/path`, {
  87. method: "GET",
  88. headers: { "Content-Type": "application/json" },
  89. })
  90. if (!response.ok) {
  91. return {
  92. error: { message: "Failed to fetch path" },
  93. data: null as PathResponse | null,
  94. }
  95. }
  96. const data = (await response.json()) as PathResponse
  97. return { data, error: null as { message: string } | null }
  98. } catch (error) {
  99. return {
  100. error: { message: error instanceof Error ? error.message : "Unknown error" },
  101. data: null as PathResponse | null,
  102. }
  103. }
  104. },
  105. },
  106. auth: {
  107. set: async (provider: string, value: any) => {
  108. const res = await fetch(`${serverBase}/app/api/auth/set`, {
  109. method: "POST",
  110. headers: { "Content-Type": "application/json" },
  111. body: JSON.stringify({ provider, value }),
  112. })
  113. if (!res.ok) throw new Error(await res.text())
  114. },
  115. list: async () => {
  116. const res = await fetch(`${serverBase}/app/api/auth/list`)
  117. return res.json() as Promise<Record<string, any>>
  118. },
  119. remove: async (provider: string) => {
  120. await fetch(`${serverBase}/app/api/auth/remove`, {
  121. method: "POST",
  122. headers: { "Content-Type": "application/json" },
  123. body: JSON.stringify({ provider }),
  124. })
  125. },
  126. methods: async (provider: string) => {
  127. const res = await fetch(`${serverBase}/app/api/auth/methods?provider=${provider}`)
  128. return res.json() as Promise<
  129. Array<{
  130. label: string
  131. type: "oauth" | "api"
  132. prompts?: any[]
  133. }>
  134. >
  135. },
  136. start: async (provider: string, methodIndex: number, inputs: any) => {
  137. const res = await fetch(`${serverBase}/app/api/auth/login/start`, {
  138. method: "POST",
  139. headers: { "Content-Type": "application/json" },
  140. body: JSON.stringify({ provider, methodIndex, inputs }),
  141. })
  142. if (!res.ok) throw new Error(await res.text())
  143. return res.json() as Promise<{ id: string; url?: string; method: "auto" | "code"; instructions?: string }>
  144. },
  145. submit: async (id: string, code: string) => {
  146. const res = await fetch(`${serverBase}/app/api/auth/login/submit`, {
  147. method: "POST",
  148. headers: { "Content-Type": "application/json" },
  149. body: JSON.stringify({ id, code }),
  150. })
  151. if (!res.ok) throw new Error(await res.text())
  152. return res.json() as Promise<boolean>
  153. },
  154. status: async (id: string) => {
  155. const res = await fetch(`${serverBase}/app/api/auth/login/status/${id}`)
  156. return res.json() as Promise<{ status: "pending" | "success" | "failed"; result?: any }>
  157. },
  158. },
  159. permissions: {
  160. respond: async (options: {
  161. path: { requestID: string }
  162. body: { reply: "once" | "always" | "reject"; message?: string }
  163. }) => {
  164. const response = await fetch(`${serverBase}/permission/${options.path.requestID}/reply`, {
  165. method: "POST",
  166. headers: { "Content-Type": "application/json" },
  167. body: JSON.stringify(options.body),
  168. })
  169. if (!response.ok) {
  170. return { error: { message: "Failed to respond to permission" }, data: null }
  171. }
  172. const data = await response.json()
  173. return { data, error: null }
  174. },
  175. },
  176. question: {
  177. reply: async (options: { requestID: string; answers: Array<Array<string>> }) => {
  178. const response = await fetch(`${serverBase}/question/${options.requestID}/reply`, {
  179. method: "POST",
  180. headers: { "Content-Type": "application/json" },
  181. body: JSON.stringify({ answers: options.answers }),
  182. })
  183. if (!response.ok) {
  184. return { error: { message: "Failed to reply to question" }, data: null }
  185. }
  186. const data = await response.json()
  187. return { data, error: null }
  188. },
  189. reject: async (options: { requestID: string }) => {
  190. const response = await fetch(`${serverBase}/question/${options.requestID}/reject`, {
  191. method: "POST",
  192. headers: { "Content-Type": "application/json" },
  193. })
  194. if (!response.ok) {
  195. return { error: { message: "Failed to reject question" }, data: null }
  196. }
  197. const data = await response.json()
  198. return { data, error: null }
  199. },
  200. },
  201. model: {
  202. get: async () => {
  203. try {
  204. const response = await fetch(`${serverBase}/app/api/model`, {
  205. method: "GET",
  206. headers: { "Content-Type": "application/json" },
  207. })
  208. if (!response.ok) {
  209. return { error: { message: "Failed to fetch model preferences" }, data: null as ModelPreferences | null }
  210. }
  211. const data = (await response.json()) as ModelPreferences
  212. return { data, error: null as { message: string } | null }
  213. } catch (error) {
  214. return { error: { message: error instanceof Error ? error.message : "Unknown error" }, data: null as ModelPreferences | null }
  215. }
  216. },
  217. update: async (options: { body: Partial<ModelPreferences> }) => {
  218. try {
  219. const response = await fetch(`${serverBase}/app/api/model`, {
  220. method: "PATCH",
  221. headers: { "Content-Type": "application/json" },
  222. body: JSON.stringify(options.body),
  223. })
  224. if (!response.ok) {
  225. return { error: { message: "Failed to update model preferences" }, data: null as ModelPreferences | null }
  226. }
  227. const data = (await response.json()) as ModelPreferences
  228. return { data, error: null as { message: string } | null }
  229. } catch (error) {
  230. return { error: { message: error instanceof Error ? error.message : "Unknown error" }, data: null as ModelPreferences | null }
  231. }
  232. },
  233. },
  234. kv: {
  235. get: async () => {
  236. try {
  237. const response = await fetch(`${serverBase}/app/api/kv`, {
  238. method: "GET",
  239. headers: { "Content-Type": "application/json" },
  240. })
  241. if (!response.ok) {
  242. return { error: { message: "Failed to fetch kv" }, data: null as Record<string, any> | null }
  243. }
  244. const data = (await response.json()) as Record<string, any>
  245. return { data, error: null as { message: string } | null }
  246. } catch (error) {
  247. return { error: { message: error instanceof Error ? error.message : "Unknown error" }, data: null as Record<string, any> | null }
  248. }
  249. },
  250. update: async (options: { body: Record<string, any> }) => {
  251. try {
  252. const response = await fetch(`${serverBase}/app/api/kv`, {
  253. method: "PATCH",
  254. headers: { "Content-Type": "application/json" },
  255. body: JSON.stringify(options.body),
  256. })
  257. if (!response.ok) {
  258. return { error: { message: "Failed to update kv" }, data: null as Record<string, any> | null }
  259. }
  260. const data = (await response.json()) as Record<string, any>
  261. return { data, error: null as { message: string } | null }
  262. } catch (error) {
  263. return { error: { message: error instanceof Error ? error.message : "Unknown error" }, data: null as Record<string, any> | null }
  264. }
  265. },
  266. },
  267. }