tui-sdk.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client/promise"
  2. import { createOpencodeClient } from "@opencode-ai/sdk/v2"
  3. export const worktree = "/tmp/opencode"
  4. export const directory = `${worktree}/packages/tui`
  5. export function json(data: unknown, init?: ResponseInit) {
  6. const headers = new Headers(init?.headers)
  7. if (!headers.has("content-type")) headers.set("content-type", "application/json")
  8. return new Response(JSON.stringify(data), {
  9. ...init,
  10. headers,
  11. })
  12. }
  13. export function createEventStream() {
  14. const encoder = new TextEncoder()
  15. const v2 = new Set<ReadableStreamDefaultController<Uint8Array>>()
  16. const pending: Uint8Array[] = []
  17. const response = (
  18. controllers: Set<ReadableStreamDefaultController<Uint8Array>>,
  19. queued: Uint8Array[],
  20. initial?: unknown,
  21. ) => {
  22. let current: ReadableStreamDefaultController<Uint8Array> | undefined
  23. return new Response(
  24. new ReadableStream<Uint8Array>({
  25. start(controller) {
  26. current = controller
  27. controllers.add(controller)
  28. if (initial) controller.enqueue(encoder.encode(`data: ${JSON.stringify(initial)}\n\n`))
  29. for (const chunk of queued.splice(0)) controller.enqueue(chunk)
  30. },
  31. cancel() {
  32. if (current) controllers.delete(current)
  33. },
  34. }),
  35. { headers: { "content-type": "text/event-stream" } },
  36. )
  37. }
  38. const send = (
  39. controllers: Set<ReadableStreamDefaultController<Uint8Array>>,
  40. queued: Uint8Array[],
  41. event: unknown,
  42. ) => {
  43. const chunk = encoder.encode(`data: ${JSON.stringify(event)}\n\n`)
  44. if (controllers.size === 0) {
  45. queued.push(chunk)
  46. return
  47. }
  48. for (const controller of controllers) controller.enqueue(chunk)
  49. }
  50. return {
  51. emit(event: OpenCodeEvent) {
  52. send(v2, pending, event)
  53. },
  54. v2() {
  55. return response(v2, pending, { id: "evt_connected", type: "server.connected", data: {} })
  56. },
  57. disconnect() {
  58. for (const controller of v2) controller.close()
  59. v2.clear()
  60. },
  61. }
  62. }
  63. export type FetchHandler = (url: URL, request: Request) => Response | undefined | Promise<Response | undefined>
  64. export function createFetch(override?: FetchHandler, events?: ReturnType<typeof createEventStream>) {
  65. const session = [] as URL[]
  66. async function fetch(input: RequestInfo | URL, init?: RequestInit) {
  67. const request = input instanceof Request ? input : new Request(input, init)
  68. const url = new URL(request.url)
  69. if (url.pathname === "/session") session.push(url)
  70. const overridden = await override?.(url, request)
  71. if (overridden) return overridden
  72. if (url.pathname === "/api/event" && events) return events.v2()
  73. if (
  74. [
  75. "/agent",
  76. "/command",
  77. "/experimental/workspace",
  78. "/experimental/workspace/status",
  79. "/formatter",
  80. "/lsp",
  81. ].includes(url.pathname)
  82. )
  83. return json([])
  84. if (["/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(url.pathname))
  85. return json({})
  86. if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
  87. if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
  88. if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: true })
  89. if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
  90. if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
  91. if (url.pathname === "/api/fs/list")
  92. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  93. if (url.pathname === "/api/project/current") return json({ id: "proj_test", directory: worktree })
  94. if (url.pathname === "/api/project/proj_test/directories") return json([{ directory: worktree }])
  95. if (url.pathname === "/api/shell")
  96. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  97. if (url.pathname === "/api/mcp")
  98. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  99. if (url.pathname === "/api/session") return json({ data: [], cursor: {} })
  100. if (url.pathname === "/api/session/active") return json({ data: {} })
  101. if (url.pathname === "/api/permission/request")
  102. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  103. if (url.pathname === "/api/form/request")
  104. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  105. if (/^\/api\/session\/[^/]+\/form$/.test(url.pathname)) return json({ data: [] })
  106. if (
  107. ["/api/agent", "/api/model", "/api/provider", "/api/integration", "/api/command", "/api/skill"].includes(
  108. url.pathname,
  109. )
  110. )
  111. return json({
  112. location: { directory, project: { id: "proj_test", directory: worktree } },
  113. data: [],
  114. })
  115. if (url.pathname === "/api/reference")
  116. return json({ location: { directory, project: { id: "proj_test", directory } }, data: [] })
  117. if (url.pathname === "/provider") return json({ all: [], default: {}, connected: [] })
  118. if (url.pathname === "/session") return json([])
  119. if (url.pathname === "/vcs") return json({ branch: "main" })
  120. throw new Error(`unexpected request: ${url.pathname}`)
  121. }
  122. fetch.preconnect = () => {}
  123. return { fetch, session }
  124. }
  125. export function createClient(fetch: typeof globalThis.fetch) {
  126. return createOpencodeClient({ baseUrl: "http://test", fetch })
  127. }
  128. export function createApi(fetch: typeof globalThis.fetch) {
  129. return OpenCode.make({ baseUrl: "http://test", fetch })
  130. }