soraacode 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/LICENSE +21 -0
  2. package/bin/soraacode.exe +10 -0
  3. package/package.json +17 -25
  4. package/{script/postinstall.mjs → postinstall.mjs} +5 -5
  5. package/AGENTS.md +0 -131
  6. package/Dockerfile +0 -18
  7. package/README.md +0 -15
  8. package/bin/opencode +0 -199
  9. package/bin/soraacode +0 -199
  10. package/bunfig.toml +0 -7
  11. package/git +0 -0
  12. package/migration/20260511173437_session-metadata/migration.sql +0 -1
  13. package/migration/20260511173437_session-metadata/snapshot.json +0 -1500
  14. package/parsers-config.ts +0 -1
  15. package/script/bench-search.ts +0 -94
  16. package/script/bench-test-suite.ts +0 -52
  17. package/script/build-headless.ts +0 -51
  18. package/script/build.ts +0 -245
  19. package/script/generate.ts +0 -14
  20. package/script/httpapi-exercise.ts +0 -1
  21. package/script/profile-test-files.ts +0 -42
  22. package/script/publish.ts +0 -213
  23. package/script/run-workspace-server +0 -106
  24. package/script/schema.ts +0 -77
  25. package/script/time.ts +0 -6
  26. package/script/trace-imports.ts +0 -153
  27. package/soraacode-0.1.7.tgz +0 -0
  28. package/specs/effect/error-boundaries-plan.md +0 -235
  29. package/specs/effect/errors.md +0 -207
  30. package/specs/effect/facades.md +0 -218
  31. package/specs/effect/guide.md +0 -247
  32. package/specs/effect/instance-context.md +0 -13
  33. package/specs/effect/loose-ends.md +0 -30
  34. package/specs/effect/migration.md +0 -62
  35. package/specs/effect/routes.md +0 -61
  36. package/specs/effect/schema.md +0 -88
  37. package/specs/effect/server-package.md +0 -58
  38. package/specs/effect/todo.md +0 -241
  39. package/specs/effect/tools.md +0 -88
  40. package/specs/openapi-translation-cleanup.md +0 -204
  41. package/specs/tui-plugins.md +0 -544
  42. package/specs/v2/api.ts +0 -67
  43. package/specs/v2/message-shape.md +0 -136
  44. package/specs/v2/notifications.md +0 -13
  45. package/specs/v2/tui-command-shim.md +0 -67
  46. package/src/account/account.ts +0 -481
  47. package/src/account/repo.ts +0 -171
  48. package/src/account/schema.ts +0 -99
  49. package/src/account/url.ts +0 -8
  50. package/src/acp/agent.ts +0 -95
  51. package/src/acp/config-option.ts +0 -206
  52. package/src/acp/content.ts +0 -269
  53. package/src/acp/directory.ts +0 -212
  54. package/src/acp/error.ts +0 -97
  55. package/src/acp/event.ts +0 -421
  56. package/src/acp/permission.ts +0 -254
  57. package/src/acp/profile.ts +0 -42
  58. package/src/acp/service.ts +0 -1226
  59. package/src/acp/session.ts +0 -232
  60. package/src/acp/tool.ts +0 -364
  61. package/src/acp/usage.ts +0 -243
  62. package/src/agent/agent.ts +0 -450
  63. package/src/agent/generate.txt +0 -75
  64. package/src/agent/prompt/compaction.txt +0 -5
  65. package/src/agent/prompt/explore.txt +0 -18
  66. package/src/agent/prompt/summary.txt +0 -11
  67. package/src/agent/prompt/title.txt +0 -44
  68. package/src/agent/subagent-permissions.ts +0 -27
  69. package/src/audio.d.ts +0 -14
  70. package/src/auth/index.ts +0 -97
  71. package/src/background/job.ts +0 -37
  72. package/src/bus/global.ts +0 -22
  73. package/src/cli/bootstrap.ts +0 -11
  74. package/src/cli/cmd/cmd.ts +0 -7
  75. package/src/cli/cmd/serve.ts +0 -24
  76. package/src/cli/effect/prompt.ts +0 -37
  77. package/src/cli/effect-cmd.ts +0 -96
  78. package/src/cli/error.ts +0 -130
  79. package/src/cli/heap.ts +0 -45
  80. package/src/cli/logo.ts +0 -2
  81. package/src/cli/network.ts +0 -80
  82. package/src/cli/ui.ts +0 -132
  83. package/src/command/index.ts +0 -177
  84. package/src/command/template/initialize.txt +0 -66
  85. package/src/command/template/review.txt +0 -101
  86. package/src/config/agent.ts +0 -59
  87. package/src/config/command.ts +0 -39
  88. package/src/config/config.ts +0 -707
  89. package/src/config/entry-name.ts +0 -19
  90. package/src/config/managed.ts +0 -69
  91. package/src/config/markdown.ts +0 -36
  92. package/src/config/parse.ts +0 -61
  93. package/src/config/paths.ts +0 -45
  94. package/src/config/plugin.ts +0 -79
  95. package/src/config/v2-compat.ts +0 -449
  96. package/src/config/variable.ts +0 -91
  97. package/src/control-plane/adapters/index.ts +0 -41
  98. package/src/control-plane/adapters/worktree.ts +0 -96
  99. package/src/control-plane/dev/README.md +0 -19
  100. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  101. package/src/control-plane/types.ts +0 -59
  102. package/src/control-plane/util.ts +0 -39
  103. package/src/control-plane/workspace-adapter-runtime.ts +0 -51
  104. package/src/control-plane/workspace-context.ts +0 -26
  105. package/src/control-plane/workspace.ts +0 -966
  106. package/src/effect/app-node-builder-v1.ts +0 -12
  107. package/src/effect/app-runtime.ts +0 -135
  108. package/src/effect/bootstrap-runtime.ts +0 -19
  109. package/src/effect/bridge.ts +0 -84
  110. package/src/effect/config-service.ts +0 -67
  111. package/src/effect/instance-ref.ts +0 -11
  112. package/src/effect/instance-registry.ts +0 -12
  113. package/src/effect/instance-state.ts +0 -69
  114. package/src/effect/promise.ts +0 -17
  115. package/src/effect/run-service.ts +0 -47
  116. package/src/effect/runner.ts +0 -217
  117. package/src/effect/runtime-flags.ts +0 -78
  118. package/src/env/index.ts +0 -41
  119. package/src/event-manifest.ts +0 -3
  120. package/src/event-v2-bridge.ts +0 -71
  121. package/src/format/formatter.ts +0 -404
  122. package/src/format/index.ts +0 -203
  123. package/src/git/index.ts +0 -348
  124. package/src/id/id.ts +0 -80
  125. package/src/ide/index.ts +0 -54
  126. package/src/image/image.ts +0 -172
  127. package/src/index-headless.ts +0 -89
  128. package/src/index.ts +0 -93
  129. package/src/installation/index.ts +0 -336
  130. package/src/lsp/client.ts +0 -650
  131. package/src/lsp/diagnostic.ts +0 -29
  132. package/src/lsp/language.ts +0 -121
  133. package/src/lsp/launch.ts +0 -21
  134. package/src/lsp/lsp.ts +0 -507
  135. package/src/lsp/server.ts +0 -1983
  136. package/src/markdown.d.ts +0 -4
  137. package/src/mcp/auth.ts +0 -163
  138. package/src/mcp/browser.ts +0 -37
  139. package/src/mcp/catalog.ts +0 -170
  140. package/src/mcp/index.ts +0 -1004
  141. package/src/mcp/oauth-callback.ts +0 -194
  142. package/src/mcp/oauth-provider.ts +0 -259
  143. package/src/node.ts +0 -4
  144. package/src/patch/index.ts +0 -686
  145. package/src/permission/arity.ts +0 -163
  146. package/src/permission/evaluate.ts +0 -1
  147. package/src/permission/index.ts +0 -223
  148. package/src/plugin/github-copilot/copilot.ts +0 -415
  149. package/src/plugin/github-copilot/models.ts +0 -261
  150. package/src/plugin/index.ts +0 -298
  151. package/src/plugin/install.ts +0 -439
  152. package/src/plugin/loader.ts +0 -237
  153. package/src/plugin/meta.ts +0 -188
  154. package/src/plugin/openai/README.md +0 -31
  155. package/src/plugin/openai/codex.ts +0 -575
  156. package/src/plugin/openai/ws-pool.ts +0 -271
  157. package/src/plugin/openai/ws.ts +0 -383
  158. package/src/plugin/pty-environment.ts +0 -24
  159. package/src/plugin/shared.ts +0 -323
  160. package/src/project/bootstrap-service.ts +0 -9
  161. package/src/project/bootstrap.ts +0 -58
  162. package/src/project/instance-context.ts +0 -24
  163. package/src/project/instance-runtime.ts +0 -16
  164. package/src/project/instance-store.ts +0 -213
  165. package/src/project/project.ts +0 -483
  166. package/src/project/vcs.ts +0 -423
  167. package/src/provider/auth.ts +0 -229
  168. package/src/provider/error.ts +0 -195
  169. package/src/provider/model-status.ts +0 -8
  170. package/src/provider/provider.ts +0 -1955
  171. package/src/provider/transform.ts +0 -1923
  172. package/src/question/index.ts +0 -161
  173. package/src/question/schema.ts +0 -4
  174. package/src/server/auth.ts +0 -48
  175. package/src/server/event.ts +0 -10
  176. package/src/server/global-lifecycle.ts +0 -28
  177. package/src/server/mdns.ts +0 -47
  178. package/src/server/proxy-util.ts +0 -48
  179. package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
  180. package/src/server/routes/instance/httpapi/api.ts +0 -97
  181. package/src/server/routes/instance/httpapi/errors.ts +0 -193
  182. package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
  183. package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
  184. package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
  185. package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
  186. package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
  187. package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
  188. package/src/server/routes/instance/httpapi/groups/global.ts +0 -139
  189. package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
  190. package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
  191. package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
  192. package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
  193. package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
  194. package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
  195. package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
  196. package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
  197. package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
  198. package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
  199. package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
  200. package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
  201. package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
  202. package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
  203. package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
  204. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
  205. package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
  206. package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
  207. package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -193
  208. package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
  209. package/src/server/routes/instance/httpapi/handlers/global.ts +0 -126
  210. package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
  211. package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
  212. package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
  213. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
  214. package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
  215. package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -116
  216. package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
  217. package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
  218. package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
  219. package/src/server/routes/instance/httpapi/handlers/session.ts +0 -442
  220. package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
  221. package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
  222. package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
  223. package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
  224. package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
  225. package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
  226. package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
  227. package/src/server/routes/instance/httpapi/middleware/error.ts +0 -44
  228. package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
  229. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
  230. package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -131
  231. package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
  232. package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
  233. package/src/server/routes/instance/httpapi/public.ts +0 -537
  234. package/src/server/routes/instance/httpapi/server.ts +0 -325
  235. package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -60
  236. package/src/server/server.ts +0 -224
  237. package/src/server/shared/fence.ts +0 -60
  238. package/src/server/shared/pty-ticket.ts +0 -15
  239. package/src/server/shared/public-ui.ts +0 -12
  240. package/src/server/shared/tui-control.ts +0 -28
  241. package/src/server/shared/ui.ts +0 -108
  242. package/src/server/shared/workspace-routing.ts +0 -45
  243. package/src/server/tui-event.ts +0 -1
  244. package/src/session/compaction.ts +0 -608
  245. package/src/session/instruction.ts +0 -237
  246. package/src/session/llm/AGENTS.md +0 -90
  247. package/src/session/llm/ai-sdk.ts +0 -291
  248. package/src/session/llm/native-request.ts +0 -196
  249. package/src/session/llm/native-runtime.ts +0 -195
  250. package/src/session/llm/request.ts +0 -226
  251. package/src/session/llm.ts +0 -390
  252. package/src/session/message-error.ts +0 -14
  253. package/src/session/message-v2.ts +0 -742
  254. package/src/session/message.ts +0 -148
  255. package/src/session/overflow.ts +0 -34
  256. package/src/session/processor.ts +0 -732
  257. package/src/session/prompt/build-switch.txt +0 -5
  258. package/src/session/prompt/default.txt +0 -35
  259. package/src/session/prompt/plan-mode.txt +0 -70
  260. package/src/session/prompt/plan.txt +0 -26
  261. package/src/session/prompt.ts +0 -1631
  262. package/src/session/reminders.ts +0 -92
  263. package/src/session/retry.ts +0 -209
  264. package/src/session/revert.ts +0 -136
  265. package/src/session/run-state.ts +0 -151
  266. package/src/session/schema.ts +0 -26
  267. package/src/session/session.ts +0 -1016
  268. package/src/session/status.ts +0 -56
  269. package/src/session/summary.ts +0 -160
  270. package/src/session/system.ts +0 -123
  271. package/src/session/todo.ts +0 -74
  272. package/src/session/tools.ts +0 -590
  273. package/src/share/session.ts +0 -58
  274. package/src/share/share-next.ts +0 -371
  275. package/src/skill/discovery.ts +0 -140
  276. package/src/skill/index.ts +0 -354
  277. package/src/snapshot/index.ts +0 -807
  278. package/src/sql.d.ts +0 -4
  279. package/src/storage/schema.ts +0 -5
  280. package/src/storage/storage.ts +0 -327
  281. package/src/sync/README.md +0 -179
  282. package/src/sync/schema.ts +0 -11
  283. package/src/temporary.ts +0 -31
  284. package/src/tool/apply_patch.ts +0 -313
  285. package/src/tool/apply_patch.txt +0 -14
  286. package/src/tool/code-mode.ts +0 -310
  287. package/src/tool/edit.ts +0 -737
  288. package/src/tool/edit.txt +0 -13
  289. package/src/tool/external-directory.ts +0 -49
  290. package/src/tool/glob.ts +0 -76
  291. package/src/tool/glob.txt +0 -7
  292. package/src/tool/grep.ts +0 -115
  293. package/src/tool/grep.txt +0 -7
  294. package/src/tool/invalid.ts +0 -21
  295. package/src/tool/json-schema.ts +0 -164
  296. package/src/tool/lsp.ts +0 -113
  297. package/src/tool/lsp.txt +0 -1
  298. package/src/tool/mcp-websearch.ts +0 -96
  299. package/src/tool/plan-enter.txt +0 -1
  300. package/src/tool/plan-exit.txt +0 -1
  301. package/src/tool/plan.ts +0 -79
  302. package/src/tool/question.ts +0 -44
  303. package/src/tool/question.txt +0 -1
  304. package/src/tool/read.ts +0 -386
  305. package/src/tool/read.txt +0 -13
  306. package/src/tool/registry.ts +0 -455
  307. package/src/tool/schema.ts +0 -14
  308. package/src/tool/shell/id.ts +0 -19
  309. package/src/tool/shell/prompt.ts +0 -293
  310. package/src/tool/shell/shell.txt +0 -21
  311. package/src/tool/shell.ts +0 -645
  312. package/src/tool/skill.ts +0 -70
  313. package/src/tool/skill.txt +0 -1
  314. package/src/tool/task.ts +0 -371
  315. package/src/tool/task.txt +0 -11
  316. package/src/tool/todo.ts +0 -46
  317. package/src/tool/todowrite.txt +0 -11
  318. package/src/tool/tool.ts +0 -183
  319. package/src/tool/truncate.ts +0 -156
  320. package/src/tool/truncation-dir.ts +0 -4
  321. package/src/tool/webfetch.ts +0 -192
  322. package/src/tool/webfetch.txt +0 -9
  323. package/src/tool/websearch.ts +0 -143
  324. package/src/tool/websearch.txt +0 -9
  325. package/src/tool/write.ts +0 -104
  326. package/src/tool/write.txt +0 -11
  327. package/src/util/archive.ts +0 -17
  328. package/src/util/bom.ts +0 -27
  329. package/src/util/data-url.ts +0 -9
  330. package/src/util/defer.ts +0 -10
  331. package/src/util/effect-http-client.ts +0 -11
  332. package/src/util/error.ts +0 -16
  333. package/src/util/filesystem.ts +0 -251
  334. package/src/util/html.ts +0 -8
  335. package/src/util/iife.ts +0 -3
  336. package/src/util/lazy.ts +0 -20
  337. package/src/util/local-context.ts +0 -25
  338. package/src/util/locale.ts +0 -4
  339. package/src/util/media.ts +0 -26
  340. package/src/util/process.ts +0 -177
  341. package/src/util/proxy-env.ts +0 -72
  342. package/src/util/queue.ts +0 -32
  343. package/src/util/record.ts +0 -3
  344. package/src/util/repository.ts +0 -232
  345. package/src/util/rpc.ts +0 -66
  346. package/src/util/signal.ts +0 -12
  347. package/src/util/timeout.ts +0 -13
  348. package/src/util/token.ts +0 -1
  349. package/src/util/wildcard.ts +0 -59
  350. package/src/worktree/index.ts +0 -623
  351. package/sst-env.d.ts +0 -10
  352. package/tsconfig.headless.json +0 -13
  353. package/tsconfig.json +0 -16
@@ -1,271 +0,0 @@
1
- import WebSocket from "ws"
2
- import { ProviderError } from "@/provider/error"
3
- import { isRecord } from "@/util/record"
4
- import { OpenAIWebSocket } from "./ws"
5
-
6
- export const TITLE_HEADER = "x-opencode-title"
7
-
8
- export interface CreateWebSocketFetchOptions {
9
- httpFetch?: typeof globalThis.fetch
10
- url?: string
11
- connectTimeout?: number
12
- idleTimeout?: number
13
- maxConnectionAge?: number
14
- streamRetries?: number
15
- }
16
-
17
- interface PoolEntry {
18
- socket?: WebSocket
19
- connectedAt?: number
20
- lastUsedAt: number
21
- busy: boolean
22
- fallback: boolean
23
- streamFailures: number
24
- }
25
-
26
- const DEFAULT_CONNECT_TIMEOUT = 15_000
27
- const DEFAULT_IDLE_TIMEOUT = 5 * 60 * 1000
28
- const DEFAULT_MAX_CONNECTION_AGE = 55 * 60 * 1000
29
- const CONNECTION_LIMIT_REACHED_CODE = "websocket_connection_limit_reached"
30
-
31
- export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
32
- const httpFetch = options?.httpFetch ?? globalThis.fetch
33
- const pool = new Map<string, PoolEntry>()
34
- const connectTimeout = options?.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT
35
- const idleTimeout = options?.idleTimeout ?? DEFAULT_IDLE_TIMEOUT
36
- const maxConnectionAge = options?.maxConnectionAge ?? DEFAULT_MAX_CONNECTION_AGE
37
- const streamRetries = options?.streamRetries ?? 5
38
- const pruneTimer = setInterval(() => prune(), Math.min(idleTimeout, 60_000))
39
- if (typeof pruneTimer === "object" && "unref" in pruneTimer && typeof pruneTimer.unref === "function") {
40
- pruneTimer.unref()
41
- }
42
-
43
- async function websocketFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
44
- const url = input instanceof URL ? input.toString() : typeof input === "string" ? input : input.url
45
- const internalHeaders = OpenAIWebSocket.normalizeHeaders(init?.headers)
46
- const httpInit = withoutInternalHeaders(init)
47
-
48
- if (init?.method !== "POST" || !new URL(url).pathname.endsWith("/responses")) {
49
- return httpFetch(input, httpInit)
50
- }
51
-
52
- const body = (() => {
53
- try {
54
- if (typeof init?.body !== "string") return undefined
55
- const parsed = JSON.parse(init.body)
56
- return typeof parsed === "object" && parsed !== null ? parsed : undefined
57
- } catch {
58
- return undefined
59
- }
60
- })()
61
- if (!body?.stream) return httpFetch(input, httpInit)
62
- if (internalHeaders[TITLE_HEADER] === "true") {
63
- return httpFetch(input, httpInit)
64
- }
65
-
66
- const sessionID = internalHeaders["x-session-affinity"] ?? internalHeaders["session-id"]
67
- if (!sessionID) {
68
- return httpFetch(input, httpInit)
69
- }
70
- const key = `${sessionID}:conversation`
71
-
72
- const entry = pool.get(key) ?? { lastUsedAt: Date.now(), busy: false, fallback: false, streamFailures: 0 }
73
- pool.set(key, entry)
74
-
75
- if (entry.fallback) {
76
- return httpFetch(input, httpInit)
77
- }
78
- if (entry.busy) {
79
- return httpFetch(input, httpInit)
80
- }
81
-
82
- entry.busy = true
83
- entry.lastUsedAt = Date.now()
84
- try {
85
- entry.socket = await socket(
86
- entry,
87
- options?.url ?? url,
88
- OpenAIWebSocket.normalizeHeaders(httpInit?.headers),
89
- connectTimeout,
90
- maxConnectionAge,
91
- init?.signal,
92
- )
93
- let resolveFirstEvent: (event: boolean | OpenAIWebSocket.WrappedError) => void = () => {}
94
- let rejectFirstEvent: (error: Error) => void = () => {}
95
- const firstEvent = new Promise<boolean | OpenAIWebSocket.WrappedError>((resolve, reject) => {
96
- resolveFirstEvent = resolve
97
- rejectFirstEvent = reject
98
- })
99
- const response = OpenAIWebSocket.streamResponsesWebSocket({
100
- socket: entry.socket,
101
- body,
102
- idleTimeout,
103
- signal: init?.signal ?? undefined,
104
- onFirstEvent: (error) => resolveFirstEvent(error ?? true),
105
- onTerminal: (event) => {
106
- entry.busy = false
107
- entry.lastUsedAt = Date.now()
108
- entry.streamFailures = 0
109
- if (event.type !== "response.completed" && event.type !== "response.done") {
110
- invalidate(entry)
111
- }
112
- },
113
- onConnectionInvalid: (_error, closeCode) => {
114
- entry.busy = false
115
- entry.lastUsedAt = Date.now()
116
- if (closeCode === OpenAIWebSocket.MESSAGE_TOO_BIG_CLOSE_CODE) entry.fallback = true
117
- else if (!entry.fallback) recordStreamFailure(entry)
118
- invalidate(entry)
119
- resolveFirstEvent(false)
120
- },
121
- onAbort: (error) => {
122
- entry.busy = false
123
- entry.lastUsedAt = Date.now()
124
- entry.streamFailures = 0
125
- invalidate(entry)
126
- rejectFirstEvent(error)
127
- },
128
- onRetryableTerminal: async (event) => {
129
- const error = connectionLimitError(event)
130
- if (!error) return undefined
131
- throw error
132
- },
133
- })
134
- const first = await firstEvent
135
- if (first !== false) {
136
- if (first === true || first.status < 200 || first.status > 599) return response
137
- return new Response(first.body, {
138
- status: first.status,
139
- headers: { "content-type": "application/json", ...first.headers },
140
- })
141
- }
142
- if (!entry.fallback) return response
143
- return httpFetch(input, httpInit)
144
- } catch (error) {
145
- entry.busy = false
146
- entry.lastUsedAt = Date.now()
147
- if (OpenAIWebSocket.isAbortError(error)) {
148
- entry.streamFailures = 0
149
- invalidate(entry)
150
- throw error
151
- }
152
-
153
- recordStreamFailure(entry)
154
- invalidate(entry)
155
- if (entry.fallback) return httpFetch(input, httpInit)
156
- return failedResponse(
157
- new ProviderError.ResponseStreamError(error instanceof Error ? error.message : String(error), {
158
- cause: error,
159
- }),
160
- )
161
- }
162
- }
163
-
164
- function recordStreamFailure(entry: PoolEntry) {
165
- entry.streamFailures++
166
- // Codex counts retries after the initial failed WebSocket attempt.
167
- if (entry.streamFailures > streamRetries) entry.fallback = true
168
- }
169
-
170
- function prune() {
171
- const now = Date.now()
172
- for (const [key, entry] of pool) {
173
- if (entry.busy) continue
174
- if (entry.fallback) continue
175
- if (now - entry.lastUsedAt < idleTimeout) continue
176
- invalidate(entry)
177
- pool.delete(key)
178
- }
179
- }
180
-
181
- function close() {
182
- clearInterval(pruneTimer)
183
- for (const entry of pool.values()) invalidate(entry)
184
- pool.clear()
185
- }
186
-
187
- function remove(sessionID: string) {
188
- const key = `${sessionID}:conversation`
189
- const entry = pool.get(key)
190
- if (!entry) return
191
- invalidate(entry)
192
- pool.delete(key)
193
- }
194
-
195
- return Object.assign(websocketFetch, { close, remove })
196
- }
197
-
198
- function connectionLimitError(event: Record<string, unknown>) {
199
- if (event.type !== "error" || !isRecord(event.error) || event.error.code !== CONNECTION_LIMIT_REACHED_CODE) return
200
- return new Error(typeof event.error.message === "string" ? event.error.message : CONNECTION_LIMIT_REACHED_CODE)
201
- }
202
-
203
- function failedResponse(error: ProviderError.ResponseStreamError) {
204
- return new Response(
205
- new ReadableStream({
206
- start(controller) {
207
- controller.error(error)
208
- },
209
- }),
210
- {
211
- status: 200,
212
- headers: { "content-type": "text/event-stream" },
213
- },
214
- )
215
- }
216
-
217
- async function socket(
218
- entry: PoolEntry,
219
- url: string,
220
- headers: Record<string, string>,
221
- connectTimeout: number,
222
- maxConnectionAge: number,
223
- signal?: AbortSignal | null,
224
- ) {
225
- if (
226
- entry.socket?.readyState === WebSocket.OPEN &&
227
- entry.connectedAt &&
228
- Date.now() - entry.connectedAt < maxConnectionAge
229
- ) {
230
- return entry.socket
231
- }
232
-
233
- invalidate(entry)
234
- const next = await OpenAIWebSocket.connectResponsesWebSocket({
235
- url: OpenAIWebSocket.toWebSocketUrl(url),
236
- headers,
237
- timeout: connectTimeout,
238
- signal: signal ?? undefined,
239
- })
240
- entry.connectedAt = Date.now()
241
- return next
242
- }
243
-
244
- function invalidate(entry: PoolEntry) {
245
- if (entry.socket) {
246
- entry.socket.on("error", () => {})
247
- entry.socket.terminate()
248
- entry.socket = undefined
249
- }
250
- entry.connectedAt = undefined
251
- }
252
-
253
- export function withoutInternalHeaders<T extends { headers?: HeadersInit }>(init: T | undefined): T | undefined {
254
- if (!init?.headers) return init
255
- if (init.headers instanceof Headers) {
256
- const headers = new Headers(init.headers)
257
- headers.delete(TITLE_HEADER)
258
- return { ...init, headers }
259
- }
260
-
261
- if (Array.isArray(init.headers)) {
262
- return { ...init, headers: init.headers.filter((item) => item[0].toLowerCase() !== TITLE_HEADER) }
263
- }
264
-
265
- return {
266
- ...init,
267
- headers: Object.fromEntries(Object.entries(init.headers).filter(([key]) => key.toLowerCase() !== TITLE_HEADER)),
268
- }
269
- }
270
-
271
- export * as OpenAIWebSocketPool from "./ws-pool"
@@ -1,383 +0,0 @@
1
- // Low-level OpenAI Responses WebSocket protocol helpers. Session pooling,
2
- // fallback, and continuation state intentionally live above this file.
3
-
4
- import WebSocket from "ws"
5
- import { APICallError } from "ai"
6
- import { ProviderError } from "@/provider/error"
7
- import { errorMessage } from "@/util/error"
8
- import { ProxyEnv } from "@/util/proxy-env"
9
- import { isRecord } from "@/util/record"
10
-
11
- export const PROTOCOL_HEADER = "responses_websockets=2026-02-06"
12
- export const MESSAGE_TOO_BIG_CLOSE_CODE = 1009
13
-
14
- export interface ConnectResponsesWebSocketOptions {
15
- url: string
16
- headers: Record<string, string>
17
- timeout?: number
18
- signal?: AbortSignal
19
- }
20
-
21
- export interface StreamResponsesWebSocketOptions {
22
- socket: WebSocket
23
- body: Record<string, unknown>
24
- idleTimeout?: number
25
- signal?: AbortSignal
26
- onFirstEvent?: (error?: WrappedError) => void
27
- onComplete?: (event: Record<string, unknown>) => void
28
- onTerminal?: (event: Record<string, unknown>) => void
29
- onRetryableTerminal?: (event: Record<string, unknown>) => Promise<WebSocket | undefined>
30
- onConnectionInvalid?: (error: ProviderError.ResponseStreamError, closeCode?: number) => void
31
- onAbort?: (error: Error) => void
32
- }
33
-
34
- export interface WrappedError {
35
- status: number
36
- headers?: Record<string, string>
37
- body: string
38
- }
39
-
40
- export function toWebSocketUrl(url: string) {
41
- return url.replace(/^http/, "ws")
42
- }
43
-
44
- export function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> {
45
- const result: Record<string, string> = {}
46
- if (!headers) return result
47
-
48
- if (headers instanceof Headers) {
49
- headers.forEach((value, key) => {
50
- result[key.toLowerCase()] = value
51
- })
52
- return result
53
- }
54
-
55
- if (Array.isArray(headers)) {
56
- for (const [key, value] of headers) {
57
- result[key.toLowerCase()] = value
58
- }
59
- return result
60
- }
61
-
62
- for (const [key, value] of Object.entries(headers)) {
63
- if (value != null) result[key.toLowerCase()] = value
64
- }
65
- return result
66
- }
67
-
68
- export function isAbortError(error: unknown): error is DOMException {
69
- return error instanceof DOMException && error.name === "AbortError"
70
- }
71
-
72
- export function connectResponsesWebSocket(options: ConnectResponsesWebSocketOptions) {
73
- return new Promise<WebSocket>((resolve, reject) => {
74
- if (options.signal?.aborted) {
75
- reject(abortError(options.signal))
76
- return
77
- }
78
-
79
- const headers: Record<string, string> = {
80
- ...options.headers,
81
- "openai-beta": options.headers["openai-beta"] ?? PROTOCOL_HEADER,
82
- }
83
- delete headers["content-length"]
84
-
85
- // Bun does not apply HTTP(S)_PROXY to WebSockets unless the proxy is supplied explicitly.
86
- const proxy =
87
- typeof Bun === "undefined"
88
- ? undefined
89
- : ProxyEnv.getProxyForUrl(options.url.replace(/^wss:/, "https:").replace(/^ws:/, "http:"))
90
- const connect = { headers, ...(proxy ? { proxy } : {}) }
91
- const socket = new WebSocket(options.url, connect)
92
- const timeout = options.timeout
93
- ? setTimeout(() => {
94
- cleanup()
95
- socket.on("error", () => {})
96
- socket.terminate()
97
- reject(new Error("WebSocket connect timed out"))
98
- }, options.timeout)
99
- : undefined
100
-
101
- function cleanup() {
102
- if (timeout) clearTimeout(timeout)
103
- socket.off("open", onOpen)
104
- socket.off("error", onError)
105
- socket.off("close", onClose)
106
- options.signal?.removeEventListener("abort", onAbort)
107
- }
108
-
109
- function onOpen() {
110
- cleanup()
111
- resolve(socket)
112
- }
113
-
114
- function onError(error: unknown) {
115
- socket.on("error", () => {})
116
- cleanup()
117
- reject(error instanceof Error ? error : new Error(errorMessage(error), { cause: error }))
118
- }
119
-
120
- function onClose(code: number, reason: Buffer) {
121
- cleanup()
122
- reject(new Error(closeMessage("WebSocket closed before open", code, reason)))
123
- }
124
-
125
- function onAbort() {
126
- cleanup()
127
- socket.on("error", () => {})
128
- socket.terminate()
129
- reject(abortError(options.signal))
130
- }
131
-
132
- socket.once("open", onOpen)
133
- socket.once("error", onError)
134
- socket.once("close", onClose)
135
- options.signal?.addEventListener("abort", onAbort, { once: true })
136
- })
137
- }
138
-
139
- export function streamResponsesWebSocket(options: StreamResponsesWebSocketOptions) {
140
- const encoder = new TextEncoder()
141
-
142
- let socket = options.socket
143
- let controller: ReadableStreamDefaultController<Uint8Array> | undefined
144
- let cleanupSocket = () => {}
145
- let completed = false
146
- let emitted = false
147
- let idleTimer: ReturnType<typeof setTimeout> | undefined
148
-
149
- function cleanup() {
150
- if (idleTimer) clearTimeout(idleTimer)
151
- cleanupSocket()
152
- options.signal?.removeEventListener("abort", onAbort)
153
- }
154
-
155
- function terminateSocket(target = socket) {
156
- target.on("error", () => {})
157
- target.terminate()
158
- }
159
-
160
- function closeCompleted() {
161
- cleanup()
162
- controller?.enqueue(encoder.encode("data: [DONE]\n\n"))
163
- controller?.close()
164
- }
165
-
166
- function invalidate(error: ProviderError.ResponseStreamError, closeCode?: number) {
167
- if (completed) return
168
- completed = true
169
- cleanup()
170
- options.onConnectionInvalid?.(error, closeCode)
171
- controller?.error(error)
172
- }
173
-
174
- function resetIdleTimeout(message: string) {
175
- if (completed) return
176
- if (!options.idleTimeout) return
177
- if (idleTimer) clearTimeout(idleTimer)
178
- idleTimer = setTimeout(() => invalidate(new ProviderError.ResponseStreamError(message)), options.idleTimeout)
179
- }
180
-
181
- async function onMessage(data: WebSocket.RawData, isBinary: boolean) {
182
- if (completed) return
183
- if (isBinary) {
184
- invalidate(new ProviderError.ResponseStreamError("Unexpected binary WebSocket frame"))
185
- return
186
- }
187
-
188
- const text = data.toString()
189
- const event = (() => {
190
- try {
191
- const parsed = JSON.parse(text)
192
- return typeof parsed === "object" && parsed !== null ? parsed : undefined
193
- } catch {
194
- return undefined
195
- }
196
- })()
197
-
198
- if (event?.type === "error" && options.onRetryableTerminal) {
199
- cleanupSocket()
200
- if (idleTimer) clearTimeout(idleTimer)
201
- idleTimer = undefined
202
- try {
203
- const next = await options.onRetryableTerminal(event)
204
- if (completed) {
205
- if (next) terminateSocket(next)
206
- return
207
- }
208
- if (next) {
209
- attach(next)
210
- return
211
- }
212
- } catch (error) {
213
- invalidate(
214
- new ProviderError.ResponseStreamError(error instanceof Error ? error.message : String(error), {
215
- cause: error,
216
- }),
217
- )
218
- return
219
- }
220
- }
221
-
222
- const wrappedError = parseWrappedError(event, text)
223
- if (wrappedError && event) {
224
- if (!emitted) options.onFirstEvent?.(wrappedError)
225
- completed = true
226
- cleanup()
227
- options.onTerminal?.(event)
228
- controller?.error(
229
- new APICallError({
230
- message: wrappedError.message,
231
- url: socket.url,
232
- requestBodyValues: options.body,
233
- statusCode: wrappedError.status,
234
- responseHeaders: wrappedError.headers,
235
- responseBody: wrappedError.body,
236
- }),
237
- )
238
- return
239
- }
240
-
241
- if (!emitted) options.onFirstEvent?.()
242
- controller?.enqueue(
243
- encoder.encode(
244
- `${text
245
- .split(/\r?\n/)
246
- .map((line) => `data: ${line}`)
247
- .join("\n")}\n\n`,
248
- ),
249
- )
250
- emitted = true
251
- resetIdleTimeout("idle timeout waiting for websocket")
252
-
253
- if (!event) return
254
-
255
- if (event.type === "response.completed" || event.type === "response.done") {
256
- completed = true
257
- options.onComplete?.(event)
258
- options.onTerminal?.(event)
259
- closeCompleted()
260
- return
261
- }
262
-
263
- if (event.type === "response.failed" || event.type === "response.incomplete" || event.type === "error") {
264
- completed = true
265
- options.onTerminal?.(event)
266
- closeCompleted()
267
- }
268
- }
269
-
270
- function onError(error: Error) {
271
- invalidate(new ProviderError.ResponseStreamError(error.message, { cause: error }))
272
- }
273
-
274
- function onClose(code: number, reason: Buffer) {
275
- if (completed) return
276
- invalidate(
277
- new ProviderError.ResponseStreamError(closeMessage("WebSocket closed before response.completed", code, reason)),
278
- code,
279
- )
280
- }
281
-
282
- function onAbort() {
283
- const error = abortError(options.signal)
284
- if (completed) return
285
- completed = true
286
- cleanup()
287
- terminateSocket()
288
- options.onAbort?.(error)
289
- controller?.error(error)
290
- }
291
-
292
- function onCancel(reason: unknown) {
293
- if (completed) return
294
- completed = true
295
- cleanup()
296
- terminateSocket()
297
- options.onAbort?.(cancelError(reason))
298
- }
299
-
300
- function attach(next: WebSocket) {
301
- cleanupSocket()
302
- socket = next
303
- socket.on("message", onMessage)
304
- socket.once("error", onError)
305
- socket.once("close", onClose)
306
- cleanupSocket = () => {
307
- socket.off("message", onMessage)
308
- socket.off("error", onError)
309
- socket.off("close", onClose)
310
- }
311
- const { stream: _stream, background: _background, ...payload } = options.body
312
- resetIdleTimeout("idle timeout sending websocket request")
313
- socket.send(JSON.stringify({ type: "response.create", ...payload }), (error) => {
314
- if (completed) return
315
- resetIdleTimeout("idle timeout waiting for websocket")
316
- if (error) invalidate(new ProviderError.ResponseStreamError(error.message, { cause: error }))
317
- })
318
- }
319
-
320
- return new Response(
321
- new ReadableStream<Uint8Array>({
322
- start(next) {
323
- controller = next
324
- options.signal?.addEventListener("abort", onAbort, { once: true })
325
-
326
- if (options.signal?.aborted) {
327
- onAbort()
328
- return
329
- }
330
-
331
- attach(socket)
332
- },
333
- cancel(reason) {
334
- onCancel(reason)
335
- },
336
- }),
337
- {
338
- status: 200,
339
- headers: { "content-type": "text/event-stream" },
340
- },
341
- )
342
- }
343
-
344
- function parseWrappedError(event: Record<string, unknown> | undefined, body: string) {
345
- if (event?.type !== "error") return
346
- const status = event.status ?? event.status_code
347
- if (typeof status !== "number" || (status >= 200 && status < 300)) return
348
- return {
349
- status,
350
- headers: isRecord(event.headers)
351
- ? Object.fromEntries(
352
- Object.entries(event.headers).flatMap(([key, value]) =>
353
- typeof value === "string" || typeof value === "number" || typeof value === "boolean"
354
- ? [[key, String(value)]]
355
- : [],
356
- ),
357
- )
358
- : undefined,
359
- body,
360
- message: isRecord(event.error) && typeof event.error.message === "string" ? event.error.message : `${status}`,
361
- }
362
- }
363
-
364
- function cancelError(reason: unknown) {
365
- if (isAbortError(reason)) return reason
366
- if (reason instanceof Error) return reason
367
- return new DOMException(typeof reason === "string" ? reason : "Aborted", "AbortError")
368
- }
369
-
370
- function abortError(signal: AbortSignal | undefined) {
371
- const reason = signal?.reason
372
- if (isAbortError(reason)) return reason
373
- return new DOMException(reason instanceof Error ? reason.message : "Aborted", "AbortError")
374
- }
375
-
376
- function closeMessage(message: string, code: number, reason: Buffer) {
377
- const details = [`code ${code}`]
378
- if (code === MESSAGE_TOO_BIG_CLOSE_CODE) details.push("message too big")
379
- if (reason.length > 0) details.push(reason.toString())
380
- return `${message} (${details.join(": ")})`
381
- }
382
-
383
- export * as OpenAIWebSocket from "./ws"
@@ -1,24 +0,0 @@
1
- export * as PluginPtyEnvironment from "./pty-environment"
2
-
3
- import { PtyEnvironment } from "@soraacode/server/pty-environment"
4
- import { Effect, Layer } from "effect"
5
- import { InstanceStore } from "@/project/instance-store"
6
- import { Plugin } from "."
7
-
8
- export const layer = Layer.effect(
9
- PtyEnvironment.Service,
10
- Effect.gen(function* () {
11
- const plugin = yield* Plugin.Service
12
- const instances = yield* InstanceStore.Service
13
- return PtyEnvironment.Service.of({
14
- get: Effect.fn("PtyEnvironment.get")(function* (input) {
15
- return yield* instances.provide(
16
- { directory: input.directory },
17
- plugin
18
- .trigger("shell.env", { cwd: input.cwd }, { env: {} as Record<string, string> })
19
- .pipe(Effect.map((result) => result.env)),
20
- )
21
- }),
22
- })
23
- }),
24
- )