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
package/src/session/reminders.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import path from "path"
|
|
2
|
-
import { SessionV1 } from "@soraacode/core/v1/session"
|
|
3
|
-
import { Effect } from "effect"
|
|
4
|
-
import { Agent } from "@/agent/agent"
|
|
5
|
-
import { FSUtil } from "@soraacode/core/fs-util"
|
|
6
|
-
import { InstanceState } from "@/effect/instance-state"
|
|
7
|
-
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
8
|
-
import { PartID } from "./schema"
|
|
9
|
-
import { MessageV2 } from "./message-v2"
|
|
10
|
-
import { Session } from "./session"
|
|
11
|
-
import PROMPT_PLAN from "./prompt/plan.txt"
|
|
12
|
-
import BUILD_SWITCH from "./prompt/build-switch.txt"
|
|
13
|
-
import PLAN_MODE from "./prompt/plan-mode.txt"
|
|
14
|
-
|
|
15
|
-
export const apply = Effect.fn("SessionReminders.apply")(function* (input: {
|
|
16
|
-
messages: SessionV1.WithParts[]
|
|
17
|
-
agent: Agent.Info
|
|
18
|
-
session: Session.Info
|
|
19
|
-
}) {
|
|
20
|
-
const flags = yield* RuntimeFlags.Service
|
|
21
|
-
const fsys = yield* FSUtil.Service
|
|
22
|
-
const sessions = yield* Session.Service
|
|
23
|
-
const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
|
|
24
|
-
if (!userMessage) return input.messages
|
|
25
|
-
|
|
26
|
-
if (!flags.experimentalPlanMode) {
|
|
27
|
-
if (input.agent.name === "plan") {
|
|
28
|
-
userMessage.parts.push({
|
|
29
|
-
id: PartID.ascending(),
|
|
30
|
-
messageID: userMessage.info.id,
|
|
31
|
-
sessionID: userMessage.info.sessionID,
|
|
32
|
-
type: "text",
|
|
33
|
-
text: PROMPT_PLAN,
|
|
34
|
-
synthetic: true,
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
|
|
38
|
-
if (wasPlan && input.agent.name === "build") {
|
|
39
|
-
userMessage.parts.push({
|
|
40
|
-
id: PartID.ascending(),
|
|
41
|
-
messageID: userMessage.info.id,
|
|
42
|
-
sessionID: userMessage.info.sessionID,
|
|
43
|
-
type: "text",
|
|
44
|
-
text: BUILD_SWITCH,
|
|
45
|
-
synthetic: true,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
return input.messages
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
|
|
52
|
-
if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
|
|
53
|
-
const ctx = yield* InstanceState.context
|
|
54
|
-
const plan = Session.plan(input.session, ctx)
|
|
55
|
-
const exists = yield* fsys.existsSafe(plan)
|
|
56
|
-
const part = yield* sessions.updatePart({
|
|
57
|
-
id: PartID.ascending(),
|
|
58
|
-
messageID: userMessage.info.id,
|
|
59
|
-
sessionID: userMessage.info.sessionID,
|
|
60
|
-
type: "text",
|
|
61
|
-
text: exists
|
|
62
|
-
? `${BUILD_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`
|
|
63
|
-
: BUILD_SWITCH,
|
|
64
|
-
synthetic: true,
|
|
65
|
-
})
|
|
66
|
-
userMessage.parts.push(part)
|
|
67
|
-
return input.messages
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (input.agent.name !== "plan" || assistantMessage?.info.agent === "plan") return input.messages
|
|
71
|
-
|
|
72
|
-
const ctx = yield* InstanceState.context
|
|
73
|
-
const plan = Session.plan(input.session, ctx)
|
|
74
|
-
const exists = yield* fsys.existsSafe(plan)
|
|
75
|
-
if (!exists) yield* fsys.ensureDir(path.dirname(plan)).pipe(Effect.catch(Effect.die))
|
|
76
|
-
const part = yield* sessions.updatePart({
|
|
77
|
-
id: PartID.ascending(),
|
|
78
|
-
messageID: userMessage.info.id,
|
|
79
|
-
sessionID: userMessage.info.sessionID,
|
|
80
|
-
type: "text",
|
|
81
|
-
text: PLAN_MODE.replace("${planInfo}", () =>
|
|
82
|
-
exists
|
|
83
|
-
? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.`
|
|
84
|
-
: `No plan file exists yet. You should create your plan at ${plan} using the write tool.`,
|
|
85
|
-
),
|
|
86
|
-
synthetic: true,
|
|
87
|
-
})
|
|
88
|
-
userMessage.parts.push(part)
|
|
89
|
-
return input.messages
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
export * as SessionReminders from "./reminders"
|
package/src/session/retry.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import type { NamedError } from "@soraacode/core/util/error"
|
|
2
|
-
import { SessionV1 } from "@soraacode/core/v1/session"
|
|
3
|
-
import { Cause, Clock, Duration, Effect, Schedule } from "effect"
|
|
4
|
-
import { MessageV2 } from "./message-v2"
|
|
5
|
-
import { iife } from "@/util/iife"
|
|
6
|
-
import { isRecord } from "@/util/record"
|
|
7
|
-
|
|
8
|
-
export type Err = ReturnType<NamedError["toObject"]>
|
|
9
|
-
|
|
10
|
-
export const GO_UPSELL_MESSAGE = "Free usage exceeded, subscribe to Go"
|
|
11
|
-
export const GO_UPSELL_URL = "https://opencode.ai/go"
|
|
12
|
-
export type RetryReason = "free_tier_limit" | "account_rate_limit" | (string & {})
|
|
13
|
-
|
|
14
|
-
export type Retryable = {
|
|
15
|
-
message: string
|
|
16
|
-
action?: {
|
|
17
|
-
reason: RetryReason
|
|
18
|
-
provider: string
|
|
19
|
-
title: string
|
|
20
|
-
message: string
|
|
21
|
-
label: string
|
|
22
|
-
link?: string
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const RETRY_INITIAL_DELAY = 2000
|
|
27
|
-
export const RETRY_BACKOFF_FACTOR = 2
|
|
28
|
-
export const RETRY_JITTER_FACTOR = 0.25
|
|
29
|
-
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
|
|
30
|
-
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
|
|
31
|
-
export const RETRY_MAX_RETRIES = 5
|
|
32
|
-
|
|
33
|
-
const RETRYABLE_MESSAGE_PATTERNS = [
|
|
34
|
-
/429|500|502|503|504|524/i,
|
|
35
|
-
/rate increased too quickly|rate limit|rate-limit|rate_limit|too many requests/i,
|
|
36
|
-
/overloaded|service unavailable|service_unavailable|service-unavailable|internal error|internal_error|internal server error|server error|server_error|server-error|provider returned error|provider_returned_error|provider-returned-error/i,
|
|
37
|
-
/terminated|fetch failed|failed to fetch|network[-_\s]error|upstream connect|connection error|connection refused|connection lost|socket connection was closed|socket hang up|reset before headers|getaddrinfo|enotfound|eai_again|econnrefused|econnreset|etimedout/i,
|
|
38
|
-
/^timeout$|\b(?:request|response|connection|network|stream|read) (?:timeout|timed out|time out)\b/i,
|
|
39
|
-
/try your request again|retry your request|resource exhausted|resource_exhausted/i,
|
|
40
|
-
/\btry again (?:later|in\b)|\b(?:currently|temporarily) at capacity\b/i,
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
function cap(ms: number) {
|
|
44
|
-
return Math.min(ms, RETRY_MAX_DELAY)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function delay(attempt: number, error?: SessionV1.APIError, random = Math.random()) {
|
|
48
|
-
if (error) {
|
|
49
|
-
const headers = error.data.responseHeaders
|
|
50
|
-
if (headers) {
|
|
51
|
-
const retryAfterMs = headers["retry-after-ms"]
|
|
52
|
-
if (retryAfterMs) {
|
|
53
|
-
const parsedMs = Number.parseFloat(retryAfterMs)
|
|
54
|
-
if (!Number.isNaN(parsedMs)) {
|
|
55
|
-
return cap(parsedMs)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const retryAfter = headers["retry-after"]
|
|
60
|
-
if (retryAfter) {
|
|
61
|
-
const parsedSeconds = Number.parseFloat(retryAfter)
|
|
62
|
-
if (!Number.isNaN(parsedSeconds)) {
|
|
63
|
-
// convert seconds to milliseconds
|
|
64
|
-
return cap(Math.ceil(parsedSeconds * 1000))
|
|
65
|
-
}
|
|
66
|
-
// Try parsing as HTTP date format
|
|
67
|
-
const parsed = Date.parse(retryAfter) - Date.now()
|
|
68
|
-
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
69
|
-
return cap(Math.ceil(parsed))
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return cap(exponential(attempt, random))
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return cap(Math.min(exponential(attempt, random), RETRY_MAX_DELAY_NO_HEADERS))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function exponential(attempt: number, random: number) {
|
|
81
|
-
const base = RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
|
|
82
|
-
return Math.ceil(base + base * RETRY_JITTER_FACTOR * random)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function retryable(error: Err, provider: string) {
|
|
86
|
-
// context overflow errors should not be retried
|
|
87
|
-
if (SessionV1.ContextOverflowError.isInstance(error)) return undefined
|
|
88
|
-
if (SessionV1.APIError.isInstance(error)) {
|
|
89
|
-
const status = error.data.statusCode
|
|
90
|
-
// 5xx errors are transient server failures and should always be retried,
|
|
91
|
-
// even when the provider SDK doesn't explicitly mark them as retryable.
|
|
92
|
-
if (
|
|
93
|
-
!error.data.isRetryable &&
|
|
94
|
-
!(status !== undefined && status >= 500) &&
|
|
95
|
-
!matchesRetryableMessage(error.data.message) &&
|
|
96
|
-
!matchesRetryableMessage(error.data.responseBody)
|
|
97
|
-
)
|
|
98
|
-
return undefined
|
|
99
|
-
if (error.data.responseBody?.includes("FreeUsageLimitError")) {
|
|
100
|
-
return {
|
|
101
|
-
message: GO_UPSELL_MESSAGE,
|
|
102
|
-
action: {
|
|
103
|
-
reason: "free_tier_limit",
|
|
104
|
-
provider,
|
|
105
|
-
title: "Free limit reached",
|
|
106
|
-
message: "Subscribe to OpenCode Go for reliable access to the best open-source models for $10/month.",
|
|
107
|
-
label: "subscribe",
|
|
108
|
-
link: GO_UPSELL_URL,
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (error.data.responseBody?.includes("GoUsageLimitError")) {
|
|
113
|
-
const body = parseJSON(error.data.responseBody)
|
|
114
|
-
const workspace = str(body?.metadata?.workspace)
|
|
115
|
-
const limitName = str(body?.metadata?.limitName)
|
|
116
|
-
const retryAfter = num(error.data.responseHeaders?.["retry-after"])
|
|
117
|
-
const resetIn = iife(() => {
|
|
118
|
-
if (retryAfter === undefined) return ""
|
|
119
|
-
const seconds = Math.max(0, Math.ceil(retryAfter))
|
|
120
|
-
const days = Math.floor(seconds / 86_400)
|
|
121
|
-
const hours = Math.floor((seconds % 86_400) / 3_600)
|
|
122
|
-
const minutes = Math.ceil((seconds % 3_600) / 60)
|
|
123
|
-
const unit = (value: number, name: string) => `${value} ${name}${value === 1 ? "" : "s"}`
|
|
124
|
-
|
|
125
|
-
if (days > 0) return hours > 0 ? `${unit(days, "day")} ${unit(hours, "hour")}` : unit(days, "day")
|
|
126
|
-
if (hours > 0) return minutes > 0 ? `${unit(hours, "hour")} ${unit(minutes, "minute")}` : unit(hours, "hour")
|
|
127
|
-
return minutes > 0 ? unit(minutes, "minute") : "less than a minute"
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const message = `${limitName ? `${limitName} usage limit` : "Usage limit"} reached. It will reset in ${resetIn}. To continue using this model now, enable usage from your available balance`
|
|
131
|
-
|
|
132
|
-
const link = `https://opencode.ai/workspace/${workspace}/go`
|
|
133
|
-
return {
|
|
134
|
-
message: `${message} - ${link}`,
|
|
135
|
-
action: {
|
|
136
|
-
reason: "account_rate_limit",
|
|
137
|
-
provider,
|
|
138
|
-
title: "Go limit reached",
|
|
139
|
-
message,
|
|
140
|
-
label: "open settings",
|
|
141
|
-
link,
|
|
142
|
-
},
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return { message: error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const message = isRecord(error.data) ? error.data.message : undefined
|
|
149
|
-
if (typeof message !== "string") return undefined
|
|
150
|
-
const lower = message.toLowerCase()
|
|
151
|
-
if (lower.includes("too_many_requests")) return { message: "Too Many Requests" }
|
|
152
|
-
if (lower.includes("exhausted") || lower.includes("unavailable")) return { message: "Provider is overloaded" }
|
|
153
|
-
if (matchesRetryableMessage(message)) return { message }
|
|
154
|
-
return undefined
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function matchesRetryableMessage(value: unknown) {
|
|
158
|
-
return typeof value === "string" && RETRYABLE_MESSAGE_PATTERNS.some((pattern) => pattern.test(value))
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function str(value: unknown) {
|
|
162
|
-
if (value === undefined || value === null) return ""
|
|
163
|
-
return String(value)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function num(value: unknown) {
|
|
167
|
-
const parsed = Number.parseFloat(str(value))
|
|
168
|
-
if (Number.isNaN(parsed)) return undefined
|
|
169
|
-
return parsed
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function parseJSON(value: unknown) {
|
|
173
|
-
return iife(() => {
|
|
174
|
-
try {
|
|
175
|
-
if (typeof value !== "string") return undefined
|
|
176
|
-
return JSON.parse(value)
|
|
177
|
-
} catch {
|
|
178
|
-
return undefined
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function policy(opts: {
|
|
184
|
-
provider: string
|
|
185
|
-
parse: (error: unknown) => Err
|
|
186
|
-
set: (input: { attempt: number; message: string; action?: Retryable["action"]; next: number }) => Effect.Effect<void>
|
|
187
|
-
}) {
|
|
188
|
-
return Schedule.fromStepWithMetadata(
|
|
189
|
-
Effect.succeed((meta: Schedule.InputMetadata<unknown>) => {
|
|
190
|
-
const error = opts.parse(meta.input)
|
|
191
|
-
const retry = retryable(error, opts.provider)
|
|
192
|
-
if (!retry) return Cause.done(meta.attempt)
|
|
193
|
-
if (meta.attempt > RETRY_MAX_RETRIES) return Cause.done(meta.attempt)
|
|
194
|
-
return Effect.gen(function* () {
|
|
195
|
-
const wait = delay(meta.attempt, SessionV1.APIError.isInstance(error) ? error : undefined)
|
|
196
|
-
const now = yield* Clock.currentTimeMillis
|
|
197
|
-
yield* opts.set({
|
|
198
|
-
attempt: meta.attempt,
|
|
199
|
-
message: retry.message,
|
|
200
|
-
action: retry.action,
|
|
201
|
-
next: now + wait,
|
|
202
|
-
})
|
|
203
|
-
return [meta.attempt, Duration.millis(wait)] as [number, Duration.Duration]
|
|
204
|
-
})
|
|
205
|
-
}),
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export * as SessionRetry from "./retry"
|
package/src/session/revert.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { LayerNode } from "@soraacode/core/effect/layer-node"
|
|
2
|
-
import { Effect, Layer, Context, Schema } from "effect"
|
|
3
|
-
import { SessionV1 } from "@soraacode/core/v1/session"
|
|
4
|
-
import { EventV2Bridge } from "@/event-v2-bridge"
|
|
5
|
-
import { Snapshot } from "../snapshot"
|
|
6
|
-
import { Storage } from "@/storage/storage"
|
|
7
|
-
import { Session } from "./session"
|
|
8
|
-
import { MessageV2 } from "./message-v2"
|
|
9
|
-
import { SessionID, MessageID, PartID } from "./schema"
|
|
10
|
-
import { SessionRunState } from "./run-state"
|
|
11
|
-
import { SessionSummary } from "./summary"
|
|
12
|
-
|
|
13
|
-
export const RevertInput = Schema.Struct({
|
|
14
|
-
sessionID: SessionID,
|
|
15
|
-
messageID: MessageID,
|
|
16
|
-
partID: Schema.optional(PartID),
|
|
17
|
-
})
|
|
18
|
-
export type RevertInput = Schema.Schema.Type<typeof RevertInput>
|
|
19
|
-
|
|
20
|
-
export interface Interface {
|
|
21
|
-
readonly revert: (input: RevertInput) => Effect.Effect<Session.Info, Session.BusyError>
|
|
22
|
-
readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect<Session.Info, Session.BusyError>
|
|
23
|
-
readonly cleanup: (session: Session.Info) => Effect.Effect<void>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRevert") {}
|
|
27
|
-
|
|
28
|
-
const layer = Layer.effect(
|
|
29
|
-
Service,
|
|
30
|
-
Effect.gen(function* () {
|
|
31
|
-
const sessions = yield* Session.Service
|
|
32
|
-
const snap = yield* Snapshot.Service
|
|
33
|
-
const storage = yield* Storage.Service
|
|
34
|
-
const events = yield* EventV2Bridge.Service
|
|
35
|
-
const summary = yield* SessionSummary.Service
|
|
36
|
-
const state = yield* SessionRunState.Service
|
|
37
|
-
|
|
38
|
-
const revert = Effect.fn("SessionRevert.revert")(function* (input: RevertInput) {
|
|
39
|
-
yield* state.assertNotBusy(input.sessionID)
|
|
40
|
-
const all = yield* sessions.messages({ sessionID: input.sessionID }).pipe(Effect.orDie)
|
|
41
|
-
let lastUser: SessionV1.User | undefined
|
|
42
|
-
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
|
43
|
-
|
|
44
|
-
let rev: Session.Info["revert"]
|
|
45
|
-
const patches: Snapshot.Patch[] = []
|
|
46
|
-
for (const msg of all) {
|
|
47
|
-
if (msg.info.role === "user") lastUser = msg.info
|
|
48
|
-
const remaining = []
|
|
49
|
-
for (const part of msg.parts) {
|
|
50
|
-
if (rev) {
|
|
51
|
-
if (part.type === "patch") patches.push(part)
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!rev) {
|
|
56
|
-
if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
|
|
57
|
-
const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
|
|
58
|
-
rev = {
|
|
59
|
-
messageID: !partID && lastUser ? lastUser.id : msg.info.id,
|
|
60
|
-
partID,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
remaining.push(part)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!rev) return session
|
|
69
|
-
|
|
70
|
-
rev.snapshot = session.revert?.snapshot ?? (yield* snap.track())
|
|
71
|
-
if (session.revert?.snapshot) yield* snap.restore(session.revert.snapshot)
|
|
72
|
-
yield* snap.revert(patches)
|
|
73
|
-
if (rev.snapshot) rev.diff = yield* snap.diff(rev.snapshot)
|
|
74
|
-
const index = all.findIndex((msg) => msg.info.id === rev.messageID)
|
|
75
|
-
const range = index < 0 ? [] : all.slice(index)
|
|
76
|
-
const diffs = yield* summary.computeDiff({ messages: range })
|
|
77
|
-
yield* storage.write(["session_diff", input.sessionID], diffs).pipe(Effect.ignore)
|
|
78
|
-
yield* events.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs })
|
|
79
|
-
yield* sessions.setRevert({
|
|
80
|
-
sessionID: input.sessionID,
|
|
81
|
-
revert: rev,
|
|
82
|
-
summary: {
|
|
83
|
-
additions: diffs.reduce((sum, x) => sum + x.additions, 0),
|
|
84
|
-
deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
|
|
85
|
-
files: diffs.length,
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
return yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const unrevert = Effect.fn("SessionRevert.unrevert")(function* (input: { sessionID: SessionID }) {
|
|
92
|
-
yield* Effect.logInfo("unreverting", { sessionID: input.sessionID })
|
|
93
|
-
yield* state.assertNotBusy(input.sessionID)
|
|
94
|
-
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
|
95
|
-
if (!session.revert) return session
|
|
96
|
-
if (session.revert.snapshot) yield* snap.restore(session.revert.snapshot)
|
|
97
|
-
yield* sessions.clearRevert(input.sessionID)
|
|
98
|
-
return yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const cleanup = Effect.fn("SessionRevert.cleanup")(function* (session: Session.Info) {
|
|
102
|
-
if (!session.revert) return
|
|
103
|
-
const sessionID = session.id
|
|
104
|
-
const msgs = yield* sessions.messages({ sessionID }).pipe(Effect.orDie)
|
|
105
|
-
const messageID = session.revert.messageID
|
|
106
|
-
const index = msgs.findIndex((msg) => msg.info.id === messageID)
|
|
107
|
-
const target = index < 0 ? undefined : msgs[index]
|
|
108
|
-
const remove = index < 0 ? [] : msgs.slice(index + (session.revert.partID ? 1 : 0))
|
|
109
|
-
for (const msg of remove) {
|
|
110
|
-
yield* sessions.removeMessage({ sessionID, messageID: msg.info.id })
|
|
111
|
-
}
|
|
112
|
-
if (session.revert.partID && target) {
|
|
113
|
-
const partID = session.revert.partID
|
|
114
|
-
const idx = target.parts.findIndex((part) => part.id === partID)
|
|
115
|
-
if (idx >= 0) {
|
|
116
|
-
const removeParts = target.parts.slice(idx)
|
|
117
|
-
target.parts = target.parts.slice(0, idx)
|
|
118
|
-
for (const part of removeParts) {
|
|
119
|
-
yield* sessions.removePart({ sessionID, messageID: target.info.id, partID: part.id })
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
yield* sessions.clearRevert(sessionID)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
return Service.of({ revert, unrevert, cleanup })
|
|
127
|
-
}),
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
export const node = LayerNode.make({
|
|
131
|
-
service: Service,
|
|
132
|
-
layer: layer,
|
|
133
|
-
deps: [Session.node, Snapshot.node, Storage.node, EventV2Bridge.node, SessionSummary.node, SessionRunState.node],
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
export * as SessionRevert from "./revert"
|
package/src/session/run-state.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { LayerNode } from "@soraacode/core/effect/layer-node"
|
|
2
|
-
import { InstanceState } from "@/effect/instance-state"
|
|
3
|
-
import { SessionV1 } from "@soraacode/core/v1/session"
|
|
4
|
-
import { Runner } from "@/effect/runner"
|
|
5
|
-
import { BackgroundJob } from "@/background/job"
|
|
6
|
-
import { Effect, Latch, Layer, Scope, Context } from "effect"
|
|
7
|
-
import { Session } from "./session"
|
|
8
|
-
import { SessionID } from "./schema"
|
|
9
|
-
import { SessionStatus } from "./status"
|
|
10
|
-
|
|
11
|
-
export interface Interface {
|
|
12
|
-
readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect<void, Session.BusyError>
|
|
13
|
-
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
|
14
|
-
readonly ensureRunning: (
|
|
15
|
-
sessionID: SessionID,
|
|
16
|
-
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
|
17
|
-
work: Effect.Effect<SessionV1.WithParts>,
|
|
18
|
-
) => Effect.Effect<SessionV1.WithParts>
|
|
19
|
-
readonly startShell: (
|
|
20
|
-
sessionID: SessionID,
|
|
21
|
-
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
|
22
|
-
work: Effect.Effect<SessionV1.WithParts>,
|
|
23
|
-
ready?: Latch.Latch,
|
|
24
|
-
) => Effect.Effect<SessionV1.WithParts, Session.BusyError>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunState") {}
|
|
28
|
-
|
|
29
|
-
const layer = Layer.effect(
|
|
30
|
-
Service,
|
|
31
|
-
Effect.gen(function* () {
|
|
32
|
-
const background = yield* BackgroundJob.Service
|
|
33
|
-
const status = yield* SessionStatus.Service
|
|
34
|
-
|
|
35
|
-
const state = yield* InstanceState.make(
|
|
36
|
-
Effect.fn("SessionRunState.state")(function* () {
|
|
37
|
-
const scope = yield* Scope.Scope
|
|
38
|
-
const runners = new Map<SessionID, Runner.Runner<SessionV1.WithParts>>()
|
|
39
|
-
yield* Effect.addFinalizer(
|
|
40
|
-
Effect.fnUntraced(function* () {
|
|
41
|
-
yield* Effect.forEach(runners.values(), (runner) => runner.cancel, {
|
|
42
|
-
concurrency: "unbounded",
|
|
43
|
-
discard: true,
|
|
44
|
-
})
|
|
45
|
-
runners.clear()
|
|
46
|
-
}),
|
|
47
|
-
)
|
|
48
|
-
return { runners, scope }
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const runner = Effect.fn("SessionRunState.runner")(function* (
|
|
53
|
-
sessionID: SessionID,
|
|
54
|
-
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
|
55
|
-
) {
|
|
56
|
-
const data = yield* InstanceState.get(state)
|
|
57
|
-
const existing = data.runners.get(sessionID)
|
|
58
|
-
if (existing) return existing
|
|
59
|
-
const next = Runner.make<SessionV1.WithParts>(data.scope, {
|
|
60
|
-
onIdle: Effect.gen(function* () {
|
|
61
|
-
data.runners.delete(sessionID)
|
|
62
|
-
yield* status.set(sessionID, { type: "idle" })
|
|
63
|
-
}),
|
|
64
|
-
onBusy: status.set(sessionID, { type: "busy" }),
|
|
65
|
-
onInterrupt,
|
|
66
|
-
})
|
|
67
|
-
data.runners.set(sessionID, next)
|
|
68
|
-
return next
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const assertNotBusy = Effect.fn("SessionRunState.assertNotBusy")(function* (sessionID: SessionID) {
|
|
72
|
-
const data = yield* InstanceState.get(state)
|
|
73
|
-
const existing = data.runners.get(sessionID)
|
|
74
|
-
if (existing?.busy) yield* busyError(sessionID)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
const cancel = Effect.fn("SessionRunState.cancel")(function* (sessionID: SessionID) {
|
|
78
|
-
yield* cancelBackgroundJobs(background, sessionID)
|
|
79
|
-
const data = yield* InstanceState.get(state)
|
|
80
|
-
const existing = data.runners.get(sessionID)
|
|
81
|
-
if (!existing) {
|
|
82
|
-
yield* status.set(sessionID, { type: "idle" })
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
yield* existing.cancel
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const ensureRunning = Effect.fn("SessionRunState.ensureRunning")(function* (
|
|
89
|
-
sessionID: SessionID,
|
|
90
|
-
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
|
91
|
-
work: Effect.Effect<SessionV1.WithParts>,
|
|
92
|
-
) {
|
|
93
|
-
return yield* (yield* runner(sessionID, onInterrupt)).ensureRunning(work)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const startShell = Effect.fn("SessionRunState.startShell")(function* (
|
|
97
|
-
sessionID: SessionID,
|
|
98
|
-
onInterrupt: Effect.Effect<SessionV1.WithParts>,
|
|
99
|
-
work: Effect.Effect<SessionV1.WithParts>,
|
|
100
|
-
ready?: Latch.Latch,
|
|
101
|
-
) {
|
|
102
|
-
return yield* (yield* runner(sessionID, onInterrupt))
|
|
103
|
-
.startShell(work, ready)
|
|
104
|
-
.pipe(Effect.catchTag("RunnerBusy", () => Effect.fail(busyError(sessionID))))
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
return Service.of({ assertNotBusy, cancel, ensureRunning, startShell })
|
|
108
|
-
}),
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
|
|
112
|
-
background: BackgroundJob.Interface,
|
|
113
|
-
sessionID: SessionID,
|
|
114
|
-
) {
|
|
115
|
-
const jobs = yield* background.list()
|
|
116
|
-
const pending = new Set<string>([sessionID])
|
|
117
|
-
const cancelled = new Set<string>()
|
|
118
|
-
const matches = (job: BackgroundJob.Info) => {
|
|
119
|
-
if (job.status !== "running") return false
|
|
120
|
-
if (cancelled.has(job.id)) return false
|
|
121
|
-
if (pending.has(job.id)) return true
|
|
122
|
-
if (typeof job.metadata?.sessionId === "string" && pending.has(job.metadata.sessionId)) return true
|
|
123
|
-
return typeof job.metadata?.parentSessionId === "string" && pending.has(job.metadata.parentSessionId)
|
|
124
|
-
}
|
|
125
|
-
let batch = jobs.filter(matches)
|
|
126
|
-
while (batch.length > 0) {
|
|
127
|
-
yield* Effect.forEach(
|
|
128
|
-
batch,
|
|
129
|
-
(job) =>
|
|
130
|
-
background.cancel(job.id).pipe(
|
|
131
|
-
Effect.tap(() =>
|
|
132
|
-
Effect.sync(() => {
|
|
133
|
-
cancelled.add(job.id)
|
|
134
|
-
pending.add(job.id)
|
|
135
|
-
if (typeof job.metadata?.sessionId === "string") pending.add(job.metadata.sessionId)
|
|
136
|
-
}),
|
|
137
|
-
),
|
|
138
|
-
),
|
|
139
|
-
{ concurrency: "unbounded", discard: true },
|
|
140
|
-
)
|
|
141
|
-
batch = jobs.filter(matches)
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
function busyError(sessionID: SessionID) {
|
|
146
|
-
return new Session.BusyError({ sessionID })
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const node = LayerNode.make({ service: Service, layer: layer, deps: [BackgroundJob.node, SessionStatus.node] })
|
|
150
|
-
|
|
151
|
-
export * as SessionRunState from "./run-state"
|
package/src/session/schema.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Schema } from "effect"
|
|
2
|
-
|
|
3
|
-
import { Identifier } from "@/id/id"
|
|
4
|
-
import { SessionV2 } from "@soraacode/core/session"
|
|
5
|
-
import { statics } from "@soraacode/core/schema"
|
|
6
|
-
|
|
7
|
-
export const SessionID = SessionV2.ID
|
|
8
|
-
export type SessionID = Schema.Schema.Type<typeof SessionID>
|
|
9
|
-
|
|
10
|
-
export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe(
|
|
11
|
-
Schema.brand("MessageID"),
|
|
12
|
-
statics((s) => ({
|
|
13
|
-
ascending: (id?: string) => s.make(Identifier.ascending("message", id)),
|
|
14
|
-
})),
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
export type MessageID = Schema.Schema.Type<typeof MessageID>
|
|
18
|
-
|
|
19
|
-
export const PartID = Schema.String.check(Schema.isStartsWith("prt")).pipe(
|
|
20
|
-
Schema.brand("PartID"),
|
|
21
|
-
statics((s) => ({
|
|
22
|
-
ascending: (id?: string) => s.make(Identifier.ascending("part", id)),
|
|
23
|
-
})),
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export type PartID = Schema.Schema.Type<typeof PartID>
|