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,136 +0,0 @@
|
|
|
1
|
-
# Message Shape
|
|
2
|
-
|
|
3
|
-
Problem:
|
|
4
|
-
|
|
5
|
-
- stored messages need enough data to replay and resume a session later
|
|
6
|
-
- prompt hooks often just want to append a synthetic user/assistant message
|
|
7
|
-
- today that means faking ids, timestamps, and request metadata
|
|
8
|
-
|
|
9
|
-
## Option 1: Two Message Shapes
|
|
10
|
-
|
|
11
|
-
Keep `User` / `Assistant` for stored history, but clean them up.
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
type User = {
|
|
15
|
-
role: "user"
|
|
16
|
-
time: { created: number }
|
|
17
|
-
request: {
|
|
18
|
-
agent: string
|
|
19
|
-
model: ModelRef
|
|
20
|
-
variant?: string
|
|
21
|
-
format?: OutputFormat
|
|
22
|
-
system?: string
|
|
23
|
-
tools?: Record<string, boolean>
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type Assistant = {
|
|
28
|
-
role: "assistant"
|
|
29
|
-
run: { agent: string; model: ModelRef; path: { cwd: string; root: string } }
|
|
30
|
-
usage: { cost: number; tokens: Tokens }
|
|
31
|
-
result: { finish?: string; error?: Error; structured?: unknown; kind: "reply" | "summary" }
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Add a separate transient `PromptMessage` for prompt surgery.
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
type PromptMessage = {
|
|
39
|
-
role: "user" | "assistant"
|
|
40
|
-
parts: PromptPart[]
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Plugin hook example:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
prompt.push({
|
|
48
|
-
role: "user",
|
|
49
|
-
parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
|
|
50
|
-
})
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Tradeoff: prompt hooks get easy lightweight messages, but there are now two message shapes.
|
|
54
|
-
|
|
55
|
-
## Option 2: Prompt Mutators
|
|
56
|
-
|
|
57
|
-
Keep `User` / `Assistant` as the stored history model.
|
|
58
|
-
|
|
59
|
-
Prompt hooks do not build messages directly. The runtime gives them prompt mutators.
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
type PromptEditor = {
|
|
63
|
-
append(input: { role: "user" | "assistant"; parts: PromptPart[] }): void
|
|
64
|
-
prepend(input: { role: "user" | "assistant"; parts: PromptPart[] }): void
|
|
65
|
-
appendTo(target: "last-user" | "last-assistant", parts: PromptPart[]): void
|
|
66
|
-
insertAfter(messageID: string, input: { role: "user" | "assistant"; parts: PromptPart[] }): void
|
|
67
|
-
insertBefore(messageID: string, input: { role: "user" | "assistant"; parts: PromptPart[] }): void
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Plugin hook examples:
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
prompt.append({
|
|
75
|
-
role: "user",
|
|
76
|
-
parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
prompt.appendTo("last-user", [{ type: "text", text: BUILD_SWITCH }])
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Tradeoff: avoids a second full message type and avoids fake ids/timestamps, but moves more magic into the hook API.
|
|
85
|
-
|
|
86
|
-
## Option 3: Separate Turn State
|
|
87
|
-
|
|
88
|
-
Move execution settings out of `User` and into a separate turn/request object.
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
type Turn = {
|
|
92
|
-
id: string
|
|
93
|
-
request: {
|
|
94
|
-
agent: string
|
|
95
|
-
model: ModelRef
|
|
96
|
-
variant?: string
|
|
97
|
-
format?: OutputFormat
|
|
98
|
-
system?: string
|
|
99
|
-
tools?: Record<string, boolean>
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
type User = {
|
|
104
|
-
role: "user"
|
|
105
|
-
turnID: string
|
|
106
|
-
time: { created: number }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type Assistant = {
|
|
110
|
-
role: "assistant"
|
|
111
|
-
turnID: string
|
|
112
|
-
usage: { cost: number; tokens: Tokens }
|
|
113
|
-
result: { finish?: string; error?: Error; structured?: unknown; kind: "reply" | "summary" }
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Examples:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
const turn = {
|
|
121
|
-
request: {
|
|
122
|
-
agent: "build",
|
|
123
|
-
model: { providerID: "openai", modelID: "gpt-5" },
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
const msg = {
|
|
130
|
-
role: "user",
|
|
131
|
-
turnID: turn.id,
|
|
132
|
-
parts: [{ type: "text", text: "Summarize the tool output above and continue." }],
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Tradeoff: stored messages get much smaller and cleaner, but replay now has to join messages with turn state and prompt hooks still need a way to pick which turn they belong to.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# TUI Notifications Default
|
|
2
|
-
|
|
3
|
-
Problem:
|
|
4
|
-
|
|
5
|
-
- v1 defaults `attention.enabled` to `false`
|
|
6
|
-
- users can opt in with `attention.enabled = true`
|
|
7
|
-
- v2 should make core TUI notifications a default behavior
|
|
8
|
-
|
|
9
|
-
## v2 Target
|
|
10
|
-
|
|
11
|
-
Flip `attention.enabled` to `true` by default in v2.
|
|
12
|
-
|
|
13
|
-
Keep `attention.enabled = false` as the explicit opt-out.
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# TUI Command Shim Removal
|
|
2
|
-
|
|
3
|
-
Problem:
|
|
4
|
-
|
|
5
|
-
- v1 keeps a deprecated `api.command` TUI plugin shim so older plugins do not fail during initialization
|
|
6
|
-
- v2 should expose only the keymap command API
|
|
7
|
-
- tests and fixtures should not encode legacy command behavior as expected behavior
|
|
8
|
-
|
|
9
|
-
## Remove Public Types
|
|
10
|
-
|
|
11
|
-
In `packages/plugin/src/tui.ts`, remove:
|
|
12
|
-
|
|
13
|
-
- `TuiCommand`
|
|
14
|
-
- `TuiCommandApi`
|
|
15
|
-
- `TuiPluginApi.command`
|
|
16
|
-
|
|
17
|
-
Keep `api.keymap` as the only TUI command registration and execution surface.
|
|
18
|
-
|
|
19
|
-
## Remove Runtime Shim
|
|
20
|
-
|
|
21
|
-
Delete `packages/opencode/src/cli/cmd/tui/plugin/command-shim.ts`.
|
|
22
|
-
|
|
23
|
-
In `packages/opencode/src/cli/cmd/tui/plugin/api.tsx`, remove:
|
|
24
|
-
|
|
25
|
-
- the `createCommandShim` import
|
|
26
|
-
- the `command: createCommandShim(...)` field from `createTuiApi(...)`
|
|
27
|
-
|
|
28
|
-
In `packages/opencode/src/cli/cmd/tui/plugin/runtime.ts`, remove:
|
|
29
|
-
|
|
30
|
-
- the `createCommandShim` import
|
|
31
|
-
- the `command: createCommandShim(...)` field from `pluginApi(...)`
|
|
32
|
-
|
|
33
|
-
## Migration Target
|
|
34
|
-
|
|
35
|
-
Plugin authors should replace old calls with keymap calls:
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
api.keymap.registerLayer({
|
|
39
|
-
commands: [
|
|
40
|
-
{
|
|
41
|
-
name: "plugin.command",
|
|
42
|
-
title: "Plugin Command",
|
|
43
|
-
namespace: "palette",
|
|
44
|
-
slashName: "plugin",
|
|
45
|
-
run() {
|
|
46
|
-
api.ui.dialog.clear()
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
bindings: [{ key: "ctrl+shift+p", cmd: "plugin.command" }],
|
|
51
|
-
})
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Direct replacements:
|
|
55
|
-
|
|
56
|
-
- `api.command.register(cb)` -> `api.keymap.registerLayer({ commands, bindings })`
|
|
57
|
-
- `api.command.trigger(name)` -> `api.keymap.dispatchCommand(name)`
|
|
58
|
-
- `api.command.show()` -> `api.keymap.dispatchCommand("command.palette.show")`
|
|
59
|
-
- `onSelect(dialog)` -> use `api.ui.dialog` from the plugin API closure
|
|
60
|
-
|
|
61
|
-
## Verification
|
|
62
|
-
|
|
63
|
-
After removal, run from package directories:
|
|
64
|
-
|
|
65
|
-
- `bun typecheck` in `packages/plugin`
|
|
66
|
-
- `bun typecheck` in `packages/opencode`
|
|
67
|
-
- TUI plugin loader tests in `packages/opencode` if runtime plugin API wiring changed
|
package/src/account/account.ts
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
import { LayerNode } from "@soraacode/core/effect/layer-node"
|
|
2
|
-
import { httpClient } from "@soraacode/core/effect/app-node-platform"
|
|
3
|
-
import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
|
|
4
|
-
import { serviceUse } from "@soraacode/core/effect/service-use"
|
|
5
|
-
import {
|
|
6
|
-
FetchHttpClient,
|
|
7
|
-
HttpClient,
|
|
8
|
-
HttpClientError,
|
|
9
|
-
HttpClientRequest,
|
|
10
|
-
HttpClientResponse,
|
|
11
|
-
} from "effect/unstable/http"
|
|
12
|
-
|
|
13
|
-
import { withTransientReadRetry } from "@/util/effect-http-client"
|
|
14
|
-
import { AccountRepo, type AccountRow } from "./repo"
|
|
15
|
-
import { normalizeServerUrl } from "./url"
|
|
16
|
-
import {
|
|
17
|
-
type AccountError,
|
|
18
|
-
AccessToken,
|
|
19
|
-
AccountID,
|
|
20
|
-
DeviceCode,
|
|
21
|
-
Info,
|
|
22
|
-
RefreshToken,
|
|
23
|
-
AccountServiceError,
|
|
24
|
-
AccountTransportError,
|
|
25
|
-
Login,
|
|
26
|
-
Org,
|
|
27
|
-
OrgID,
|
|
28
|
-
PollDenied,
|
|
29
|
-
PollError,
|
|
30
|
-
PollExpired,
|
|
31
|
-
PollPending,
|
|
32
|
-
type PollResult,
|
|
33
|
-
PollSlow,
|
|
34
|
-
PollSuccess,
|
|
35
|
-
UserCode,
|
|
36
|
-
} from "./schema"
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
AccountID,
|
|
40
|
-
type AccountError,
|
|
41
|
-
AccountRepoError,
|
|
42
|
-
AccountServiceError,
|
|
43
|
-
AccountTransportError,
|
|
44
|
-
AccessToken,
|
|
45
|
-
RefreshToken,
|
|
46
|
-
DeviceCode,
|
|
47
|
-
UserCode,
|
|
48
|
-
Info,
|
|
49
|
-
Org,
|
|
50
|
-
OrgID,
|
|
51
|
-
Login,
|
|
52
|
-
PollSuccess,
|
|
53
|
-
PollPending,
|
|
54
|
-
PollSlow,
|
|
55
|
-
PollExpired,
|
|
56
|
-
PollDenied,
|
|
57
|
-
PollError,
|
|
58
|
-
PollResult,
|
|
59
|
-
} from "./schema"
|
|
60
|
-
|
|
61
|
-
export type AccountOrgs = {
|
|
62
|
-
account: Info
|
|
63
|
-
orgs: readonly Org[]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type ActiveOrg = {
|
|
67
|
-
account: Info
|
|
68
|
-
org: Org
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class RemoteConfig extends Schema.Class<RemoteConfig>("RemoteConfig")({
|
|
72
|
-
config: Schema.Record(Schema.String, Schema.Json),
|
|
73
|
-
}) {}
|
|
74
|
-
|
|
75
|
-
const DurationFromSeconds = Schema.Number.pipe(
|
|
76
|
-
Schema.decodeTo(Schema.Duration, {
|
|
77
|
-
decode: SchemaGetter.transform((n) => Duration.seconds(n)),
|
|
78
|
-
encode: SchemaGetter.transform((d) => Duration.toSeconds(d)),
|
|
79
|
-
}),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
class TokenRefresh extends Schema.Class<TokenRefresh>("TokenRefresh")({
|
|
83
|
-
access_token: AccessToken,
|
|
84
|
-
refresh_token: RefreshToken,
|
|
85
|
-
expires_in: DurationFromSeconds,
|
|
86
|
-
}) {}
|
|
87
|
-
|
|
88
|
-
class DeviceAuth extends Schema.Class<DeviceAuth>("DeviceAuth")({
|
|
89
|
-
device_code: DeviceCode,
|
|
90
|
-
user_code: UserCode,
|
|
91
|
-
verification_uri_complete: Schema.String,
|
|
92
|
-
expires_in: DurationFromSeconds,
|
|
93
|
-
interval: DurationFromSeconds,
|
|
94
|
-
}) {}
|
|
95
|
-
|
|
96
|
-
class DeviceTokenSuccess extends Schema.Class<DeviceTokenSuccess>("DeviceTokenSuccess")({
|
|
97
|
-
access_token: AccessToken,
|
|
98
|
-
refresh_token: RefreshToken,
|
|
99
|
-
token_type: Schema.Literal("Bearer"),
|
|
100
|
-
expires_in: DurationFromSeconds,
|
|
101
|
-
}) {}
|
|
102
|
-
|
|
103
|
-
class DeviceTokenError extends Schema.Class<DeviceTokenError>("DeviceTokenError")({
|
|
104
|
-
error: Schema.String,
|
|
105
|
-
error_description: Schema.String,
|
|
106
|
-
}) {
|
|
107
|
-
toPollResult(): PollResult {
|
|
108
|
-
if (this.error === "authorization_pending") return new PollPending()
|
|
109
|
-
if (this.error === "slow_down") return new PollSlow()
|
|
110
|
-
if (this.error === "expired_token") return new PollExpired()
|
|
111
|
-
if (this.error === "access_denied") return new PollDenied()
|
|
112
|
-
return new PollError({ cause: this.error })
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const DeviceToken = Schema.Union([DeviceTokenSuccess, DeviceTokenError])
|
|
117
|
-
|
|
118
|
-
class User extends Schema.Class<User>("User")({
|
|
119
|
-
id: AccountID,
|
|
120
|
-
email: Schema.String,
|
|
121
|
-
}) {}
|
|
122
|
-
|
|
123
|
-
class ClientId extends Schema.Class<ClientId>("ClientId")({ client_id: Schema.String }) {}
|
|
124
|
-
|
|
125
|
-
class DeviceTokenRequest extends Schema.Class<DeviceTokenRequest>("DeviceTokenRequest")({
|
|
126
|
-
grant_type: Schema.String,
|
|
127
|
-
device_code: DeviceCode,
|
|
128
|
-
client_id: Schema.String,
|
|
129
|
-
}) {}
|
|
130
|
-
|
|
131
|
-
class TokenRefreshRequest extends Schema.Class<TokenRefreshRequest>("TokenRefreshRequest")({
|
|
132
|
-
grant_type: Schema.String,
|
|
133
|
-
refresh_token: RefreshToken,
|
|
134
|
-
client_id: Schema.String,
|
|
135
|
-
}) {}
|
|
136
|
-
|
|
137
|
-
const clientId = "opencode-cli"
|
|
138
|
-
const eagerRefreshThreshold = Duration.minutes(5)
|
|
139
|
-
const eagerRefreshThresholdMs = Duration.toMillis(eagerRefreshThreshold)
|
|
140
|
-
|
|
141
|
-
const isTokenFresh = (tokenExpiry: number | null, now: number) =>
|
|
142
|
-
tokenExpiry != null && tokenExpiry > now + eagerRefreshThresholdMs
|
|
143
|
-
|
|
144
|
-
const mapAccountServiceError =
|
|
145
|
-
(message = "Account service operation failed") =>
|
|
146
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, AccountError, R> =>
|
|
147
|
-
effect.pipe(Effect.mapError((cause) => accountErrorFromCause(cause, message)))
|
|
148
|
-
|
|
149
|
-
const accountErrorFromCause = (cause: unknown, message: string): AccountError => {
|
|
150
|
-
if (cause instanceof AccountServiceError || cause instanceof AccountTransportError) {
|
|
151
|
-
return cause
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (HttpClientError.isHttpClientError(cause)) {
|
|
155
|
-
switch (cause.reason._tag) {
|
|
156
|
-
case "TransportError": {
|
|
157
|
-
return AccountTransportError.fromHttpClientError(cause.reason)
|
|
158
|
-
}
|
|
159
|
-
default: {
|
|
160
|
-
return new AccountServiceError({ message, cause })
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return new AccountServiceError({ message, cause })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface Interface {
|
|
169
|
-
readonly active: () => Effect.Effect<Option.Option<Info>, AccountError>
|
|
170
|
-
readonly activeOrg: () => Effect.Effect<Option.Option<ActiveOrg>, AccountError>
|
|
171
|
-
readonly list: () => Effect.Effect<Info[], AccountError>
|
|
172
|
-
readonly orgsByAccount: () => Effect.Effect<readonly AccountOrgs[], AccountError>
|
|
173
|
-
readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountError>
|
|
174
|
-
readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountError>
|
|
175
|
-
readonly orgs: (accountID: AccountID) => Effect.Effect<readonly Org[], AccountError>
|
|
176
|
-
readonly config: (
|
|
177
|
-
accountID: AccountID,
|
|
178
|
-
orgID: OrgID,
|
|
179
|
-
) => Effect.Effect<Option.Option<Record<string, unknown>>, AccountError>
|
|
180
|
-
readonly token: (accountID: AccountID) => Effect.Effect<Option.Option<AccessToken>, AccountError>
|
|
181
|
-
readonly login: (url: string) => Effect.Effect<Login, AccountError>
|
|
182
|
-
readonly poll: (input: Login) => Effect.Effect<PollResult, AccountError>
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
|
|
186
|
-
|
|
187
|
-
export const use = serviceUse(Service)
|
|
188
|
-
|
|
189
|
-
const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
|
|
190
|
-
Service,
|
|
191
|
-
Effect.gen(function* () {
|
|
192
|
-
const repo = yield* AccountRepo.Service
|
|
193
|
-
const http = yield* HttpClient.HttpClient
|
|
194
|
-
const httpRead = withTransientReadRetry(http)
|
|
195
|
-
const httpOk = HttpClient.filterStatusOk(http)
|
|
196
|
-
const httpReadOk = HttpClient.filterStatusOk(httpRead)
|
|
197
|
-
|
|
198
|
-
const executeRead = (request: HttpClientRequest.HttpClientRequest) =>
|
|
199
|
-
httpRead.execute(request).pipe(mapAccountServiceError("HTTP request failed"))
|
|
200
|
-
|
|
201
|
-
const executeReadOk = (request: HttpClientRequest.HttpClientRequest) =>
|
|
202
|
-
httpReadOk.execute(request).pipe(mapAccountServiceError("HTTP request failed"))
|
|
203
|
-
|
|
204
|
-
const executeEffectOk = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
|
|
205
|
-
request.pipe(
|
|
206
|
-
Effect.flatMap((req) => httpOk.execute(req)),
|
|
207
|
-
mapAccountServiceError("HTTP request failed"),
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
const executeEffect = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
|
|
211
|
-
request.pipe(
|
|
212
|
-
Effect.flatMap((req) => http.execute(req)),
|
|
213
|
-
mapAccountServiceError("HTTP request failed"),
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
const refreshToken = Effect.fnUntraced(function* (row: AccountRow) {
|
|
217
|
-
const now = yield* Clock.currentTimeMillis
|
|
218
|
-
|
|
219
|
-
const response = yield* executeEffectOk(
|
|
220
|
-
HttpClientRequest.post(`${row.url}/auth/device/token`).pipe(
|
|
221
|
-
HttpClientRequest.acceptJson,
|
|
222
|
-
HttpClientRequest.schemaBodyJson(TokenRefreshRequest)(
|
|
223
|
-
new TokenRefreshRequest({
|
|
224
|
-
grant_type: "refresh_token",
|
|
225
|
-
refresh_token: row.refresh_token,
|
|
226
|
-
client_id: clientId,
|
|
227
|
-
}),
|
|
228
|
-
),
|
|
229
|
-
),
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
const parsed = yield* HttpClientResponse.schemaBodyJson(TokenRefresh)(response).pipe(
|
|
233
|
-
mapAccountServiceError("Failed to decode response"),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
const expiry = Option.some(now + Duration.toMillis(parsed.expires_in))
|
|
237
|
-
|
|
238
|
-
yield* repo.persistToken({
|
|
239
|
-
accountID: row.id,
|
|
240
|
-
accessToken: parsed.access_token,
|
|
241
|
-
refreshToken: parsed.refresh_token,
|
|
242
|
-
expiry,
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
return parsed.access_token
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
const refreshTokenCache = yield* Cache.make<AccountID, AccessToken, AccountError>({
|
|
249
|
-
capacity: Number.POSITIVE_INFINITY,
|
|
250
|
-
timeToLive: Duration.zero,
|
|
251
|
-
lookup: Effect.fnUntraced(function* (accountID) {
|
|
252
|
-
const maybeAccount = yield* repo.getRow(accountID)
|
|
253
|
-
if (Option.isNone(maybeAccount)) {
|
|
254
|
-
return yield* Effect.fail(new AccountServiceError({ message: "Account not found during token refresh" }))
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const account = maybeAccount.value
|
|
258
|
-
const now = yield* Clock.currentTimeMillis
|
|
259
|
-
if (isTokenFresh(account.token_expiry, now)) {
|
|
260
|
-
return account.access_token
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return yield* refreshToken(account)
|
|
264
|
-
}),
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
|
|
268
|
-
const now = yield* Clock.currentTimeMillis
|
|
269
|
-
if (isTokenFresh(row.token_expiry, now)) {
|
|
270
|
-
return row.access_token
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return yield* Cache.get(refreshTokenCache, row.id)
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
const resolveAccess = Effect.fnUntraced(function* (accountID: AccountID) {
|
|
277
|
-
const maybeAccount = yield* repo.getRow(accountID)
|
|
278
|
-
if (Option.isNone(maybeAccount)) return Option.none()
|
|
279
|
-
|
|
280
|
-
const account = maybeAccount.value
|
|
281
|
-
const accessToken = yield* resolveToken(account)
|
|
282
|
-
return Option.some({ account, accessToken })
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
const fetchOrgs = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) {
|
|
286
|
-
const response = yield* executeReadOk(
|
|
287
|
-
HttpClientRequest.get(`${url}/api/orgs`).pipe(
|
|
288
|
-
HttpClientRequest.acceptJson,
|
|
289
|
-
HttpClientRequest.bearerToken(accessToken),
|
|
290
|
-
),
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
return yield* HttpClientResponse.schemaBodyJson(Schema.Array(Org))(response).pipe(
|
|
294
|
-
mapAccountServiceError("Failed to decode response"),
|
|
295
|
-
)
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
const fetchUser = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) {
|
|
299
|
-
const response = yield* executeReadOk(
|
|
300
|
-
HttpClientRequest.get(`${url}/api/user`).pipe(
|
|
301
|
-
HttpClientRequest.acceptJson,
|
|
302
|
-
HttpClientRequest.bearerToken(accessToken),
|
|
303
|
-
),
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
return yield* HttpClientResponse.schemaBodyJson(User)(response).pipe(
|
|
307
|
-
mapAccountServiceError("Failed to decode response"),
|
|
308
|
-
)
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
const token = Effect.fn("Account.token")((accountID: AccountID) =>
|
|
312
|
-
resolveAccess(accountID).pipe(Effect.map(Option.map((r) => r.accessToken))),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
const activeOrg = Effect.fn("Account.activeOrg")(function* () {
|
|
316
|
-
const activeAccount = yield* repo.active()
|
|
317
|
-
if (Option.isNone(activeAccount)) return Option.none<ActiveOrg>()
|
|
318
|
-
|
|
319
|
-
const account = activeAccount.value
|
|
320
|
-
if (!account.active_org_id) return Option.none<ActiveOrg>()
|
|
321
|
-
|
|
322
|
-
const accountOrgs = yield* orgs(account.id)
|
|
323
|
-
const org = accountOrgs.find((item) => item.id === account.active_org_id)
|
|
324
|
-
if (!org) return Option.none<ActiveOrg>()
|
|
325
|
-
|
|
326
|
-
return Option.some({ account, org })
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
const orgsByAccount = Effect.fn("Account.orgsByAccount")(function* () {
|
|
330
|
-
const accounts = yield* repo.list()
|
|
331
|
-
return yield* Effect.forEach(
|
|
332
|
-
accounts,
|
|
333
|
-
(account) =>
|
|
334
|
-
orgs(account.id).pipe(
|
|
335
|
-
Effect.catch(() => Effect.succeed([] as readonly Org[])),
|
|
336
|
-
Effect.map((orgs) => ({ account, orgs })),
|
|
337
|
-
),
|
|
338
|
-
{ concurrency: 3 },
|
|
339
|
-
)
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
const orgs = Effect.fn("Account.orgs")(function* (accountID: AccountID) {
|
|
343
|
-
const resolved = yield* resolveAccess(accountID)
|
|
344
|
-
if (Option.isNone(resolved)) return []
|
|
345
|
-
|
|
346
|
-
const { account, accessToken } = resolved.value
|
|
347
|
-
|
|
348
|
-
return yield* fetchOrgs(account.url, accessToken)
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
const remove = Effect.fn("Account.remove")(function* (accountID: AccountID) {
|
|
352
|
-
const active = yield* repo.active()
|
|
353
|
-
yield* repo.remove(accountID)
|
|
354
|
-
if (Option.isNone(active) || active.value.id !== accountID) return
|
|
355
|
-
|
|
356
|
-
const next = (yield* orgsByAccount()).flatMap((group) =>
|
|
357
|
-
group.orgs.map((org) => ({ accountID: group.account.id, orgID: org.id })),
|
|
358
|
-
)[0]
|
|
359
|
-
if (!next) return
|
|
360
|
-
yield* repo.use(next.accountID, Option.some(next.orgID))
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
const config = Effect.fn("Account.config")(function* (accountID: AccountID, orgID: OrgID) {
|
|
364
|
-
const resolved = yield* resolveAccess(accountID)
|
|
365
|
-
if (Option.isNone(resolved)) return Option.none()
|
|
366
|
-
|
|
367
|
-
const { account, accessToken } = resolved.value
|
|
368
|
-
|
|
369
|
-
const response = yield* executeRead(
|
|
370
|
-
HttpClientRequest.get(`${account.url}/api/config`).pipe(
|
|
371
|
-
HttpClientRequest.acceptJson,
|
|
372
|
-
HttpClientRequest.bearerToken(accessToken),
|
|
373
|
-
HttpClientRequest.setHeaders({ "x-org-id": orgID }),
|
|
374
|
-
),
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
if (response.status === 404) return Option.none()
|
|
378
|
-
|
|
379
|
-
const ok = yield* HttpClientResponse.filterStatusOk(response).pipe(mapAccountServiceError())
|
|
380
|
-
|
|
381
|
-
const parsed = yield* HttpClientResponse.schemaBodyJson(RemoteConfig)(ok).pipe(
|
|
382
|
-
mapAccountServiceError("Failed to decode response"),
|
|
383
|
-
)
|
|
384
|
-
return Option.some(parsed.config)
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
const login = Effect.fn("Account.login")(function* (server: string) {
|
|
388
|
-
const normalizedServer = normalizeServerUrl(server)
|
|
389
|
-
const response = yield* executeEffectOk(
|
|
390
|
-
HttpClientRequest.post(`${normalizedServer}/auth/device/code`).pipe(
|
|
391
|
-
HttpClientRequest.acceptJson,
|
|
392
|
-
HttpClientRequest.schemaBodyJson(ClientId)(new ClientId({ client_id: clientId })),
|
|
393
|
-
),
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceAuth)(response).pipe(
|
|
397
|
-
mapAccountServiceError("Failed to decode response"),
|
|
398
|
-
)
|
|
399
|
-
const verification = yield* Effect.try({
|
|
400
|
-
try: () => {
|
|
401
|
-
const url = new URL(parsed.verification_uri_complete, `${normalizedServer}/`)
|
|
402
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("expected HTTP(S)")
|
|
403
|
-
return url.href
|
|
404
|
-
},
|
|
405
|
-
catch: (cause) => new AccountServiceError({ message: "Invalid device verification URL", cause }),
|
|
406
|
-
})
|
|
407
|
-
return new Login({
|
|
408
|
-
code: parsed.device_code,
|
|
409
|
-
user: parsed.user_code,
|
|
410
|
-
url: verification,
|
|
411
|
-
server: normalizedServer,
|
|
412
|
-
expiry: parsed.expires_in,
|
|
413
|
-
interval: parsed.interval,
|
|
414
|
-
})
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
const poll = Effect.fn("Account.poll")(function* (input: Login) {
|
|
418
|
-
const response = yield* executeEffect(
|
|
419
|
-
HttpClientRequest.post(`${input.server}/auth/device/token`).pipe(
|
|
420
|
-
HttpClientRequest.acceptJson,
|
|
421
|
-
HttpClientRequest.schemaBodyJson(DeviceTokenRequest)(
|
|
422
|
-
new DeviceTokenRequest({
|
|
423
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
424
|
-
device_code: input.code,
|
|
425
|
-
client_id: clientId,
|
|
426
|
-
}),
|
|
427
|
-
),
|
|
428
|
-
),
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceToken)(response).pipe(
|
|
432
|
-
mapAccountServiceError("Failed to decode response"),
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
if (parsed instanceof DeviceTokenError) return parsed.toPollResult()
|
|
436
|
-
const accessToken = parsed.access_token
|
|
437
|
-
|
|
438
|
-
const user = fetchUser(input.server, accessToken)
|
|
439
|
-
const orgs = fetchOrgs(input.server, accessToken)
|
|
440
|
-
|
|
441
|
-
const [account, remoteOrgs] = yield* Effect.all([user, orgs], { concurrency: 2 })
|
|
442
|
-
|
|
443
|
-
// TODO: When there are multiple orgs, let the user choose
|
|
444
|
-
const firstOrgID = remoteOrgs.length > 0 ? Option.some(remoteOrgs[0].id) : Option.none<OrgID>()
|
|
445
|
-
|
|
446
|
-
const now = yield* Clock.currentTimeMillis
|
|
447
|
-
const expiry = now + Duration.toMillis(parsed.expires_in)
|
|
448
|
-
const refreshToken = parsed.refresh_token
|
|
449
|
-
|
|
450
|
-
yield* repo.persistAccount({
|
|
451
|
-
id: account.id,
|
|
452
|
-
email: account.email,
|
|
453
|
-
url: input.server,
|
|
454
|
-
accessToken,
|
|
455
|
-
refreshToken,
|
|
456
|
-
expiry,
|
|
457
|
-
orgID: firstOrgID,
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
return new PollSuccess({ email: account.email })
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
return Service.of({
|
|
464
|
-
active: repo.active,
|
|
465
|
-
activeOrg,
|
|
466
|
-
list: repo.list,
|
|
467
|
-
orgsByAccount,
|
|
468
|
-
remove,
|
|
469
|
-
use: repo.use,
|
|
470
|
-
orgs,
|
|
471
|
-
config,
|
|
472
|
-
token,
|
|
473
|
-
login,
|
|
474
|
-
poll,
|
|
475
|
-
})
|
|
476
|
-
}),
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AccountRepo.node, httpClient] })
|
|
480
|
-
|
|
481
|
-
export * as Account from "./account"
|