soraacode 0.1.7 → 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.
- package/LICENSE +21 -0
- package/bin/soraacode.exe +10 -0
- package/package.json +17 -114
- package/{script/postinstall.mjs → postinstall.mjs} +5 -5
- package/AGENTS.md +0 -131
- package/Dockerfile +0 -18
- package/README.md +0 -15
- package/bin/opencode +0 -199
- package/bin/soraacode +0 -199
- package/bunfig.toml +0 -7
- package/git +0 -0
- package/migration/20260511173437_session-metadata/migration.sql +0 -1
- package/migration/20260511173437_session-metadata/snapshot.json +0 -1500
- package/parsers-config.ts +0 -1
- package/script/bench-search.ts +0 -94
- package/script/bench-test-suite.ts +0 -52
- package/script/build-headless.ts +0 -51
- package/script/build.ts +0 -245
- package/script/generate.ts +0 -14
- package/script/httpapi-exercise.ts +0 -1
- package/script/profile-test-files.ts +0 -42
- package/script/publish.ts +0 -213
- package/script/run-workspace-server +0 -106
- package/script/schema.ts +0 -77
- package/script/time.ts +0 -6
- package/script/trace-imports.ts +0 -153
- package/specs/effect/error-boundaries-plan.md +0 -235
- package/specs/effect/errors.md +0 -207
- package/specs/effect/facades.md +0 -218
- package/specs/effect/guide.md +0 -247
- package/specs/effect/instance-context.md +0 -13
- package/specs/effect/loose-ends.md +0 -30
- package/specs/effect/migration.md +0 -62
- package/specs/effect/routes.md +0 -61
- package/specs/effect/schema.md +0 -88
- package/specs/effect/server-package.md +0 -58
- package/specs/effect/todo.md +0 -241
- package/specs/effect/tools.md +0 -88
- package/specs/openapi-translation-cleanup.md +0 -204
- package/specs/tui-plugins.md +0 -544
- package/specs/v2/api.ts +0 -67
- package/specs/v2/message-shape.md +0 -136
- package/specs/v2/notifications.md +0 -13
- package/specs/v2/tui-command-shim.md +0 -67
- package/src/account/account.ts +0 -481
- package/src/account/repo.ts +0 -171
- package/src/account/schema.ts +0 -99
- package/src/account/url.ts +0 -8
- package/src/acp/agent.ts +0 -95
- package/src/acp/config-option.ts +0 -206
- package/src/acp/content.ts +0 -269
- package/src/acp/directory.ts +0 -212
- package/src/acp/error.ts +0 -97
- package/src/acp/event.ts +0 -421
- package/src/acp/permission.ts +0 -254
- package/src/acp/profile.ts +0 -42
- package/src/acp/service.ts +0 -1226
- package/src/acp/session.ts +0 -232
- package/src/acp/tool.ts +0 -364
- package/src/acp/usage.ts +0 -243
- package/src/agent/agent.ts +0 -450
- package/src/agent/generate.txt +0 -75
- package/src/agent/prompt/compaction.txt +0 -5
- package/src/agent/prompt/explore.txt +0 -18
- package/src/agent/prompt/summary.txt +0 -11
- package/src/agent/prompt/title.txt +0 -44
- package/src/agent/subagent-permissions.ts +0 -27
- package/src/audio.d.ts +0 -14
- package/src/auth/index.ts +0 -97
- package/src/background/job.ts +0 -37
- package/src/bus/global.ts +0 -22
- package/src/cli/bootstrap.ts +0 -11
- package/src/cli/cmd/cmd.ts +0 -7
- package/src/cli/cmd/serve.ts +0 -24
- package/src/cli/effect/prompt.ts +0 -37
- package/src/cli/effect-cmd.ts +0 -96
- package/src/cli/error.ts +0 -130
- package/src/cli/heap.ts +0 -45
- package/src/cli/logo.ts +0 -2
- package/src/cli/network.ts +0 -80
- package/src/cli/ui.ts +0 -132
- package/src/command/index.ts +0 -177
- package/src/command/template/initialize.txt +0 -66
- package/src/command/template/review.txt +0 -101
- package/src/config/agent.ts +0 -59
- package/src/config/command.ts +0 -39
- package/src/config/config.ts +0 -707
- package/src/config/entry-name.ts +0 -19
- package/src/config/managed.ts +0 -69
- package/src/config/markdown.ts +0 -36
- package/src/config/parse.ts +0 -61
- package/src/config/paths.ts +0 -45
- package/src/config/plugin.ts +0 -79
- package/src/config/v2-compat.ts +0 -449
- package/src/config/variable.ts +0 -91
- package/src/control-plane/adapters/index.ts +0 -41
- package/src/control-plane/adapters/worktree.ts +0 -96
- package/src/control-plane/dev/README.md +0 -19
- package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
- package/src/control-plane/types.ts +0 -59
- package/src/control-plane/util.ts +0 -39
- package/src/control-plane/workspace-adapter-runtime.ts +0 -51
- package/src/control-plane/workspace-context.ts +0 -26
- package/src/control-plane/workspace.ts +0 -966
- package/src/effect/app-node-builder-v1.ts +0 -12
- package/src/effect/app-runtime.ts +0 -135
- package/src/effect/bootstrap-runtime.ts +0 -19
- package/src/effect/bridge.ts +0 -84
- package/src/effect/config-service.ts +0 -67
- package/src/effect/instance-ref.ts +0 -11
- package/src/effect/instance-registry.ts +0 -12
- package/src/effect/instance-state.ts +0 -69
- package/src/effect/promise.ts +0 -17
- package/src/effect/run-service.ts +0 -47
- package/src/effect/runner.ts +0 -217
- package/src/effect/runtime-flags.ts +0 -78
- package/src/env/index.ts +0 -41
- package/src/event-manifest.ts +0 -3
- package/src/event-v2-bridge.ts +0 -71
- package/src/format/formatter.ts +0 -404
- package/src/format/index.ts +0 -203
- package/src/git/index.ts +0 -348
- package/src/id/id.ts +0 -80
- package/src/ide/index.ts +0 -54
- package/src/image/image.ts +0 -172
- package/src/index-headless.ts +0 -89
- package/src/index.ts +0 -93
- package/src/installation/index.ts +0 -336
- package/src/lsp/client.ts +0 -650
- package/src/lsp/diagnostic.ts +0 -29
- package/src/lsp/language.ts +0 -121
- package/src/lsp/launch.ts +0 -21
- package/src/lsp/lsp.ts +0 -507
- package/src/lsp/server.ts +0 -1983
- package/src/markdown.d.ts +0 -4
- package/src/mcp/auth.ts +0 -163
- package/src/mcp/browser.ts +0 -37
- package/src/mcp/catalog.ts +0 -170
- package/src/mcp/index.ts +0 -1004
- package/src/mcp/oauth-callback.ts +0 -194
- package/src/mcp/oauth-provider.ts +0 -259
- package/src/node.ts +0 -4
- package/src/patch/index.ts +0 -686
- package/src/permission/arity.ts +0 -163
- package/src/permission/evaluate.ts +0 -1
- package/src/permission/index.ts +0 -223
- package/src/plugin/github-copilot/copilot.ts +0 -415
- package/src/plugin/github-copilot/models.ts +0 -261
- package/src/plugin/index.ts +0 -298
- package/src/plugin/install.ts +0 -439
- package/src/plugin/loader.ts +0 -237
- package/src/plugin/meta.ts +0 -188
- package/src/plugin/openai/README.md +0 -31
- package/src/plugin/openai/codex.ts +0 -575
- package/src/plugin/openai/ws-pool.ts +0 -271
- package/src/plugin/openai/ws.ts +0 -383
- package/src/plugin/pty-environment.ts +0 -24
- package/src/plugin/shared.ts +0 -323
- package/src/project/bootstrap-service.ts +0 -9
- package/src/project/bootstrap.ts +0 -58
- package/src/project/instance-context.ts +0 -24
- package/src/project/instance-runtime.ts +0 -16
- package/src/project/instance-store.ts +0 -213
- package/src/project/project.ts +0 -483
- package/src/project/vcs.ts +0 -423
- package/src/provider/auth.ts +0 -229
- package/src/provider/error.ts +0 -195
- package/src/provider/model-status.ts +0 -8
- package/src/provider/provider.ts +0 -1955
- package/src/provider/transform.ts +0 -1923
- package/src/question/index.ts +0 -161
- package/src/question/schema.ts +0 -4
- package/src/server/auth.ts +0 -48
- package/src/server/event.ts +0 -10
- package/src/server/global-lifecycle.ts +0 -28
- package/src/server/mdns.ts +0 -47
- package/src/server/proxy-util.ts +0 -48
- package/src/server/routes/instance/httpapi/AGENTS.md +0 -39
- package/src/server/routes/instance/httpapi/api.ts +0 -97
- package/src/server/routes/instance/httpapi/errors.ts +0 -193
- package/src/server/routes/instance/httpapi/groups/config.ts +0 -65
- package/src/server/routes/instance/httpapi/groups/control-plane.ts +0 -35
- package/src/server/routes/instance/httpapi/groups/control.ts +0 -76
- package/src/server/routes/instance/httpapi/groups/event.ts +0 -29
- package/src/server/routes/instance/httpapi/groups/experimental.ts +0 -275
- package/src/server/routes/instance/httpapi/groups/file.ts +0 -185
- package/src/server/routes/instance/httpapi/groups/global.ts +0 -139
- package/src/server/routes/instance/httpapi/groups/instance.ts +0 -206
- package/src/server/routes/instance/httpapi/groups/mcp.ts +0 -156
- package/src/server/routes/instance/httpapi/groups/metadata.ts +0 -18
- package/src/server/routes/instance/httpapi/groups/permission.ts +0 -61
- package/src/server/routes/instance/httpapi/groups/project-copy.ts +0 -32
- package/src/server/routes/instance/httpapi/groups/project.ts +0 -93
- package/src/server/routes/instance/httpapi/groups/provider.ts +0 -101
- package/src/server/routes/instance/httpapi/groups/pty.ts +0 -172
- package/src/server/routes/instance/httpapi/groups/query.ts +0 -12
- package/src/server/routes/instance/httpapi/groups/question.ts +0 -74
- package/src/server/routes/instance/httpapi/groups/session.ts +0 -462
- package/src/server/routes/instance/httpapi/groups/sync.ts +0 -113
- package/src/server/routes/instance/httpapi/groups/tui.ts +0 -208
- package/src/server/routes/instance/httpapi/groups/workspace.ts +0 -141
- package/src/server/routes/instance/httpapi/handlers/config.ts +0 -34
- package/src/server/routes/instance/httpapi/handlers/control-plane.ts +0 -37
- package/src/server/routes/instance/httpapi/handlers/control.ts +0 -43
- package/src/server/routes/instance/httpapi/handlers/event.ts +0 -99
- package/src/server/routes/instance/httpapi/handlers/experimental.ts +0 -193
- package/src/server/routes/instance/httpapi/handlers/file.ts +0 -139
- package/src/server/routes/instance/httpapi/handlers/global.ts +0 -126
- package/src/server/routes/instance/httpapi/handlers/instance.ts +0 -110
- package/src/server/routes/instance/httpapi/handlers/mcp.ts +0 -111
- package/src/server/routes/instance/httpapi/handlers/permission.ts +0 -41
- package/src/server/routes/instance/httpapi/handlers/project-copy.ts +0 -83
- package/src/server/routes/instance/httpapi/handlers/project.ts +0 -63
- package/src/server/routes/instance/httpapi/handlers/provider.ts +0 -116
- package/src/server/routes/instance/httpapi/handlers/pty.ts +0 -273
- package/src/server/routes/instance/httpapi/handlers/question.ts +0 -54
- package/src/server/routes/instance/httpapi/handlers/session-errors.ts +0 -21
- package/src/server/routes/instance/httpapi/handlers/session.ts +0 -442
- package/src/server/routes/instance/httpapi/handlers/sync.ts +0 -89
- package/src/server/routes/instance/httpapi/handlers/tui.ts +0 -131
- package/src/server/routes/instance/httpapi/handlers/workspace.ts +0 -102
- package/src/server/routes/instance/httpapi/lifecycle.ts +0 -54
- package/src/server/routes/instance/httpapi/middleware/authorization.ts +0 -150
- package/src/server/routes/instance/httpapi/middleware/compression.ts +0 -64
- package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +0 -29
- package/src/server/routes/instance/httpapi/middleware/error.ts +0 -44
- package/src/server/routes/instance/httpapi/middleware/fence.ts +0 -25
- package/src/server/routes/instance/httpapi/middleware/instance-context.ts +0 -43
- package/src/server/routes/instance/httpapi/middleware/proxy.ts +0 -131
- package/src/server/routes/instance/httpapi/middleware/schema-error.ts +0 -41
- package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +0 -250
- package/src/server/routes/instance/httpapi/public.ts +0 -537
- package/src/server/routes/instance/httpapi/server.ts +0 -325
- package/src/server/routes/instance/httpapi/websocket-tracker.ts +0 -60
- package/src/server/server.ts +0 -224
- package/src/server/shared/fence.ts +0 -60
- package/src/server/shared/pty-ticket.ts +0 -15
- package/src/server/shared/public-ui.ts +0 -12
- package/src/server/shared/tui-control.ts +0 -28
- package/src/server/shared/ui.ts +0 -108
- package/src/server/shared/workspace-routing.ts +0 -45
- package/src/server/tui-event.ts +0 -1
- package/src/session/compaction.ts +0 -608
- package/src/session/instruction.ts +0 -237
- package/src/session/llm/AGENTS.md +0 -90
- package/src/session/llm/ai-sdk.ts +0 -291
- package/src/session/llm/native-request.ts +0 -196
- package/src/session/llm/native-runtime.ts +0 -195
- package/src/session/llm/request.ts +0 -226
- package/src/session/llm.ts +0 -390
- package/src/session/message-error.ts +0 -14
- package/src/session/message-v2.ts +0 -742
- package/src/session/message.ts +0 -148
- package/src/session/overflow.ts +0 -34
- package/src/session/processor.ts +0 -732
- package/src/session/prompt/build-switch.txt +0 -5
- package/src/session/prompt/default.txt +0 -35
- package/src/session/prompt/plan-mode.txt +0 -70
- package/src/session/prompt/plan.txt +0 -26
- package/src/session/prompt.ts +0 -1631
- package/src/session/reminders.ts +0 -92
- package/src/session/retry.ts +0 -209
- package/src/session/revert.ts +0 -136
- package/src/session/run-state.ts +0 -151
- package/src/session/schema.ts +0 -26
- package/src/session/session.ts +0 -1016
- package/src/session/status.ts +0 -56
- package/src/session/summary.ts +0 -160
- package/src/session/system.ts +0 -123
- package/src/session/todo.ts +0 -74
- package/src/session/tools.ts +0 -590
- package/src/share/session.ts +0 -58
- package/src/share/share-next.ts +0 -371
- package/src/skill/discovery.ts +0 -140
- package/src/skill/index.ts +0 -354
- package/src/snapshot/index.ts +0 -807
- package/src/sql.d.ts +0 -4
- package/src/storage/schema.ts +0 -5
- package/src/storage/storage.ts +0 -327
- package/src/sync/README.md +0 -179
- package/src/sync/schema.ts +0 -11
- package/src/temporary.ts +0 -31
- package/src/tool/apply_patch.ts +0 -313
- package/src/tool/apply_patch.txt +0 -14
- package/src/tool/code-mode.ts +0 -310
- package/src/tool/edit.ts +0 -737
- package/src/tool/edit.txt +0 -13
- package/src/tool/external-directory.ts +0 -49
- package/src/tool/glob.ts +0 -76
- package/src/tool/glob.txt +0 -7
- package/src/tool/grep.ts +0 -115
- package/src/tool/grep.txt +0 -7
- package/src/tool/invalid.ts +0 -21
- package/src/tool/json-schema.ts +0 -164
- package/src/tool/lsp.ts +0 -113
- package/src/tool/lsp.txt +0 -1
- package/src/tool/mcp-websearch.ts +0 -96
- package/src/tool/plan-enter.txt +0 -1
- package/src/tool/plan-exit.txt +0 -1
- package/src/tool/plan.ts +0 -79
- package/src/tool/question.ts +0 -44
- package/src/tool/question.txt +0 -1
- package/src/tool/read.ts +0 -386
- package/src/tool/read.txt +0 -13
- package/src/tool/registry.ts +0 -455
- package/src/tool/schema.ts +0 -14
- package/src/tool/shell/id.ts +0 -19
- package/src/tool/shell/prompt.ts +0 -293
- package/src/tool/shell/shell.txt +0 -21
- package/src/tool/shell.ts +0 -645
- package/src/tool/skill.ts +0 -70
- package/src/tool/skill.txt +0 -1
- package/src/tool/task.ts +0 -371
- package/src/tool/task.txt +0 -11
- package/src/tool/todo.ts +0 -46
- package/src/tool/todowrite.txt +0 -11
- package/src/tool/tool.ts +0 -183
- package/src/tool/truncate.ts +0 -156
- package/src/tool/truncation-dir.ts +0 -4
- package/src/tool/webfetch.ts +0 -192
- package/src/tool/webfetch.txt +0 -9
- package/src/tool/websearch.ts +0 -143
- package/src/tool/websearch.txt +0 -9
- package/src/tool/write.ts +0 -104
- package/src/tool/write.txt +0 -11
- package/src/util/archive.ts +0 -17
- package/src/util/bom.ts +0 -27
- package/src/util/data-url.ts +0 -9
- package/src/util/defer.ts +0 -10
- package/src/util/effect-http-client.ts +0 -11
- package/src/util/error.ts +0 -16
- package/src/util/filesystem.ts +0 -251
- package/src/util/html.ts +0 -8
- package/src/util/iife.ts +0 -3
- package/src/util/lazy.ts +0 -20
- package/src/util/local-context.ts +0 -25
- package/src/util/locale.ts +0 -4
- package/src/util/media.ts +0 -26
- package/src/util/process.ts +0 -177
- package/src/util/proxy-env.ts +0 -72
- package/src/util/queue.ts +0 -32
- package/src/util/record.ts +0 -3
- package/src/util/repository.ts +0 -232
- package/src/util/rpc.ts +0 -66
- package/src/util/signal.ts +0 -12
- package/src/util/timeout.ts +0 -13
- package/src/util/token.ts +0 -1
- package/src/util/wildcard.ts +0 -59
- package/src/worktree/index.ts +0 -623
- package/sst-env.d.ts +0 -10
- package/tsconfig.headless.json +0 -13
- package/tsconfig.json +0 -16
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { listAdapters } from "@/control-plane/adapters"
|
|
2
|
-
import { Workspace } from "@/control-plane/workspace"
|
|
3
|
-
import * as InstanceState from "@/effect/instance-state"
|
|
4
|
-
import { Vcs } from "@/project/vcs"
|
|
5
|
-
import { Cause, Effect } from "effect"
|
|
6
|
-
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
|
7
|
-
import { InstanceHttpApi } from "../api"
|
|
8
|
-
import { notFound } from "../errors"
|
|
9
|
-
import { ApiVcsApplyError } from "../groups/instance"
|
|
10
|
-
import { ApiWorkspaceCreateError, ApiWorkspaceWarpError, CreatePayload, WarpPayload } from "../groups/workspace"
|
|
11
|
-
|
|
12
|
-
export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspace", (handlers) =>
|
|
13
|
-
Effect.gen(function* () {
|
|
14
|
-
const workspace = yield* Workspace.Service
|
|
15
|
-
|
|
16
|
-
const adapters = Effect.fn("WorkspaceHttpApi.adapters")(function* () {
|
|
17
|
-
const instance = yield* InstanceState.context
|
|
18
|
-
return yield* Effect.sync(() => listAdapters(instance.project.id))
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const list = Effect.fn("WorkspaceHttpApi.list")(function* () {
|
|
22
|
-
return yield* workspace.list((yield* InstanceState.context).project)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const create = Effect.fn("WorkspaceHttpApi.create")(function* (ctx: { payload: typeof CreatePayload.Type }) {
|
|
26
|
-
const instance = yield* InstanceState.context
|
|
27
|
-
return yield* workspace
|
|
28
|
-
.create({
|
|
29
|
-
...ctx.payload,
|
|
30
|
-
extra: ctx.payload.extra ?? null,
|
|
31
|
-
projectID: instance.project.id,
|
|
32
|
-
})
|
|
33
|
-
.pipe(
|
|
34
|
-
Effect.catchCause((cause) => {
|
|
35
|
-
// Plugin throws surface as defects (because EffectBridge.fromPromise uses Effect.promise),
|
|
36
|
-
// bypassing Effect.mapError. Walk the cause to surface the real error to the client.
|
|
37
|
-
const die = cause.reasons.find(Cause.isDieReason)
|
|
38
|
-
const fail = cause.reasons.find(Cause.isFailReason)
|
|
39
|
-
const reason: unknown = die?.defect ?? fail?.error
|
|
40
|
-
const message = reason instanceof Error ? reason.message : "Workspace creation failed"
|
|
41
|
-
return Effect.fail(
|
|
42
|
-
new ApiWorkspaceCreateError({
|
|
43
|
-
name: "WorkspaceCreateError",
|
|
44
|
-
data: { message },
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
47
|
-
}),
|
|
48
|
-
)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const syncList = Effect.fn("WorkspaceHttpApi.syncList")(function* () {
|
|
52
|
-
yield* workspace.syncList((yield* InstanceState.context).project)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const status = Effect.fn("WorkspaceHttpApi.status")(function* () {
|
|
56
|
-
const ids = new Set((yield* workspace.list((yield* InstanceState.context).project)).map((item) => item.id))
|
|
57
|
-
return (yield* workspace.status()).filter((item) => ids.has(item.workspaceID))
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const remove = Effect.fn("WorkspaceHttpApi.remove")(function* (ctx: { params: { id: Workspace.Info["id"] } }) {
|
|
61
|
-
return yield* workspace.remove(ctx.params.id)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const warp = Effect.fn("WorkspaceHttpApi.warp")(function* (ctx: { payload: typeof WarpPayload.Type }) {
|
|
65
|
-
yield* workspace
|
|
66
|
-
.sessionWarp({
|
|
67
|
-
workspaceID: ctx.payload.id,
|
|
68
|
-
sessionID: ctx.payload.sessionID,
|
|
69
|
-
copyChanges: ctx.payload.copyChanges,
|
|
70
|
-
})
|
|
71
|
-
.pipe(
|
|
72
|
-
Effect.mapError((error) => {
|
|
73
|
-
if (error instanceof Workspace.WorkspaceNotFoundError) return notFound(error.message)
|
|
74
|
-
if (error instanceof Vcs.PatchApplyError) {
|
|
75
|
-
return new ApiVcsApplyError({
|
|
76
|
-
name: "VcsApplyError",
|
|
77
|
-
data: {
|
|
78
|
-
message: error.message,
|
|
79
|
-
reason: error.reason,
|
|
80
|
-
},
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
return new ApiWorkspaceWarpError({
|
|
84
|
-
name: "WorkspaceWarpError",
|
|
85
|
-
data: {
|
|
86
|
-
message: error.message,
|
|
87
|
-
},
|
|
88
|
-
})
|
|
89
|
-
}),
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
return handlers
|
|
94
|
-
.handle("adapters", adapters)
|
|
95
|
-
.handle("list", list)
|
|
96
|
-
.handle("create", create)
|
|
97
|
-
.handle("syncList", syncList)
|
|
98
|
-
.handle("status", status)
|
|
99
|
-
.handle("remove", remove)
|
|
100
|
-
.handle("warp", warp)
|
|
101
|
-
}),
|
|
102
|
-
)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { EffectBridge } from "@/effect/bridge"
|
|
2
|
-
import type { InstanceContext } from "@/project/instance-context"
|
|
3
|
-
import { InstanceStore } from "@/project/instance-store"
|
|
4
|
-
import { Effect } from "effect"
|
|
5
|
-
import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http"
|
|
6
|
-
|
|
7
|
-
type MarkedInstance = {
|
|
8
|
-
ctx: InstanceContext
|
|
9
|
-
store: InstanceStore.Interface
|
|
10
|
-
bridge: EffectBridge.Shape
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Disposal is requested by an endpoint handler, but must run from the outer
|
|
14
|
-
// server middleware after the response has been produced. The original Request
|
|
15
|
-
// object is the stable handoff key between those two phases.
|
|
16
|
-
const disposeAfterResponse = new WeakMap<object, MarkedInstance>()
|
|
17
|
-
|
|
18
|
-
const mark = (ctx: InstanceContext) =>
|
|
19
|
-
Effect.gen(function* () {
|
|
20
|
-
return { ctx, store: yield* InstanceStore.Service, bridge: yield* EffectBridge.make() }
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
export const markInstanceForDisposal = (ctx: InstanceContext) =>
|
|
24
|
-
Effect.gen(function* () {
|
|
25
|
-
const marked = yield* mark(ctx)
|
|
26
|
-
return yield* HttpEffect.appendPreResponseHandler((request, response) =>
|
|
27
|
-
Effect.sync(() => {
|
|
28
|
-
// The response is sent before disposeMiddleware performs the teardown.
|
|
29
|
-
disposeAfterResponse.set(request.source, marked)
|
|
30
|
-
return response
|
|
31
|
-
}),
|
|
32
|
-
)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
export const markInstanceForReload = (ctx: InstanceContext, next: InstanceStore.LoadInput) =>
|
|
36
|
-
Effect.gen(function* () {
|
|
37
|
-
const marked = yield* mark(ctx)
|
|
38
|
-
return yield* HttpEffect.appendPreResponseHandler((_request, response) =>
|
|
39
|
-
Effect.as(Effect.uninterruptible(marked.bridge.run(marked.store.reload(next))), response),
|
|
40
|
-
)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) =>
|
|
44
|
-
Effect.gen(function* () {
|
|
45
|
-
const response = yield* effect
|
|
46
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
47
|
-
const marked = disposeAfterResponse.get(request.source)
|
|
48
|
-
if (!marked) return response
|
|
49
|
-
disposeAfterResponse.delete(request.source)
|
|
50
|
-
yield* Effect.uninterruptible(marked.bridge.run(marked.store.dispose(marked.ctx))).pipe(
|
|
51
|
-
Effect.catchCause((cause) => Effect.logWarning("instance disposal failed", { cause })),
|
|
52
|
-
)
|
|
53
|
-
return response
|
|
54
|
-
})
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { ServerAuth } from "@/server/auth"
|
|
2
|
-
import { Effect, Encoding, Layer, Redacted } from "effect"
|
|
3
|
-
import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
|
4
|
-
import { HttpApiError, HttpApiMiddleware } from "effect/unstable/httpapi"
|
|
5
|
-
import { hasPtyConnectTicketURL } from "@/server/shared/pty-ticket"
|
|
6
|
-
import { isPublicUIPath } from "@/server/shared/public-ui"
|
|
7
|
-
export {
|
|
8
|
-
Authorization as ServerAuthorization,
|
|
9
|
-
authorizationLayer as serverAuthorizationLayer,
|
|
10
|
-
} from "@soraacode/server/middleware/authorization"
|
|
11
|
-
|
|
12
|
-
const AUTH_TOKEN_QUERY = "auth_token"
|
|
13
|
-
const UNAUTHORIZED = 401
|
|
14
|
-
const WWW_AUTHENTICATE = 'Basic realm="Secure Area"'
|
|
15
|
-
|
|
16
|
-
// Avoid HttpApiSecurity alternatives here: Effect security middleware wraps the
|
|
17
|
-
// full handler, so a downstream failure can make the next auth alternative run
|
|
18
|
-
// and remap an authorized NotFound into Unauthorized.
|
|
19
|
-
export class Authorization extends HttpApiMiddleware.Service<Authorization>()(
|
|
20
|
-
"@opencode/ExperimentalHttpApiAuthorization",
|
|
21
|
-
{
|
|
22
|
-
error: HttpApiError.UnauthorizedNoContent,
|
|
23
|
-
},
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
export class PtyConnectAuthorization extends HttpApiMiddleware.Service<PtyConnectAuthorization>()(
|
|
27
|
-
"@opencode/ExperimentalHttpApiPtyConnectAuthorization",
|
|
28
|
-
{
|
|
29
|
-
error: HttpApiError.UnauthorizedNoContent,
|
|
30
|
-
},
|
|
31
|
-
) {}
|
|
32
|
-
|
|
33
|
-
function emptyCredential() {
|
|
34
|
-
return {
|
|
35
|
-
username: "",
|
|
36
|
-
password: Redacted.make(""),
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function validateCredential<A, E, R>(
|
|
41
|
-
effect: Effect.Effect<A, E, R>,
|
|
42
|
-
credential: ServerAuth.DecodedCredentials,
|
|
43
|
-
config: ServerAuth.Info,
|
|
44
|
-
) {
|
|
45
|
-
return Effect.gen(function* () {
|
|
46
|
-
if (!ServerAuth.required(config)) return yield* effect
|
|
47
|
-
if (!ServerAuth.authorized(credential, config)) {
|
|
48
|
-
yield* HttpEffect.appendPreResponseHandler((_request, response) =>
|
|
49
|
-
Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)),
|
|
50
|
-
)
|
|
51
|
-
return yield* new HttpApiError.Unauthorized({})
|
|
52
|
-
}
|
|
53
|
-
return yield* effect
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function decodeCredential(input: string) {
|
|
58
|
-
return Effect.fromResult(Encoding.decodeBase64String(input)).pipe(
|
|
59
|
-
Effect.match({
|
|
60
|
-
onFailure: emptyCredential,
|
|
61
|
-
onSuccess: (header) => {
|
|
62
|
-
const separator = header.indexOf(":")
|
|
63
|
-
if (separator === -1) return emptyCredential()
|
|
64
|
-
return {
|
|
65
|
-
username: header.slice(0, separator),
|
|
66
|
-
password: Redacted.make(header.slice(separator + 1)),
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
}),
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function credentialFromRequest(request: HttpServerRequest.HttpServerRequest) {
|
|
74
|
-
return credentialFromURL(new URL(request.url, "http://localhost"), request)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function credentialFromURL(url: URL, request: HttpServerRequest.HttpServerRequest) {
|
|
78
|
-
const token = url.searchParams.get(AUTH_TOKEN_QUERY)
|
|
79
|
-
if (token) return decodeCredential(token)
|
|
80
|
-
const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "")
|
|
81
|
-
if (match) return decodeCredential(match[1])
|
|
82
|
-
return Effect.succeed(emptyCredential())
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function validateRawCredential<A, E, R>(
|
|
86
|
-
effect: Effect.Effect<A, E, R>,
|
|
87
|
-
credential: ServerAuth.DecodedCredentials,
|
|
88
|
-
config: ServerAuth.Info,
|
|
89
|
-
) {
|
|
90
|
-
if (!ServerAuth.required(config)) return effect
|
|
91
|
-
if (!ServerAuth.authorized(credential, config))
|
|
92
|
-
return Effect.succeed(
|
|
93
|
-
HttpServerResponse.empty({
|
|
94
|
-
status: UNAUTHORIZED,
|
|
95
|
-
headers: { "www-authenticate": WWW_AUTHENTICATE },
|
|
96
|
-
}),
|
|
97
|
-
)
|
|
98
|
-
return effect
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export const authorizationRouterMiddleware = HttpRouter.middleware()(
|
|
102
|
-
Effect.gen(function* () {
|
|
103
|
-
const config = yield* ServerAuth.Config
|
|
104
|
-
if (!ServerAuth.required(config)) return (effect) => effect
|
|
105
|
-
|
|
106
|
-
return (effect) =>
|
|
107
|
-
Effect.gen(function* () {
|
|
108
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
109
|
-
const url = new URL(request.url, "http://localhost")
|
|
110
|
-
if (isPublicUIPath(request.method, url.pathname)) return yield* effect
|
|
111
|
-
return yield* credentialFromURL(url, request).pipe(
|
|
112
|
-
Effect.flatMap((credential) => validateRawCredential(effect, credential, config)),
|
|
113
|
-
)
|
|
114
|
-
})
|
|
115
|
-
}),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
export const authorizationLayer = Layer.effect(
|
|
119
|
-
Authorization,
|
|
120
|
-
Effect.gen(function* () {
|
|
121
|
-
const config = yield* ServerAuth.Config
|
|
122
|
-
if (!ServerAuth.required(config)) return Authorization.of((effect) => effect)
|
|
123
|
-
return Authorization.of((effect) =>
|
|
124
|
-
Effect.gen(function* () {
|
|
125
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
126
|
-
return yield* credentialFromRequest(request).pipe(
|
|
127
|
-
Effect.flatMap((credential) => validateCredential(effect, credential, config)),
|
|
128
|
-
)
|
|
129
|
-
}),
|
|
130
|
-
)
|
|
131
|
-
}),
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
export const ptyConnectAuthorizationLayer = Layer.effect(
|
|
135
|
-
PtyConnectAuthorization,
|
|
136
|
-
Effect.gen(function* () {
|
|
137
|
-
const config = yield* ServerAuth.Config
|
|
138
|
-
if (!ServerAuth.required(config)) return PtyConnectAuthorization.of((effect) => effect)
|
|
139
|
-
return PtyConnectAuthorization.of((effect) =>
|
|
140
|
-
Effect.gen(function* () {
|
|
141
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
142
|
-
const url = new URL(request.url, "http://localhost")
|
|
143
|
-
if (hasPtyConnectTicketURL(url)) return yield* effect
|
|
144
|
-
return yield* credentialFromURL(url, request).pipe(
|
|
145
|
-
Effect.flatMap((credential) => validateCredential(effect, credential, config)),
|
|
146
|
-
)
|
|
147
|
-
}),
|
|
148
|
-
)
|
|
149
|
-
}),
|
|
150
|
-
)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { deflateSync, gzipSync } from "node:zlib"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { HttpBody, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
|
4
|
-
|
|
5
|
-
// Keep the server's compressible content-type set stable across HTTP backend changes.
|
|
6
|
-
const COMPRESSIBLE_CONTENT_TYPE_REGEX =
|
|
7
|
-
/^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i
|
|
8
|
-
|
|
9
|
-
const NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i
|
|
10
|
-
|
|
11
|
-
const STREAMING_PATHS = new Set(["/event", "/global/event"])
|
|
12
|
-
const STREAMING_POST_REGEX = /^\/session\/[^/]+\/(?:message|prompt_async)$/
|
|
13
|
-
|
|
14
|
-
const THRESHOLD_BYTES = 1024
|
|
15
|
-
|
|
16
|
-
type Encoding = "gzip" | "deflate"
|
|
17
|
-
|
|
18
|
-
function pickEncoding(acceptEncoding: string | undefined): Encoding | undefined {
|
|
19
|
-
if (!acceptEncoding) return undefined
|
|
20
|
-
const lower = acceptEncoding.toLowerCase()
|
|
21
|
-
if (lower.includes("gzip")) return "gzip"
|
|
22
|
-
if (lower.includes("deflate")) return "deflate"
|
|
23
|
-
return undefined
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function pathOf(url: string): string {
|
|
27
|
-
const queryIndex = url.indexOf("?")
|
|
28
|
-
return queryIndex === -1 ? url : url.slice(0, queryIndex)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const compressionLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
|
|
32
|
-
Effect.gen(function* () {
|
|
33
|
-
const response = yield* effect
|
|
34
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
35
|
-
|
|
36
|
-
if (request.method === "HEAD") return response
|
|
37
|
-
if (response.headers["content-encoding"]) return response
|
|
38
|
-
if (response.headers["transfer-encoding"]) return response
|
|
39
|
-
|
|
40
|
-
const body = response.body
|
|
41
|
-
if (body._tag !== "Uint8Array") return response
|
|
42
|
-
if (body.body.byteLength < THRESHOLD_BYTES) return response
|
|
43
|
-
|
|
44
|
-
const cacheControl = response.headers["cache-control"]
|
|
45
|
-
if (cacheControl && NO_TRANSFORM_REGEX.test(cacheControl)) return response
|
|
46
|
-
|
|
47
|
-
const path = pathOf(request.url)
|
|
48
|
-
if (STREAMING_PATHS.has(path)) return response
|
|
49
|
-
if (request.method === "POST" && STREAMING_POST_REGEX.test(path)) return response
|
|
50
|
-
|
|
51
|
-
const contentType = body.contentType
|
|
52
|
-
if (!COMPRESSIBLE_CONTENT_TYPE_REGEX.test(contentType)) return response
|
|
53
|
-
|
|
54
|
-
const encoding = pickEncoding(request.headers["accept-encoding"])
|
|
55
|
-
if (!encoding) return response
|
|
56
|
-
|
|
57
|
-
const compressed = encoding === "gzip" ? gzipSync(body.body) : deflateSync(body.body)
|
|
58
|
-
return HttpServerResponse.setHeader(
|
|
59
|
-
HttpServerResponse.setBody(response, HttpBody.uint8Array(compressed, contentType)),
|
|
60
|
-
"content-encoding",
|
|
61
|
-
encoding,
|
|
62
|
-
)
|
|
63
|
-
}),
|
|
64
|
-
).layer
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect"
|
|
2
|
-
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
|
|
3
|
-
|
|
4
|
-
// effect-smol's HttpMiddleware.cors builds OPTIONS preflight responses by
|
|
5
|
-
// spreading allowOrigin() and allowHeaders() into the same record. Both set
|
|
6
|
-
// the `vary` key, so allowHeaders' `Vary: Access-Control-Request-Headers`
|
|
7
|
-
// overwrites allowOrigin's `Vary: Origin`. With dynamic origin echoing, the
|
|
8
|
-
// missing `Vary: Origin` lets shared caches reuse a preflight cached for one
|
|
9
|
-
// origin against a different origin.
|
|
10
|
-
//
|
|
11
|
-
// TODO: upstream a fix that merges Vary values in headersFromRequestOptions
|
|
12
|
-
// (packages/effect/src/unstable/http/HttpMiddleware.ts ~line 332).
|
|
13
|
-
export const corsVaryFix = HttpRouter.middleware(
|
|
14
|
-
(effect) =>
|
|
15
|
-
Effect.gen(function* () {
|
|
16
|
-
const response = yield* effect
|
|
17
|
-
const allowOrigin = response.headers["access-control-allow-origin"]
|
|
18
|
-
if (!allowOrigin || allowOrigin === "*") return response
|
|
19
|
-
|
|
20
|
-
const vary = response.headers["vary"]
|
|
21
|
-
if (!vary) return HttpServerResponse.setHeader(response, "vary", "Origin")
|
|
22
|
-
|
|
23
|
-
const tokens = vary.split(",").map((s) => s.trim().toLowerCase())
|
|
24
|
-
if (tokens.includes("origin") || tokens.includes("*")) return response
|
|
25
|
-
|
|
26
|
-
return HttpServerResponse.setHeader(response, "vary", `${vary}, Origin`)
|
|
27
|
-
}),
|
|
28
|
-
{ global: true },
|
|
29
|
-
)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { NamedError } from "@soraacode/core/util/error"
|
|
2
|
-
import { ConfigErrorV1 } from "@soraacode/core/v1/config/error"
|
|
3
|
-
import { Cause, Effect } from "effect"
|
|
4
|
-
import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
|
|
5
|
-
|
|
6
|
-
// Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
|
|
7
|
-
export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
|
|
8
|
-
effect.pipe(
|
|
9
|
-
Effect.catchCause((cause) => {
|
|
10
|
-
const defect = cause.reasons.filter(Cause.isDieReason).find((reason) => {
|
|
11
|
-
if (HttpServerResponse.isHttpServerResponse(reason.defect)) return false
|
|
12
|
-
if (HttpServerError.isHttpServerError(reason.defect)) return false
|
|
13
|
-
if (HttpServerRespondable.isRespondable(reason.defect)) return false
|
|
14
|
-
return true
|
|
15
|
-
})
|
|
16
|
-
if (!defect) return Effect.failCause(cause)
|
|
17
|
-
|
|
18
|
-
const error = defect.defect
|
|
19
|
-
if (
|
|
20
|
-
ConfigErrorV1.JsonError.isInstance(error) ||
|
|
21
|
-
ConfigErrorV1.InvalidError.isInstance(error) ||
|
|
22
|
-
ConfigErrorV1.FrontmatterError.isInstance(error) ||
|
|
23
|
-
ConfigErrorV1.DirectoryTypoError.isInstance(error) ||
|
|
24
|
-
ConfigErrorV1.RemoteAuthError.isInstance(error)
|
|
25
|
-
) {
|
|
26
|
-
return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 400 }))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
|
30
|
-
|
|
31
|
-
return Effect.logError("failed", { ref, error, cause: Cause.pretty(cause) }).pipe(
|
|
32
|
-
Effect.as(
|
|
33
|
-
HttpServerResponse.jsonUnsafe(
|
|
34
|
-
new NamedError.Unknown({
|
|
35
|
-
message: "Unexpected server error. Check server logs for details.",
|
|
36
|
-
ref,
|
|
37
|
-
}).toObject(),
|
|
38
|
-
{ status: 500 },
|
|
39
|
-
),
|
|
40
|
-
),
|
|
41
|
-
)
|
|
42
|
-
}),
|
|
43
|
-
),
|
|
44
|
-
).layer
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Flag } from "@soraacode/core/flag/flag"
|
|
2
|
-
import { Database } from "@soraacode/core/database/database"
|
|
3
|
-
import { Effect } from "effect"
|
|
4
|
-
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
|
5
|
-
import * as Fence from "@/server/shared/fence"
|
|
6
|
-
|
|
7
|
-
const ignoredMethods = new Set(["GET", "HEAD", "OPTIONS"])
|
|
8
|
-
|
|
9
|
-
export const fenceLayer = HttpRouter.middleware<{ requires: Database.Service; handles: unknown }>()(
|
|
10
|
-
Effect.gen(function* () {
|
|
11
|
-
const { db } = yield* Database.Service
|
|
12
|
-
return (effect) =>
|
|
13
|
-
Effect.gen(function* () {
|
|
14
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
15
|
-
if (!Flag.OPENCODE_WORKSPACE_ID || ignoredMethods.has(request.method)) return yield* effect
|
|
16
|
-
|
|
17
|
-
const previous = yield* Fence.load(db)
|
|
18
|
-
const response = yield* effect
|
|
19
|
-
const current = Fence.diff(previous, yield* Fence.load(db))
|
|
20
|
-
if (Object.keys(current).length === 0) return response
|
|
21
|
-
|
|
22
|
-
return HttpServerResponse.setHeader(response, Fence.HEADER, JSON.stringify(current))
|
|
23
|
-
})
|
|
24
|
-
}),
|
|
25
|
-
).layer
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
|
|
2
|
-
import { InstanceStore } from "@/project/instance-store"
|
|
3
|
-
import { Effect, Layer } from "effect"
|
|
4
|
-
import { HttpServerResponse } from "effect/unstable/http"
|
|
5
|
-
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
|
6
|
-
import { WorkspaceRouteContext } from "./workspace-routing"
|
|
7
|
-
|
|
8
|
-
export class InstanceContextMiddleware extends HttpApiMiddleware.Service<
|
|
9
|
-
InstanceContextMiddleware,
|
|
10
|
-
{
|
|
11
|
-
requires: WorkspaceRouteContext
|
|
12
|
-
}
|
|
13
|
-
>()("@opencode/ExperimentalHttpApiInstanceContext") {}
|
|
14
|
-
|
|
15
|
-
function decode(input: string): string {
|
|
16
|
-
try {
|
|
17
|
-
return decodeURIComponent(input)
|
|
18
|
-
} catch {
|
|
19
|
-
return input
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function provideInstanceContext<E>(
|
|
24
|
-
effect: Effect.Effect<HttpServerResponse.HttpServerResponse, E>,
|
|
25
|
-
store: InstanceStore.Interface,
|
|
26
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse, E, WorkspaceRouteContext> {
|
|
27
|
-
return Effect.gen(function* () {
|
|
28
|
-
const route = yield* WorkspaceRouteContext
|
|
29
|
-
const ctx = yield* store.load({ directory: decode(route.directory) })
|
|
30
|
-
return yield* effect.pipe(
|
|
31
|
-
Effect.provideService(InstanceRef, ctx),
|
|
32
|
-
Effect.provideService(WorkspaceRef, route.workspaceID),
|
|
33
|
-
)
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const instanceContextLayer = Layer.effect(
|
|
38
|
-
InstanceContextMiddleware,
|
|
39
|
-
Effect.gen(function* () {
|
|
40
|
-
const store = yield* InstanceStore.Service
|
|
41
|
-
return InstanceContextMiddleware.of((effect) => provideInstanceContext(effect, store))
|
|
42
|
-
}),
|
|
43
|
-
)
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { ProxyUtil } from "@/server/proxy-util"
|
|
2
|
-
import { Effect, Stream } from "effect"
|
|
3
|
-
import { HttpBody, HttpClient, HttpClientRequest, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
|
4
|
-
import * as Socket from "effect/unstable/socket/Socket"
|
|
5
|
-
import { WebSocketTracker } from "../websocket-tracker"
|
|
6
|
-
|
|
7
|
-
function requestBody(request: HttpServerRequest.HttpServerRequest) {
|
|
8
|
-
if (request.method === "GET" || request.method === "HEAD") return HttpBody.empty
|
|
9
|
-
if (request.source instanceof Request && request.source.body === null) return HttpBody.empty
|
|
10
|
-
const len = request.headers["content-length"]
|
|
11
|
-
return HttpBody.stream(request.stream, request.headers["content-type"], len ? Number(len) : undefined)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function websocket(
|
|
15
|
-
request: HttpServerRequest.HttpServerRequest,
|
|
16
|
-
target: string | URL,
|
|
17
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse, never, Socket.WebSocketConstructor> {
|
|
18
|
-
return Effect.scoped(
|
|
19
|
-
Effect.gen(function* () {
|
|
20
|
-
const inbound = yield* Effect.orDie(request.upgrade)
|
|
21
|
-
const outbound = yield* Socket.makeWebSocket(ProxyUtil.websocketTargetURL(target), {
|
|
22
|
-
protocols: ProxyUtil.websocketProtocols(request.headers),
|
|
23
|
-
})
|
|
24
|
-
const writeInbound = yield* inbound.writer
|
|
25
|
-
const writeOutbound = yield* outbound.writer
|
|
26
|
-
const closeSocket = (socket: Socket.Socket, write: (event: Socket.CloseEvent) => Effect.Effect<void, unknown>) =>
|
|
27
|
-
socket
|
|
28
|
-
.runRaw(() => Effect.void, {
|
|
29
|
-
onOpen: write(WebSocketTracker.SERVER_CLOSING_EVENT()).pipe(Effect.catch(() => Effect.void)),
|
|
30
|
-
})
|
|
31
|
-
.pipe(
|
|
32
|
-
Effect.timeout("1 second"),
|
|
33
|
-
Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void),
|
|
34
|
-
Effect.catch(() => Effect.void),
|
|
35
|
-
)
|
|
36
|
-
const closeAccepted = Effect.all([closeSocket(inbound, writeInbound), closeSocket(outbound, writeOutbound)], {
|
|
37
|
-
concurrency: "unbounded",
|
|
38
|
-
discard: true,
|
|
39
|
-
})
|
|
40
|
-
const registered = yield* WebSocketTracker.register(
|
|
41
|
-
Effect.all(
|
|
42
|
-
[
|
|
43
|
-
writeInbound(WebSocketTracker.SERVER_CLOSING_EVENT()),
|
|
44
|
-
writeOutbound(WebSocketTracker.SERVER_CLOSING_EVENT()),
|
|
45
|
-
],
|
|
46
|
-
{ concurrency: "unbounded", discard: true },
|
|
47
|
-
),
|
|
48
|
-
)
|
|
49
|
-
if (!registered) {
|
|
50
|
-
yield* closeAccepted
|
|
51
|
-
return HttpServerResponse.empty()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
yield* outbound
|
|
55
|
-
.runRaw((message) => writeInbound(message))
|
|
56
|
-
.pipe(
|
|
57
|
-
Effect.catchReason("SocketError", "SocketCloseError", (reason) =>
|
|
58
|
-
writeInbound(new Socket.CloseEvent(reason.code, reason.closeReason)).pipe(Effect.catch(() => Effect.void)),
|
|
59
|
-
),
|
|
60
|
-
Effect.catch(() =>
|
|
61
|
-
writeInbound(new Socket.CloseEvent(1011, "proxy error")).pipe(Effect.catch(() => Effect.void)),
|
|
62
|
-
),
|
|
63
|
-
Effect.forkScoped,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
yield* inbound
|
|
67
|
-
.runRaw((message) => {
|
|
68
|
-
return writeOutbound(typeof message === "string" ? message : message.slice())
|
|
69
|
-
})
|
|
70
|
-
.pipe(
|
|
71
|
-
Effect.catch(() => Effect.void),
|
|
72
|
-
Effect.ensuring(writeOutbound(new Socket.CloseEvent()).pipe(Effect.catch(() => Effect.void))),
|
|
73
|
-
)
|
|
74
|
-
return HttpServerResponse.empty()
|
|
75
|
-
}).pipe(Effect.orDie),
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function statusText(response: unknown) {
|
|
80
|
-
return (response as { source?: Response }).source?.statusText
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function http(
|
|
84
|
-
client: HttpClient.HttpClient,
|
|
85
|
-
url: string | URL,
|
|
86
|
-
extra: HeadersInit | undefined,
|
|
87
|
-
request: HttpServerRequest.HttpServerRequest,
|
|
88
|
-
): Effect.Effect<HttpServerResponse.HttpServerResponse> {
|
|
89
|
-
return Effect.gen(function* () {
|
|
90
|
-
const response = yield* client.execute(
|
|
91
|
-
HttpClientRequest.make(request.method as never)(url, {
|
|
92
|
-
headers: ProxyUtil.headers(request.headers as HeadersInit, extra),
|
|
93
|
-
body: requestBody(request),
|
|
94
|
-
}),
|
|
95
|
-
)
|
|
96
|
-
const headers = new Headers(response.headers as HeadersInit)
|
|
97
|
-
headers.delete("content-encoding")
|
|
98
|
-
headers.delete("content-length")
|
|
99
|
-
|
|
100
|
-
// An upstream 5xx from a remote workspace sandbox arrives here as an opaque
|
|
101
|
-
// status — its real cause (and log line) live only inside the sandbox. Buffer
|
|
102
|
-
// the small error body, log it locally so it shows up in the host's log, and
|
|
103
|
-
// forward it unchanged (preserving content-type so the client can still parse
|
|
104
|
-
// the structured error, e.g. its `ref`).
|
|
105
|
-
if (response.status >= 500) {
|
|
106
|
-
const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed("")))
|
|
107
|
-
const contentType = response.headers["content-type"] ?? "application/json"
|
|
108
|
-
headers.delete("content-type")
|
|
109
|
-
yield* Effect.logError("workspace proxy upstream error", {
|
|
110
|
-
url: url.toString(),
|
|
111
|
-
method: request.method,
|
|
112
|
-
status: response.status,
|
|
113
|
-
body: body.slice(0, 2000),
|
|
114
|
-
})
|
|
115
|
-
return HttpServerResponse.text(body, {
|
|
116
|
-
status: response.status,
|
|
117
|
-
statusText: statusText(response),
|
|
118
|
-
headers,
|
|
119
|
-
contentType,
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return HttpServerResponse.stream(response.stream.pipe(Stream.catchCause(() => Stream.empty)), {
|
|
124
|
-
status: response.status,
|
|
125
|
-
statusText: statusText(response),
|
|
126
|
-
headers,
|
|
127
|
-
})
|
|
128
|
-
}).pipe(Effect.catch(() => Effect.succeed(HttpServerResponse.empty({ status: 500 }))))
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export * as HttpApiProxy from "./proxy"
|