Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- packages/app/.gitignore +3 -0
- packages/app/AGENTS.md +50 -0
- packages/app/README.md +50 -0
- packages/app/V1_API_MIGRATION.md +220 -0
- packages/app/bunfig.toml +3 -0
- packages/app/create-effect-simplification-spec.md +515 -0
- packages/app/happydom.ts +75 -0
- packages/app/index.html +28 -0
- packages/app/package.json +97 -0
- packages/app/playwright.config.ts +45 -0
- packages/app/sst-env.d.ts +10 -0
- packages/app/tsconfig.json +26 -0
- packages/app/vite.config.ts +33 -0
- packages/app/vite.js +48 -0
- packages/core/bunfig.toml +2 -0
- packages/core/drizzle.config.ts +10 -0
- packages/core/package.json +132 -0
- packages/core/schema.json +2071 -0
- packages/core/sst-env.d.ts +10 -0
- packages/core/tsconfig.json +7 -0
- packages/effect-drizzle-sqlite/AGENTS.md +19 -0
- packages/effect-drizzle-sqlite/package.json +28 -0
- packages/enterprise/.gitignore +26 -0
- packages/enterprise/README.md +32 -0
- packages/enterprise/package.json +46 -0
- packages/enterprise/sst-env.d.ts +10 -0
- packages/enterprise/test-debug.ts +40 -0
- packages/enterprise/tsconfig.json +20 -0
- packages/enterprise/vite.config.ts +37 -0
- packages/http-recorder/LICENSE +21 -0
- packages/http-recorder/README.md +217 -0
- packages/http-recorder/package.json +60 -0
- packages/http-recorder/sst-env.d.ts +10 -0
- packages/http-recorder/tsconfig.json +22 -0
- packages/httpapi-codegen/README.md +42 -0
- packages/httpapi-codegen/package.json +22 -0
- packages/httpapi-codegen/sst-env.d.ts +10 -0
- packages/httpapi-codegen/tsconfig.json +8 -0
- packages/identity/mark-192x192.png +0 -0
- packages/identity/mark-512x512-light.png +0 -0
- packages/identity/mark-512x512.png +0 -0
- packages/identity/mark-96x96.png +0 -0
- packages/identity/mark-light.svg +5 -0
- packages/identity/mark.svg +7 -0
- packages/llm/AGENTS.md +321 -0
- packages/llm/DESIGN.md +1114 -0
- packages/llm/README.md +131 -0
- packages/llm/package.json +52 -0
- packages/llm/sst-env.d.ts +10 -0
- packages/llm/tsconfig.json +15 -0
packages/app/.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
src/assets/theme.css
|
| 2 |
+
e2e/test-results
|
| 3 |
+
e2e/playwright-report
|
packages/app/AGENTS.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Priorities
|
| 2 |
+
|
| 3 |
+
- Prioritise, in this order: stability, simplicity, performance.
|
| 4 |
+
- Before changing session or timeline code, record a production benchmark baseline and compare it after the change.
|
| 5 |
+
|
| 6 |
+
## Debugging
|
| 7 |
+
|
| 8 |
+
- NEVER try to restart the app, or the server process, EVER.
|
| 9 |
+
|
| 10 |
+
## Local Dev
|
| 11 |
+
|
| 12 |
+
- `opencode dev web` proxies `https://app.opencode.ai`, so local UI/CSS changes will not show there.
|
| 13 |
+
- For local UI changes, run the backend and app dev servers separately.
|
| 14 |
+
- Backend (from `packages/opencode`): `bun run ./src/index.ts serve --port 4096`
|
| 15 |
+
- App (from `packages/app`): `bun dev -- --port 4444`
|
| 16 |
+
- Open `http://localhost:4444` to verify UI changes (it targets the backend at `http://localhost:4096`).
|
| 17 |
+
|
| 18 |
+
## SolidJS
|
| 19 |
+
|
| 20 |
+
- Always prefer `createStore` over multiple `createSignal` calls
|
| 21 |
+
|
| 22 |
+
## Localization
|
| 23 |
+
|
| 24 |
+
- NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors.
|
| 25 |
+
- When migrating existing copy to i18n, preserve the English text byte-for-byte unless the task explicitly requests a copy change.
|
| 26 |
+
- NEVER change existing English text or English keys to facilitate translation. English is intentional, designer-written source copy; adapt locale-specific translations and i18n mechanics around it.
|
| 27 |
+
- Keep locale complexity behind the shared typed i18n APIs. Feature and component code should use `language.t(...)` for ordinary copy and `language.plural(baseKey, count, params)` for count-sensitive copy. It must not inspect the locale, call `Intl.PluralRules`, construct or select plural-category keys such as `.one` or `.other`, or branch on locale-specific grammar.
|
| 28 |
+
- Prefer complete translated phrases. Do not concatenate grammatical fragments or make call sites assemble sentences. Keep placeholders to irreducible dynamic values such as names, paths, and counts.
|
| 29 |
+
- If a translation cannot be expressed by the current API, deepen the shared language/UI i18n module so one typed call owns locale selection, plural resolution, fallback, and interpolation. Do not leak that machinery into product code.
|
| 30 |
+
- Do not translate from model knowledge alone. Verify terminology and grammar with Unicode CLDR locale/plural data, Microsoft Localization Style Guides and terminology, Apple localization/style guidance and localized platform UI, Mozilla localization style guides, Mozilla Pontoon, and the Firefox localization corpus at `github.com/mozilla-l10n/firefox-l10n`.
|
| 31 |
+
- For developer-facing terminology, prefer the words already used by the target language's developer community over literal dictionary translations. Cross-check maintained localized developer products such as Firefox, KDE, and VS Code; use at least two independent corpora when they are available. If established practice keeps an English loanword or acronym, keep it rather than inventing a translation.
|
| 32 |
+
- Translate complete UI phrases in context. A glossary hit is evidence, not permission to translate word-by-word. Check terse labels such as session, prompt, agent, model, fork, shell, terminal, workspace, and worktree in the same grammatical role before choosing a term.
|
| 33 |
+
- Before a locale is ready, audit recurring concepts for one consistent translation and review every value that still equals English. Classify retained English as a product name, provider/tool name, URL, code token, keyboard legend, acronym, asset name, or established borrowing; translate unexplained leftovers.
|
| 34 |
+
- In translation review notes, name the corpora used and call out uncertain or region-specific terminology so native speakers can focus review where it matters.
|
| 35 |
+
- Also use the relevant language authority or official dictionary for the locale (for example RAE/Fundéu, FranceTerme, Duden, TDK, Kotus/Kielitoimiston sanakirja, Språkrådet/Bokmålsordboka, Rada Języka Polskiego/PWN, the Russian and Arabic language academies, the Ukrainian Orthography, Taiwan MOE dictionaries, or the Royal Society of Thailand). Treat the English dictionary as the semantic source of truth and preserve placeholders, code identifiers, product names, and keyboard labels.
|
| 36 |
+
|
| 37 |
+
## Tool Calling
|
| 38 |
+
|
| 39 |
+
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
| 40 |
+
|
| 41 |
+
## Browser Automation
|
| 42 |
+
|
| 43 |
+
Use `agent-browser` for web automation. Run `agent-browser --help` for all commands.
|
| 44 |
+
|
| 45 |
+
Core workflow:
|
| 46 |
+
|
| 47 |
+
1. `agent-browser open <url>` - Navigate to page
|
| 48 |
+
2. `agent-browser snapshot -i` - Get interactive elements with refs (@e1, @e2)
|
| 49 |
+
3. `agent-browser click @e1` / `fill @e2 "text"` - Interact using refs
|
| 50 |
+
4. Re-snapshot after page changes
|
packages/app/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Usage
|
| 2 |
+
|
| 3 |
+
Dependencies for these templates are managed with [pnpm](https://pnpm.io) using `pnpm up -Lri`.
|
| 4 |
+
|
| 5 |
+
This is the reason you see a `pnpm-lock.yaml`. That said, any package manager will work. This file can safely be removed once you clone a template.
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
$ npm install # or pnpm install or yarn install
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
| 12 |
+
|
| 13 |
+
## Available Scripts
|
| 14 |
+
|
| 15 |
+
In the project directory, you can run:
|
| 16 |
+
|
| 17 |
+
### `npm run dev` or `npm start`
|
| 18 |
+
|
| 19 |
+
Runs the app in the development mode.<br>
|
| 20 |
+
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
| 21 |
+
|
| 22 |
+
The page will reload if you make edits.<br>
|
| 23 |
+
|
| 24 |
+
### `npm run build`
|
| 25 |
+
|
| 26 |
+
Builds the app for production to the `dist` folder.<br>
|
| 27 |
+
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
| 28 |
+
|
| 29 |
+
The build is minified and the filenames include the hashes.<br>
|
| 30 |
+
Your app is ready to be deployed!
|
| 31 |
+
|
| 32 |
+
## E2E Testing
|
| 33 |
+
|
| 34 |
+
Playwright starts the Vite dev server automatically via `webServer`, and UI tests expect an opencode backend at `localhost:4096` by default.
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
bunx playwright install chromium
|
| 38 |
+
bun run test:e2e:local
|
| 39 |
+
bun run test:e2e:local -- --grep "settings"
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
Environment options:
|
| 43 |
+
|
| 44 |
+
- `PLAYWRIGHT_SERVER_HOST` / `PLAYWRIGHT_SERVER_PORT` (backend address, default: `localhost:4096`)
|
| 45 |
+
- `PLAYWRIGHT_PORT` (Vite dev server port, default: `3000`)
|
| 46 |
+
- `PLAYWRIGHT_BASE_URL` (override base URL, default: `http://localhost:<PLAYWRIGHT_PORT>`)
|
| 47 |
+
|
| 48 |
+
## Deployment
|
| 49 |
+
|
| 50 |
+
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
packages/app/V1_API_MIGRATION.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# V1 API Migration Checklist
|
| 2 |
+
|
| 3 |
+
The app is currently hybrid. In this document, V1 refers to the legacy unprefixed server APIs used by `@opencode-ai/sdk/v2`, despite the SDK package name.
|
| 4 |
+
|
| 5 |
+
## Events
|
| 6 |
+
|
| 7 |
+
- [x] Replace `GET /global/event` with `GET /api/event`.
|
| 8 |
+
- `src/context/server-sdk.tsx`
|
| 9 |
+
- [x] Reduce current granular session and message events into the existing app projections.
|
| 10 |
+
- `src/context/server-session-v2-reducer.ts`
|
| 11 |
+
- `src/context/server-session.ts`
|
| 12 |
+
- [ ] Remove transitional session event dependencies: `session.created`, `session.updated`, `session.diff`, `session.status`, `session.idle`, and `session.error`.
|
| 13 |
+
- `src/context/global-sync/event-reducer.ts`
|
| 14 |
+
- `src/context/server-session.ts`
|
| 15 |
+
- `src/context/notification.tsx`
|
| 16 |
+
- `src/pages/session/usage-exceeded-dialogs.tsx`
|
| 17 |
+
- [ ] Remove legacy message event compatibility: `message.updated`, `message.removed`, `message.part.updated`, `message.part.removed`, and `message.part.delta`.
|
| 18 |
+
- `src/context/global-sync/event-reducer.ts`
|
| 19 |
+
- `src/context/server-session.ts`
|
| 20 |
+
- [x] Adapt current permission and question events to the existing request model.
|
| 21 |
+
- `src/context/global-sync/event-reducer.ts`
|
| 22 |
+
- `src/context/permission.tsx`
|
| 23 |
+
- [x] Consume current file watcher events.
|
| 24 |
+
- `src/context/file.tsx`
|
| 25 |
+
- [x] Consume current VCS events.
|
| 26 |
+
- `src/context/global-sync/event-reducer.ts`
|
| 27 |
+
- `src/pages/session.tsx`
|
| 28 |
+
- [x] Consume current `pty.exited` events.
|
| 29 |
+
- `src/context/terminal.tsx`
|
| 30 |
+
- [ ] Migrate LSP and reference events.
|
| 31 |
+
- `src/context/global-sync/event-reducer.ts`
|
| 32 |
+
|
| 33 |
+
## Sessions
|
| 34 |
+
|
| 35 |
+
- [x] Replace `GET /session/status` with one server-scoped `GET /api/session/active` snapshot plus V2 execution events.
|
| 36 |
+
- `src/context/server-sync.tsx`
|
| 37 |
+
- [x] Migrate session listing from `GET /session`.
|
| 38 |
+
- `src/context/server-sync.tsx`
|
| 39 |
+
- `src/context/directory-sync.ts`
|
| 40 |
+
- `src/pages/layout.tsx`
|
| 41 |
+
- [x] Migrate the remaining direct session read from `GET /session/:sessionID`.
|
| 42 |
+
- `src/components/titlebar.tsx`
|
| 43 |
+
- [x] Migrate session updates from `PATCH /session/:sessionID`.
|
| 44 |
+
- `src/context/directory-sync.ts`
|
| 45 |
+
- `src/context/layout.tsx`
|
| 46 |
+
- `src/pages/home.tsx`
|
| 47 |
+
- `src/pages/layout.tsx`
|
| 48 |
+
- `src/pages/session/timeline/message-timeline.tsx`
|
| 49 |
+
- `src/components/titlebar-tab-nav.tsx`
|
| 50 |
+
- Renames use `POST /api/session/:sessionID/rename`; archival uses `POST /api/session/:sessionID/archive`.
|
| 51 |
+
- [x] Migrate session deletion from `DELETE /session/:sessionID`.
|
| 52 |
+
- `src/pages/session/timeline/message-timeline.tsx`
|
| 53 |
+
- [x] Remove session diff loading from `GET /session/:sessionID/diff`.
|
| 54 |
+
- Historical Session diffs remain unavailable until the current API defines their snapshot semantics.
|
| 55 |
+
- [x] Migrate abort from `POST /session/:sessionID/abort`.
|
| 56 |
+
- `src/components/prompt-input/submit.ts`
|
| 57 |
+
- `src/pages/session/use-session-commands.tsx`
|
| 58 |
+
- `src/pages/session.tsx`
|
| 59 |
+
- [x] Migrate revert and unrevert from `POST /session/:sessionID/revert` and `POST /session/:sessionID/unrevert`.
|
| 60 |
+
- `src/pages/session/use-session-commands.tsx`
|
| 61 |
+
- `src/pages/session.tsx`
|
| 62 |
+
- [x] Replace `POST /session/:sessionID/summarize` with the current compact API.
|
| 63 |
+
- `src/pages/session/use-session-commands.tsx`
|
| 64 |
+
- [x] Migrate slash commands from `POST /session/:sessionID/command`.
|
| 65 |
+
- `src/components/prompt-input/submit.ts`
|
| 66 |
+
- [x] Migrate shell execution from `POST /session/:sessionID/shell`.
|
| 67 |
+
- `src/components/prompt-input/submit.ts`
|
| 68 |
+
- [x] Migrate session fork from `POST /session/:sessionID/fork`.
|
| 69 |
+
- `src/components/dialog-fork.tsx`
|
| 70 |
+
- [ ] Migrate sharing from `POST /session/:sessionID/share` and `DELETE /session/:sessionID/share`.
|
| 71 |
+
- `src/pages/session/use-session-commands.tsx`
|
| 72 |
+
- `src/pages/session/timeline/message-timeline.tsx`
|
| 73 |
+
- Blocked: the current API has no sharing contract or implementation.
|
| 74 |
+
|
| 75 |
+
## Session Compatibility Fallbacks
|
| 76 |
+
|
| 77 |
+
These calls are retained as fallback adapters. The current production path supplies the current session and message APIs.
|
| 78 |
+
|
| 79 |
+
- [ ] Remove fallback `GET /session/:sessionID` after compatibility support is unnecessary.
|
| 80 |
+
- `src/context/server-session.ts`
|
| 81 |
+
- [ ] Remove fallback `GET /session/:sessionID/message` after compatibility support is unnecessary.
|
| 82 |
+
- `src/context/server-session.ts`
|
| 83 |
+
- [ ] Remove fallback `GET /session/:sessionID/message/:messageID` after compatibility support is unnecessary.
|
| 84 |
+
- `src/context/server-session.ts`
|
| 85 |
+
|
| 86 |
+
## Filesystem
|
| 87 |
+
|
| 88 |
+
- [ ] Migrate file listing from `GET /file`.
|
| 89 |
+
- `src/context/file.tsx`
|
| 90 |
+
- [ ] Migrate file reads from `GET /file/content`.
|
| 91 |
+
- `src/context/file.tsx`
|
| 92 |
+
- `src/pages/session/review-tab.tsx`
|
| 93 |
+
- `src/pages/session/v2/review-panel-v2.tsx`
|
| 94 |
+
- [x] Migrate path discovery from `GET /path` to `GET /api/path`.
|
| 95 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 96 |
+
- `src/components/dialog-select-directory.tsx`
|
| 97 |
+
- `src/components/dialog-select-directory-v2.tsx`
|
| 98 |
+
|
| 99 |
+
## Projects And Worktrees
|
| 100 |
+
|
| 101 |
+
- [x] Migrate project listing from `GET /project` to `GET /api/project`.
|
| 102 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 103 |
+
- [x] Migrate the current project lookup from `GET /project/current` to `GET /api/project/current`.
|
| 104 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 105 |
+
- [ ] Migrate Git initialization from `POST /project/git/init`.
|
| 106 |
+
- `src/pages/session.tsx`
|
| 107 |
+
- [x] Migrate project updates from `PATCH /project/:projectID` to `PATCH /api/project/:projectID`.
|
| 108 |
+
- `src/context/layout.tsx`
|
| 109 |
+
- `src/components/edit-project.ts`
|
| 110 |
+
- `src/pages/layout.tsx`
|
| 111 |
+
- [ ] Migrate experimental worktree listing, creation, removal, and reset from `/experimental/worktree`.
|
| 112 |
+
- `src/pages/layout.tsx`
|
| 113 |
+
- `src/components/prompt-input/submit.ts`
|
| 114 |
+
- Listing now uses `GET /api/project/:projectID/directories`; create, removal, and reset remain.
|
| 115 |
+
- [ ] Migrate instance disposal from `POST /instance/dispose`.
|
| 116 |
+
- `src/pages/layout.tsx`
|
| 117 |
+
|
| 118 |
+
## VCS
|
| 119 |
+
|
| 120 |
+
- [x] Migrate repository information from `GET /vcs` to `GET /api/vcs`.
|
| 121 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 122 |
+
- [x] Migrate diffs from `GET /vcs/diff` to `GET /api/vcs/diff`.
|
| 123 |
+
- `src/pages/session.tsx`
|
| 124 |
+
- [x] Migrate status from `GET /vcs/status` to `GET /api/vcs/status`.
|
| 125 |
+
- `src/pages/layout.tsx`
|
| 126 |
+
|
| 127 |
+
## Configuration And Authentication
|
| 128 |
+
|
| 129 |
+
- [ ] Migrate global configuration reads from `GET /global/config`.
|
| 130 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 131 |
+
- [ ] Migrate directory configuration reads from `GET /config`.
|
| 132 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 133 |
+
- [ ] Migrate global configuration updates from `PATCH /global/config`.
|
| 134 |
+
- `src/context/server-sync.tsx`
|
| 135 |
+
- [x] Migrate provider authentication method discovery from `GET /provider/auth` to `GET /api/integration/:integrationID`.
|
| 136 |
+
- `src/components/dialog-connect-provider.tsx`
|
| 137 |
+
- [x] Migrate built-in provider OAuth authorization and callbacks to `/api/integration/:integrationID/connect/oauth/*`.
|
| 138 |
+
- `src/components/dialog-connect-provider.tsx`
|
| 139 |
+
- [ ] Migrate remaining credentials from `PUT /auth/:providerID` and `DELETE /auth/:providerID`.
|
| 140 |
+
- Built-in provider key connections now use `POST /api/integration/:integrationID/connect/key`.
|
| 141 |
+
- `src/components/dialog-connect-provider.tsx`
|
| 142 |
+
- `src/components/dialog-custom-provider.tsx`
|
| 143 |
+
- `src/components/settings-providers.tsx`
|
| 144 |
+
- `src/components/settings-v2/providers.tsx`
|
| 145 |
+
- [ ] Migrate global disposal from `POST /global/dispose`.
|
| 146 |
+
- `src/components/dialog-connect-provider.tsx`
|
| 147 |
+
- `src/components/settings-providers.tsx`
|
| 148 |
+
- `src/components/settings-v2/providers.tsx`
|
| 149 |
+
|
| 150 |
+
## Permissions And Questions
|
| 151 |
+
|
| 152 |
+
- [x] Migrate permission listing from `GET /permission` to `GET /api/permission/request`.
|
| 153 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 154 |
+
- `src/context/permission.tsx`
|
| 155 |
+
- [x] Migrate permission responses from `/session/:sessionID/permissions/:permissionID`.
|
| 156 |
+
- `src/context/permission.tsx`
|
| 157 |
+
- `src/pages/session/composer/session-composer-state.ts`
|
| 158 |
+
- [x] Migrate question listing from `GET /question` to `GET /api/question/request`.
|
| 159 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 160 |
+
- [x] Migrate question replies and rejections from `/question/:requestID/*` to `/api/session/:sessionID/question/:requestID/*`.
|
| 161 |
+
- `src/pages/session/composer/session-question-dock.tsx`
|
| 162 |
+
|
| 163 |
+
## Commands, MCP, LSP, And References
|
| 164 |
+
|
| 165 |
+
- [x] Migrate command listing from `GET /command` to `GET /api/command`.
|
| 166 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 167 |
+
- `src/context/server-sync.tsx`
|
| 168 |
+
- [x] Migrate MCP listing, connection, and disconnection from `/mcp` to `/api/mcp`.
|
| 169 |
+
- `src/context/server-sync.tsx`
|
| 170 |
+
- [ ] Replace legacy MCP authentication with the Integration OAuth workflow.
|
| 171 |
+
- `src/context/server-sync.tsx`
|
| 172 |
+
- [x] Migrate experimental resource listing from `GET /experimental/resource` to `GET /api/mcp/resource`.
|
| 173 |
+
- `src/context/server-sync.tsx`
|
| 174 |
+
- [ ] Migrate LSP status from `GET /lsp`.
|
| 175 |
+
- `src/context/server-sync.tsx`
|
| 176 |
+
- [x] Move `GET /api/reference` off the legacy generated SDK transport.
|
| 177 |
+
- `src/context/global-sync/bootstrap.ts`
|
| 178 |
+
|
| 179 |
+
## Search
|
| 180 |
+
|
| 181 |
+
- [x] Migrate global session search from `GET /experimental/session` to `GET /api/session`.
|
| 182 |
+
- `src/components/command-palette.ts`
|
| 183 |
+
- `src/components/dialog-command-palette-v2.tsx`
|
| 184 |
+
|
| 185 |
+
## PTY And Terminal
|
| 186 |
+
|
| 187 |
+
- [x] Migrate PTY creation, reads, updates, and deletion from `/pty` to `/api/pty`.
|
| 188 |
+
- `src/context/terminal.tsx`
|
| 189 |
+
- `src/components/terminal.tsx`
|
| 190 |
+
- [x] Migrate shell listing from `GET /pty/shells` to `GET /api/pty/shells`.
|
| 191 |
+
- `src/components/settings-general.tsx`
|
| 192 |
+
- `src/components/settings-v2/general.tsx`
|
| 193 |
+
- [x] Migrate connection tokens from `POST /pty/:ptyID/connect-token` to `POST /api/pty/:ptyID/connect-token`.
|
| 194 |
+
- `src/components/terminal.tsx`
|
| 195 |
+
- [x] Migrate the direct WebSocket connection from `/pty/:ptyID/connect` to `/api/pty/:ptyID/connect`.
|
| 196 |
+
- `src/components/terminal.tsx`
|
| 197 |
+
|
| 198 |
+
## Legacy Types And Adapters
|
| 199 |
+
|
| 200 |
+
These are not V1 network requests, but they keep the UI coupled to V1 data contracts.
|
| 201 |
+
|
| 202 |
+
- [ ] Replace the current-session-to-legacy-session adapter.
|
| 203 |
+
- `src/utils/session.ts`
|
| 204 |
+
- [ ] Replace the current-message-to-legacy-message-and-part adapter.
|
| 205 |
+
- `src/utils/session-message.ts`
|
| 206 |
+
- [ ] Replace current agent, provider, and model adapters to legacy SDK structures.
|
| 207 |
+
- `src/context/global-sync/utils.ts`
|
| 208 |
+
- [ ] Replace legacy `Session`, `Message`, `Part`, `PermissionRequest`, `QuestionRequest`, `Project`, `FileNode`, `FileDiffInfo`, and `Event` types throughout app state and rendering.
|
| 209 |
+
- [ ] Remove the `@opencode-ai/sdk` runtime dependency after all legacy calls and types are gone.
|
| 210 |
+
- `package.json`
|
| 211 |
+
|
| 212 |
+
## Test Infrastructure
|
| 213 |
+
|
| 214 |
+
- [ ] Replace V1 endpoint mocks with current API mocks.
|
| 215 |
+
- `e2e/utils/mock-server.ts`
|
| 216 |
+
- [x] Replace `/global/event` and `/event` interception with current event transport handling.
|
| 217 |
+
- `e2e/utils/sse-transport.ts`
|
| 218 |
+
- [ ] Replace `SessionV1` and legacy SDK fixtures in timeline performance tests.
|
| 219 |
+
- `e2e/performance/timeline-stability/fixture.ts`
|
| 220 |
+
- [ ] Remove remaining legacy SDK type fixtures from unit and browser tests.
|
packages/app/bunfig.toml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[test]
|
| 2 |
+
root = "./src"
|
| 3 |
+
preload = ["./happydom.ts"]
|
packages/app/create-effect-simplification-spec.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CreateEffect Simplification Implementation Spec
|
| 2 |
+
|
| 3 |
+
Reduce reactive misuse across `packages/app`.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Context
|
| 8 |
+
|
| 9 |
+
This work targets `packages/app/src`, which currently has 101 `createEffect` calls across 37 files.
|
| 10 |
+
|
| 11 |
+
The biggest clusters are `pages/session.tsx` (19), `pages/layout.tsx` (13), `pages/session/file-tabs.tsx` (6), and several context providers that mirror one store into another.
|
| 12 |
+
|
| 13 |
+
Key issues from the audit:
|
| 14 |
+
|
| 15 |
+
- Derived state is being written through effects instead of computed directly
|
| 16 |
+
- Session and file resets are handled by watch-and-clear effects instead of keyed state boundaries
|
| 17 |
+
- User-driven actions are hidden inside reactive effects
|
| 18 |
+
- Context layers mirror and hydrate child stores with multiple sync effects
|
| 19 |
+
- Several areas repeat the same imperative trigger pattern in multiple effects
|
| 20 |
+
|
| 21 |
+
Keep the implementation focused on removing unnecessary effects, not on broad UI redesign.
|
| 22 |
+
|
| 23 |
+
## Goals
|
| 24 |
+
|
| 25 |
+
- Cut high-churn `createEffect` usage in the hottest files first
|
| 26 |
+
- Replace effect-driven derived state with reactive derivation
|
| 27 |
+
- Replace reset-on-key effects with keyed ownership boundaries
|
| 28 |
+
- Move event-driven work to direct actions and write paths
|
| 29 |
+
- Remove mirrored store hydration where a single source of truth can exist
|
| 30 |
+
- Leave necessary external sync effects in place, but make them narrower and clearer
|
| 31 |
+
|
| 32 |
+
## Non-Goals
|
| 33 |
+
|
| 34 |
+
- Do not rewrite unrelated component structure just to reduce the count
|
| 35 |
+
- Do not change product behavior, navigation flow, or persisted data shape unless required for a cleaner write boundary
|
| 36 |
+
- Do not remove effects that bridge to DOM, editors, polling, or external APIs unless there is a clearly safer equivalent
|
| 37 |
+
- Do not attempt a repo-wide cleanup outside `packages/app`
|
| 38 |
+
|
| 39 |
+
## Effect Taxonomy And Replacement Rules
|
| 40 |
+
|
| 41 |
+
Use these rules during implementation.
|
| 42 |
+
|
| 43 |
+
### Prefer `createMemo`
|
| 44 |
+
|
| 45 |
+
Use `createMemo` when the target value is pure derived state from other signals or stores.
|
| 46 |
+
|
| 47 |
+
Do this when an effect only reads reactive inputs and writes another reactive value that could be computed instead.
|
| 48 |
+
|
| 49 |
+
Apply this to:
|
| 50 |
+
|
| 51 |
+
- `packages/app/src/pages/session.tsx:141`
|
| 52 |
+
- `packages/app/src/pages/layout.tsx:557`
|
| 53 |
+
- `packages/app/src/components/terminal.tsx:261`
|
| 54 |
+
- `packages/app/src/components/session/session-header.tsx:309`
|
| 55 |
+
|
| 56 |
+
Rules:
|
| 57 |
+
|
| 58 |
+
- If no external system is touched, do not use `createEffect`
|
| 59 |
+
- Derive once, then read the memo where needed
|
| 60 |
+
- If normalization is required, prefer normalizing at the write boundary before falling back to a memo
|
| 61 |
+
|
| 62 |
+
### Prefer Keyed Remounts
|
| 63 |
+
|
| 64 |
+
Use keyed remounts when local UI state should reset because an identity changed.
|
| 65 |
+
|
| 66 |
+
Do this with `sessionKey`, `scope()`, or another stable identity instead of watching the key and manually clearing signals.
|
| 67 |
+
|
| 68 |
+
Apply this to:
|
| 69 |
+
|
| 70 |
+
- `packages/app/src/pages/session.tsx:325`
|
| 71 |
+
- `packages/app/src/pages/session.tsx:336`
|
| 72 |
+
- `packages/app/src/pages/session.tsx:477`
|
| 73 |
+
- `packages/app/src/pages/session.tsx:869`
|
| 74 |
+
- `packages/app/src/pages/session.tsx:963`
|
| 75 |
+
- `packages/app/src/pages/session/message-timeline.tsx:149`
|
| 76 |
+
- `packages/app/src/context/file.tsx:100`
|
| 77 |
+
|
| 78 |
+
Rules:
|
| 79 |
+
|
| 80 |
+
- If the desired behavior is "new identity, fresh local state," key the owner subtree
|
| 81 |
+
- Keep state local to the keyed boundary so teardown and recreation handle the reset naturally
|
| 82 |
+
|
| 83 |
+
### Prefer Event Handlers And Actions
|
| 84 |
+
|
| 85 |
+
Use direct handlers, store actions, and async command functions when work happens because a user clicked, selected, reloaded, or navigated.
|
| 86 |
+
|
| 87 |
+
Do this when an effect is just watching for a flag change, command token, or event-bus signal to trigger imperative logic.
|
| 88 |
+
|
| 89 |
+
Apply this to:
|
| 90 |
+
|
| 91 |
+
- `packages/app/src/pages/layout.tsx:484`
|
| 92 |
+
- `packages/app/src/pages/layout.tsx:652`
|
| 93 |
+
- `packages/app/src/pages/layout.tsx:776`
|
| 94 |
+
- `packages/app/src/pages/layout.tsx:1489`
|
| 95 |
+
- `packages/app/src/pages/layout.tsx:1519`
|
| 96 |
+
- `packages/app/src/components/file-tree.tsx:328`
|
| 97 |
+
- `packages/app/src/pages/session/terminal-panel.tsx:55`
|
| 98 |
+
- `packages/app/src/context/global-sync.tsx:148`
|
| 99 |
+
- Duplicated trigger sets in:
|
| 100 |
+
- `packages/app/src/pages/session/review-tab.tsx:122`
|
| 101 |
+
- `packages/app/src/pages/session/review-tab.tsx:130`
|
| 102 |
+
- `packages/app/src/pages/session/review-tab.tsx:138`
|
| 103 |
+
- `packages/app/src/pages/session/file-tabs.tsx:367`
|
| 104 |
+
- `packages/app/src/pages/session/file-tabs.tsx:378`
|
| 105 |
+
- `packages/app/src/pages/session/file-tabs.tsx:389`
|
| 106 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
|
| 107 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
|
| 108 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
|
| 109 |
+
|
| 110 |
+
Rules:
|
| 111 |
+
|
| 112 |
+
- If the trigger is user intent, call the action at the source of that intent
|
| 113 |
+
- If the same imperative work is triggered from multiple places, extract one function and call it directly
|
| 114 |
+
|
| 115 |
+
### Prefer `onMount` And `onCleanup`
|
| 116 |
+
|
| 117 |
+
Use `onMount` and `onCleanup` for lifecycle-only setup and teardown.
|
| 118 |
+
|
| 119 |
+
This is the right fit for subscriptions, one-time wiring, timers, and imperative integration that should not rerun for ordinary reactive changes.
|
| 120 |
+
|
| 121 |
+
Use this when:
|
| 122 |
+
|
| 123 |
+
- Setup should happen once per owner lifecycle
|
| 124 |
+
- Cleanup should always pair with teardown
|
| 125 |
+
- The work is not conceptually derived state
|
| 126 |
+
|
| 127 |
+
### Keep `createEffect` When It Is A Real Bridge
|
| 128 |
+
|
| 129 |
+
Keep `createEffect` when it synchronizes reactive data to an external imperative sink.
|
| 130 |
+
|
| 131 |
+
Examples that should remain, though they may be narrowed or split:
|
| 132 |
+
|
| 133 |
+
- DOM/editor sync in `packages/app/src/components/prompt-input.tsx:690`
|
| 134 |
+
- Scroll sync in `packages/app/src/pages/session.tsx:685`
|
| 135 |
+
- Scroll/hash sync in `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
|
| 136 |
+
- External sync in:
|
| 137 |
+
- `packages/app/src/context/language.tsx:207`
|
| 138 |
+
- `packages/app/src/context/settings.tsx:110`
|
| 139 |
+
- `packages/app/src/context/sdk.tsx:26`
|
| 140 |
+
- Polling in:
|
| 141 |
+
- `packages/app/src/components/status-popover.tsx:59`
|
| 142 |
+
- `packages/app/src/components/dialog-select-server.tsx:273`
|
| 143 |
+
|
| 144 |
+
Rules:
|
| 145 |
+
|
| 146 |
+
- Keep the effect single-purpose
|
| 147 |
+
- Make dependencies explicit and narrow
|
| 148 |
+
- Avoid writing back into the same reactive graph unless absolutely required
|
| 149 |
+
|
| 150 |
+
## Implementation Plan
|
| 151 |
+
|
| 152 |
+
### Phase 0: Classification Pass
|
| 153 |
+
|
| 154 |
+
Before changing code, tag each targeted effect as one of: derive, reset, event, lifecycle, or external bridge.
|
| 155 |
+
|
| 156 |
+
Acceptance criteria:
|
| 157 |
+
|
| 158 |
+
- Every targeted effect in this spec is tagged with a replacement strategy before refactoring starts
|
| 159 |
+
- Shared helpers to be introduced are identified up front to avoid repeating patterns
|
| 160 |
+
|
| 161 |
+
### Phase 1: Derived-State Cleanup
|
| 162 |
+
|
| 163 |
+
Tackle highest-value, lowest-risk derived-state cleanup first.
|
| 164 |
+
|
| 165 |
+
Priority items:
|
| 166 |
+
|
| 167 |
+
- Normalize tabs at write boundaries and remove `packages/app/src/pages/session.tsx:141`
|
| 168 |
+
- Stop syncing `workspaceOrder` in `packages/app/src/pages/layout.tsx:557`
|
| 169 |
+
- Make prompt slash filtering reactive so `packages/app/src/components/prompt-input.tsx:652` can be removed
|
| 170 |
+
- Replace other obvious derived-state effects in terminal and session header
|
| 171 |
+
|
| 172 |
+
Acceptance criteria:
|
| 173 |
+
|
| 174 |
+
- No behavior change in tab ordering, prompt filtering, terminal display, or header state
|
| 175 |
+
- Targeted derived-state effects are deleted, not just moved
|
| 176 |
+
|
| 177 |
+
### Phase 2: Keyed Reset Cleanup
|
| 178 |
+
|
| 179 |
+
Replace reset-on-key effects with keyed ownership boundaries.
|
| 180 |
+
|
| 181 |
+
Priority items:
|
| 182 |
+
|
| 183 |
+
- Key session-scoped UI and state by `sessionKey`
|
| 184 |
+
- Key file-scoped state by `scope()`
|
| 185 |
+
- Remove manual clear-and-reseed effects in session and file context
|
| 186 |
+
|
| 187 |
+
Acceptance criteria:
|
| 188 |
+
|
| 189 |
+
- Switching session or file scope recreates the intended local state cleanly
|
| 190 |
+
- No stale state leaks across session or scope changes
|
| 191 |
+
- Target reset effects are deleted
|
| 192 |
+
|
| 193 |
+
### Phase 3: Event-Driven Work Extraction
|
| 194 |
+
|
| 195 |
+
Move event-driven work out of reactive effects.
|
| 196 |
+
|
| 197 |
+
Priority items:
|
| 198 |
+
|
| 199 |
+
- Replace `globalStore.reload` effect dispatching with direct calls
|
| 200 |
+
- Split mixed-responsibility effect in `packages/app/src/pages/layout.tsx:1489`
|
| 201 |
+
- Collapse duplicated imperative trigger triplets into single functions
|
| 202 |
+
- Move file-tree and terminal-panel imperative work to explicit handlers
|
| 203 |
+
|
| 204 |
+
Acceptance criteria:
|
| 205 |
+
|
| 206 |
+
- User-triggered behavior still fires exactly once per intended action
|
| 207 |
+
- No effect remains whose only job is to notice a command-like state and trigger an imperative function
|
| 208 |
+
|
| 209 |
+
### Phase 4: Context Ownership Cleanup
|
| 210 |
+
|
| 211 |
+
Remove mirrored child-store hydration patterns.
|
| 212 |
+
|
| 213 |
+
Priority items:
|
| 214 |
+
|
| 215 |
+
- Remove child-store hydration mirrors in `packages/app/src/context/global-sync/child-store.ts:184`, `:190`, `:193`
|
| 216 |
+
- Simplify mirror logic in `packages/app/src/context/global-sync.tsx:130`, `:138`
|
| 217 |
+
- Revisit `packages/app/src/context/layout.tsx:424` if it still mirrors instead of deriving
|
| 218 |
+
|
| 219 |
+
Acceptance criteria:
|
| 220 |
+
|
| 221 |
+
- There is one clear source of truth for each synced value
|
| 222 |
+
- Child stores no longer need effect-based hydration to stay consistent
|
| 223 |
+
- Initialization and updates both work without manual mirror effects
|
| 224 |
+
|
| 225 |
+
### Phase 5: Cleanup And Keeper Review
|
| 226 |
+
|
| 227 |
+
Clean up remaining targeted hotspots and narrow the effects that should stay.
|
| 228 |
+
|
| 229 |
+
Acceptance criteria:
|
| 230 |
+
|
| 231 |
+
- Remaining `createEffect` calls in touched files are all true bridges or clearly justified lifecycle sync
|
| 232 |
+
- Mixed-responsibility effects are split into smaller units where still needed
|
| 233 |
+
|
| 234 |
+
## Detailed Work Items By Area
|
| 235 |
+
|
| 236 |
+
### 1. Normalize Tab State
|
| 237 |
+
|
| 238 |
+
Files:
|
| 239 |
+
|
| 240 |
+
- `packages/app/src/pages/session.tsx:141`
|
| 241 |
+
|
| 242 |
+
Work:
|
| 243 |
+
|
| 244 |
+
- Move tab normalization into the functions that create, load, or update tab state
|
| 245 |
+
- Make readers consume already-normalized tab data
|
| 246 |
+
- Remove the effect that rewrites derived tab state after the fact
|
| 247 |
+
|
| 248 |
+
Rationale:
|
| 249 |
+
|
| 250 |
+
- Tabs should become valid when written, not be repaired later
|
| 251 |
+
- This removes a feedback loop and makes state easier to trust
|
| 252 |
+
|
| 253 |
+
Acceptance criteria:
|
| 254 |
+
|
| 255 |
+
- The effect at `packages/app/src/pages/session.tsx:141` is removed
|
| 256 |
+
- Newly created and restored tabs are normalized before they enter local state
|
| 257 |
+
- Tab rendering still matches current behavior for valid and edge-case inputs
|
| 258 |
+
|
| 259 |
+
### 2. Key Session-Owned State
|
| 260 |
+
|
| 261 |
+
Files:
|
| 262 |
+
|
| 263 |
+
- `packages/app/src/pages/session.tsx:325`
|
| 264 |
+
- `packages/app/src/pages/session.tsx:336`
|
| 265 |
+
- `packages/app/src/pages/session.tsx:477`
|
| 266 |
+
- `packages/app/src/pages/session.tsx:869`
|
| 267 |
+
- `packages/app/src/pages/session.tsx:963`
|
| 268 |
+
- `packages/app/src/pages/session/message-timeline.tsx:149`
|
| 269 |
+
|
| 270 |
+
Work:
|
| 271 |
+
|
| 272 |
+
- Identify state that should reset when `sessionKey` changes
|
| 273 |
+
- Move that state under a keyed subtree or keyed owner boundary
|
| 274 |
+
- Remove effects that watch `sessionKey` just to clear local state, refs, or temporary UI flags
|
| 275 |
+
|
| 276 |
+
Rationale:
|
| 277 |
+
|
| 278 |
+
- Session identity already defines the lifetime of this UI state
|
| 279 |
+
- Keyed ownership makes reset behavior automatic and easier to reason about
|
| 280 |
+
|
| 281 |
+
Acceptance criteria:
|
| 282 |
+
|
| 283 |
+
- The targeted reset effects are removed
|
| 284 |
+
- Changing sessions resets only the intended session-local state
|
| 285 |
+
- Scroll and editor state that should persist are not accidentally reset
|
| 286 |
+
|
| 287 |
+
### 3. Derive Workspace Order
|
| 288 |
+
|
| 289 |
+
Files:
|
| 290 |
+
|
| 291 |
+
- `packages/app/src/pages/layout.tsx:557`
|
| 292 |
+
|
| 293 |
+
Work:
|
| 294 |
+
|
| 295 |
+
- Stop writing `workspaceOrder` from live workspace data in an effect
|
| 296 |
+
- Represent user overrides separately from live workspace data
|
| 297 |
+
- Compute effective order from current data plus overrides with a memo or pure helper
|
| 298 |
+
|
| 299 |
+
Rationale:
|
| 300 |
+
|
| 301 |
+
- Persisted user intent and live source data should not mirror each other through an effect
|
| 302 |
+
- A computed effective order avoids drift and racey resync behavior
|
| 303 |
+
|
| 304 |
+
Acceptance criteria:
|
| 305 |
+
|
| 306 |
+
- The effect at `packages/app/src/pages/layout.tsx:557` is removed
|
| 307 |
+
- Workspace order updates correctly when workspaces appear, disappear, or are reordered by the user
|
| 308 |
+
- User overrides persist without requiring a sync-back effect
|
| 309 |
+
|
| 310 |
+
### 4. Remove Child-Store Mirrors
|
| 311 |
+
|
| 312 |
+
Files:
|
| 313 |
+
|
| 314 |
+
- `packages/app/src/context/global-sync.tsx:130`
|
| 315 |
+
- `packages/app/src/context/global-sync.tsx:138`
|
| 316 |
+
- `packages/app/src/context/global-sync.tsx:148`
|
| 317 |
+
- `packages/app/src/context/global-sync/child-store.ts:184`
|
| 318 |
+
- `packages/app/src/context/global-sync/child-store.ts:190`
|
| 319 |
+
- `packages/app/src/context/global-sync/child-store.ts:193`
|
| 320 |
+
- `packages/app/src/context/layout.tsx:424`
|
| 321 |
+
|
| 322 |
+
Work:
|
| 323 |
+
|
| 324 |
+
- Trace the actual ownership of global and child store values
|
| 325 |
+
- Replace hydration and mirror effects with explicit initialization and direct updates
|
| 326 |
+
- Remove the `globalStore.reload` event-bus pattern and call the needed reload paths directly
|
| 327 |
+
|
| 328 |
+
Rationale:
|
| 329 |
+
|
| 330 |
+
- Mirrors make it hard to tell which state is authoritative
|
| 331 |
+
- Event-bus style state toggles hide control flow and create accidental reruns
|
| 332 |
+
|
| 333 |
+
Acceptance criteria:
|
| 334 |
+
|
| 335 |
+
- Child store hydration no longer depends on effect-based copying
|
| 336 |
+
- Reload work can be followed from the event source to the handler without a reactive relay
|
| 337 |
+
- State remains correct on first load, child creation, and subsequent updates
|
| 338 |
+
|
| 339 |
+
### 5. Key File-Scoped State
|
| 340 |
+
|
| 341 |
+
Files:
|
| 342 |
+
|
| 343 |
+
- `packages/app/src/context/file.tsx:100`
|
| 344 |
+
|
| 345 |
+
Work:
|
| 346 |
+
|
| 347 |
+
- Move file-scoped local state under a boundary keyed by `scope()`
|
| 348 |
+
- Remove any effect that watches `scope()` only to reset file-local state
|
| 349 |
+
|
| 350 |
+
Rationale:
|
| 351 |
+
|
| 352 |
+
- File scope changes are identity changes
|
| 353 |
+
- Keyed ownership gives a cleaner reset than manual clear logic
|
| 354 |
+
|
| 355 |
+
Acceptance criteria:
|
| 356 |
+
|
| 357 |
+
- The effect at `packages/app/src/context/file.tsx:100` is removed
|
| 358 |
+
- Switching scopes resets only scope-local state
|
| 359 |
+
- No previous-scope data appears after a scope change
|
| 360 |
+
|
| 361 |
+
### 6. Split Layout Side Effects
|
| 362 |
+
|
| 363 |
+
Files:
|
| 364 |
+
|
| 365 |
+
- `packages/app/src/pages/layout.tsx:1489`
|
| 366 |
+
- Related event-driven effects near `packages/app/src/pages/layout.tsx:484`, `:652`, `:776`, `:1519`
|
| 367 |
+
|
| 368 |
+
Work:
|
| 369 |
+
|
| 370 |
+
- Break the mixed-responsibility effect at `:1489` into direct actions and smaller bridge effects only where required
|
| 371 |
+
- Move user-triggered branches into the actual command or handler that causes them
|
| 372 |
+
- Remove any branch that only exists because one effect is handling unrelated concerns
|
| 373 |
+
|
| 374 |
+
Rationale:
|
| 375 |
+
|
| 376 |
+
- Mixed effects hide cause and make reruns hard to predict
|
| 377 |
+
- Smaller units reduce accidental coupling and make future cleanup safer
|
| 378 |
+
|
| 379 |
+
Acceptance criteria:
|
| 380 |
+
|
| 381 |
+
- The effect at `packages/app/src/pages/layout.tsx:1489` no longer mixes unrelated responsibilities
|
| 382 |
+
- Event-driven branches execute from direct handlers
|
| 383 |
+
- Remaining effects in this area each have one clear external sync purpose
|
| 384 |
+
|
| 385 |
+
### 7. Remove Duplicate Triggers
|
| 386 |
+
|
| 387 |
+
Files:
|
| 388 |
+
|
| 389 |
+
- `packages/app/src/pages/session/review-tab.tsx:122`
|
| 390 |
+
- `packages/app/src/pages/session/review-tab.tsx:130`
|
| 391 |
+
- `packages/app/src/pages/session/review-tab.tsx:138`
|
| 392 |
+
- `packages/app/src/pages/session/file-tabs.tsx:367`
|
| 393 |
+
- `packages/app/src/pages/session/file-tabs.tsx:378`
|
| 394 |
+
- `packages/app/src/pages/session/file-tabs.tsx:389`
|
| 395 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:144`
|
| 396 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:149`
|
| 397 |
+
- `packages/app/src/pages/session/use-session-hash-scroll.ts:167`
|
| 398 |
+
|
| 399 |
+
Work:
|
| 400 |
+
|
| 401 |
+
- Extract one explicit imperative function per behavior
|
| 402 |
+
- Call that function from each source event instead of replicating the same effect pattern multiple times
|
| 403 |
+
- Preserve the scroll-sync effect that is truly syncing with the DOM, but remove duplicate trigger scaffolding around it
|
| 404 |
+
|
| 405 |
+
Rationale:
|
| 406 |
+
|
| 407 |
+
- Duplicate triggers make it easy to miss a case or fire twice
|
| 408 |
+
- One named action is easier to test and reason about
|
| 409 |
+
|
| 410 |
+
Acceptance criteria:
|
| 411 |
+
|
| 412 |
+
- Repeated imperative effect triplets are collapsed into shared functions
|
| 413 |
+
- Scroll behavior still works, including hash-based navigation
|
| 414 |
+
- No duplicate firing is introduced
|
| 415 |
+
|
| 416 |
+
### 8. Make Prompt Filtering Reactive
|
| 417 |
+
|
| 418 |
+
Files:
|
| 419 |
+
|
| 420 |
+
- `packages/app/src/components/prompt-input.tsx:652`
|
| 421 |
+
- Keep `packages/app/src/components/prompt-input.tsx:690` as needed
|
| 422 |
+
|
| 423 |
+
Work:
|
| 424 |
+
|
| 425 |
+
- Convert slash filtering into a pure reactive derivation from the current input and candidate command list
|
| 426 |
+
- Keep only the editor or DOM bridge effect if it is still needed for imperative syncing
|
| 427 |
+
|
| 428 |
+
Rationale:
|
| 429 |
+
|
| 430 |
+
- Filtering is classic derived state
|
| 431 |
+
- It should not need an effect if it can be computed from current inputs
|
| 432 |
+
|
| 433 |
+
Acceptance criteria:
|
| 434 |
+
|
| 435 |
+
- The effect at `packages/app/src/components/prompt-input.tsx:652` is removed
|
| 436 |
+
- Filtered slash-command results update correctly as the input changes
|
| 437 |
+
- The editor sync effect at `:690` still behaves correctly
|
| 438 |
+
|
| 439 |
+
### 9. Clean Up Smaller Derived-State Cases
|
| 440 |
+
|
| 441 |
+
Files:
|
| 442 |
+
|
| 443 |
+
- `packages/app/src/components/terminal.tsx:261`
|
| 444 |
+
- `packages/app/src/components/session/session-header.tsx:309`
|
| 445 |
+
|
| 446 |
+
Work:
|
| 447 |
+
|
| 448 |
+
- Replace effect-written local state with memos or inline derivation
|
| 449 |
+
- Remove intermediate setters when the value can be computed directly
|
| 450 |
+
|
| 451 |
+
Rationale:
|
| 452 |
+
|
| 453 |
+
- These are low-risk wins that reinforce the same pattern
|
| 454 |
+
- They also help keep follow-up cleanup consistent
|
| 455 |
+
|
| 456 |
+
Acceptance criteria:
|
| 457 |
+
|
| 458 |
+
- Targeted effects are removed
|
| 459 |
+
- UI output remains unchanged under the same inputs
|
| 460 |
+
|
| 461 |
+
## Verification And Regression Checks
|
| 462 |
+
|
| 463 |
+
Run focused checks after each phase, not only at the end.
|
| 464 |
+
|
| 465 |
+
### Suggested Verification
|
| 466 |
+
|
| 467 |
+
- Switch between sessions rapidly and confirm local session UI resets only where intended
|
| 468 |
+
- Open, close, and reorder tabs and confirm order and normalization remain stable
|
| 469 |
+
- Change workspaces, reload workspace data, and verify effective ordering is correct
|
| 470 |
+
- Change file scope and confirm stale file state does not bleed across scopes
|
| 471 |
+
- Trigger layout actions that previously depended on effects and confirm they still fire once
|
| 472 |
+
- Use slash commands in the prompt and verify filtering updates as you type
|
| 473 |
+
- Test review tab, file tab, and hash-scroll flows for duplicate or missing triggers
|
| 474 |
+
- Verify global sync initialization, reload, and child-store creation paths
|
| 475 |
+
|
| 476 |
+
### Regression Checks
|
| 477 |
+
|
| 478 |
+
- No accidental infinite reruns
|
| 479 |
+
- No double-firing network or command actions
|
| 480 |
+
- No lost cleanup for listeners, timers, or scroll handlers
|
| 481 |
+
- No preserved stale state after identity changes
|
| 482 |
+
- No removed effect that was actually bridging to DOM or an external API
|
| 483 |
+
|
| 484 |
+
If available, add or update tests around pure helpers introduced during this cleanup.
|
| 485 |
+
|
| 486 |
+
Favor tests for derived ordering, normalization, and action extraction, since those are easiest to lock down.
|
| 487 |
+
|
| 488 |
+
## Definition Of Done
|
| 489 |
+
|
| 490 |
+
This work is done when all of the following are true:
|
| 491 |
+
|
| 492 |
+
- The highest-leverage targets in this spec are implemented
|
| 493 |
+
- Each removed effect has been replaced by a clearer pattern: memo, keyed boundary, direct action, or lifecycle hook
|
| 494 |
+
- The "should remain" effects still exist only where they serve a real external sync purpose
|
| 495 |
+
- Touched files have fewer mixed-responsibility effects and clearer ownership of state
|
| 496 |
+
- Manual verification covers session switching, file scope changes, workspace ordering, prompt filtering, and reload flows
|
| 497 |
+
- No behavior regressions are found in the targeted areas
|
| 498 |
+
|
| 499 |
+
A reduced raw `createEffect` count is helpful, but it is not the main success metric.
|
| 500 |
+
|
| 501 |
+
The main success metric is clearer ownership and fewer effect-driven state repairs.
|
| 502 |
+
|
| 503 |
+
## Risks And Rollout Notes
|
| 504 |
+
|
| 505 |
+
Main risks:
|
| 506 |
+
|
| 507 |
+
- Keyed remounts can reset too much if state boundaries are drawn too high
|
| 508 |
+
- Store mirror removal can break initialization order if ownership is not mapped first
|
| 509 |
+
- Moving event work out of effects can accidentally skip triggers that were previously implicit
|
| 510 |
+
|
| 511 |
+
Rollout notes:
|
| 512 |
+
|
| 513 |
+
- Land in small phases, with each phase keeping the app behaviorally stable
|
| 514 |
+
- Prefer isolated PRs by phase or by file cluster, especially for context-store changes
|
| 515 |
+
- Review each remaining effect in touched files and leave it only if it clearly bridges to something external
|
packages/app/happydom.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { GlobalRegistrator } from "@happy-dom/global-registrator"
|
| 2 |
+
|
| 3 |
+
GlobalRegistrator.register()
|
| 4 |
+
|
| 5 |
+
const originalGetContext = HTMLCanvasElement.prototype.getContext
|
| 6 |
+
// @ts-expect-error - we're overriding with a simplified mock
|
| 7 |
+
HTMLCanvasElement.prototype.getContext = function (contextType: string, _options?: unknown) {
|
| 8 |
+
if (contextType === "2d") {
|
| 9 |
+
return {
|
| 10 |
+
canvas: this,
|
| 11 |
+
fillStyle: "#000000",
|
| 12 |
+
strokeStyle: "#000000",
|
| 13 |
+
font: "12px monospace",
|
| 14 |
+
textAlign: "start",
|
| 15 |
+
textBaseline: "alphabetic",
|
| 16 |
+
globalAlpha: 1,
|
| 17 |
+
globalCompositeOperation: "source-over",
|
| 18 |
+
imageSmoothingEnabled: true,
|
| 19 |
+
lineWidth: 1,
|
| 20 |
+
lineCap: "butt",
|
| 21 |
+
lineJoin: "miter",
|
| 22 |
+
miterLimit: 10,
|
| 23 |
+
shadowBlur: 0,
|
| 24 |
+
shadowColor: "rgba(0, 0, 0, 0)",
|
| 25 |
+
shadowOffsetX: 0,
|
| 26 |
+
shadowOffsetY: 0,
|
| 27 |
+
fillRect: () => {},
|
| 28 |
+
strokeRect: () => {},
|
| 29 |
+
clearRect: () => {},
|
| 30 |
+
fillText: () => {},
|
| 31 |
+
strokeText: () => {},
|
| 32 |
+
measureText: (text: string) => ({ width: text.length * 8 }),
|
| 33 |
+
drawImage: () => {},
|
| 34 |
+
save: () => {},
|
| 35 |
+
restore: () => {},
|
| 36 |
+
scale: () => {},
|
| 37 |
+
rotate: () => {},
|
| 38 |
+
translate: () => {},
|
| 39 |
+
transform: () => {},
|
| 40 |
+
setTransform: () => {},
|
| 41 |
+
resetTransform: () => {},
|
| 42 |
+
createLinearGradient: () => ({ addColorStop: () => {} }),
|
| 43 |
+
createRadialGradient: () => ({ addColorStop: () => {} }),
|
| 44 |
+
createPattern: () => null,
|
| 45 |
+
beginPath: () => {},
|
| 46 |
+
closePath: () => {},
|
| 47 |
+
moveTo: () => {},
|
| 48 |
+
lineTo: () => {},
|
| 49 |
+
bezierCurveTo: () => {},
|
| 50 |
+
quadraticCurveTo: () => {},
|
| 51 |
+
arc: () => {},
|
| 52 |
+
arcTo: () => {},
|
| 53 |
+
ellipse: () => {},
|
| 54 |
+
rect: () => {},
|
| 55 |
+
fill: () => {},
|
| 56 |
+
stroke: () => {},
|
| 57 |
+
clip: () => {},
|
| 58 |
+
isPointInPath: () => false,
|
| 59 |
+
isPointInStroke: () => false,
|
| 60 |
+
getTransform: () => ({}),
|
| 61 |
+
getImageData: () => ({
|
| 62 |
+
data: new Uint8ClampedArray(0),
|
| 63 |
+
width: 0,
|
| 64 |
+
height: 0,
|
| 65 |
+
}),
|
| 66 |
+
putImageData: () => {},
|
| 67 |
+
createImageData: () => ({
|
| 68 |
+
data: new Uint8ClampedArray(0),
|
| 69 |
+
width: 0,
|
| 70 |
+
height: 0,
|
| 71 |
+
}),
|
| 72 |
+
} as unknown as CanvasRenderingContext2D
|
| 73 |
+
}
|
| 74 |
+
return originalGetContext.call(this, contextType as "2d", _options)
|
| 75 |
+
}
|
packages/app/index.html
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en" style="background-color: var(--v2-background-bg-deep, #fafafa)">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta
|
| 6 |
+
name="viewport"
|
| 7 |
+
content="width=device-width, initial-scale=1, interactive-widget=resizes-content, viewport-fit=cover"
|
| 8 |
+
/>
|
| 9 |
+
<title>OpenCode</title>
|
| 10 |
+
<link rel="icon" type="image/png" href="/favicon-96x96-v3.png" sizes="96x96" />
|
| 11 |
+
<link rel="icon" type="image/svg+xml" href="/favicon-v3.svg" />
|
| 12 |
+
<link rel="shortcut icon" href="/favicon-v3.ico" />
|
| 13 |
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-v3.png" />
|
| 14 |
+
<link rel="manifest" href="/site.webmanifest" />
|
| 15 |
+
<meta name="theme-color" content="#fafafa" />
|
| 16 |
+
<meta name="mobile-web-app-capable" content="yes" />
|
| 17 |
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
| 18 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
| 19 |
+
<meta property="og:image" content="/social-share.png" />
|
| 20 |
+
<meta property="twitter:image" content="/social-share.png" />
|
| 21 |
+
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
| 22 |
+
</head>
|
| 23 |
+
<body class="antialiased overscroll-none text-12-regular overflow-hidden bg-v2-background-bg-deep">
|
| 24 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
| 25 |
+
<div id="root" class="flex flex-col h-dvh bg-v2-background-bg-deep p-px"></div>
|
| 26 |
+
<script src="/src/entry.tsx" type="module"></script>
|
| 27 |
+
</body>
|
| 28 |
+
</html>
|
packages/app/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@opencode-ai/app",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"description": "",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"exports": {
|
| 7 |
+
".": "./src/index.ts",
|
| 8 |
+
"./desktop-menu": "./src/desktop-menu.ts",
|
| 9 |
+
"./i18n/desktop-native": "./src/i18n/desktop-native.ts",
|
| 10 |
+
"./updater": "./src/updater.ts",
|
| 11 |
+
"./wsl/types": "./src/wsl/types.ts",
|
| 12 |
+
"./vite": "./vite.js",
|
| 13 |
+
"./index.css": "./src/index.css"
|
| 14 |
+
},
|
| 15 |
+
"scripts": {
|
| 16 |
+
"typecheck": "tsgo -b",
|
| 17 |
+
"typecheck:e2e": "tsgo -p e2e/tsconfig.json",
|
| 18 |
+
"start": "vite",
|
| 19 |
+
"dev": "vite",
|
| 20 |
+
"build": "vite build",
|
| 21 |
+
"serve": "vite preview",
|
| 22 |
+
"test": "bun run test:unit && bun run test:browser",
|
| 23 |
+
"test:unit": "bun test --conditions=solid --only-failures --preload ./happydom.ts ./src",
|
| 24 |
+
"test:browser": "bun test --conditions=browser --preload ./happydom.ts ./test-browser",
|
| 25 |
+
"test:unit:watch": "bun test --conditions=solid --watch --preload ./happydom.ts ./src",
|
| 26 |
+
"test:e2e": "playwright test",
|
| 27 |
+
"test:e2e:local": "playwright test",
|
| 28 |
+
"test:e2e:ui": "playwright test --ui",
|
| 29 |
+
"test:e2e:report": "playwright show-report e2e/playwright-report",
|
| 30 |
+
"test:stability": "bun test ./e2e/performance/unit/visual-stability.test.ts && playwright test --config e2e/performance/timeline-stability/playwright.config.ts",
|
| 31 |
+
"test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts"
|
| 32 |
+
},
|
| 33 |
+
"license": "MIT",
|
| 34 |
+
"devDependencies": {
|
| 35 |
+
"@happy-dom/global-registrator": "20.12.0",
|
| 36 |
+
"@playwright/test": "catalog:",
|
| 37 |
+
"@sentry/vite-plugin": "catalog:",
|
| 38 |
+
"@tailwindcss/vite": "catalog:",
|
| 39 |
+
"@tsconfig/bun": "1.0.9",
|
| 40 |
+
"@types/bun": "catalog:",
|
| 41 |
+
"@types/luxon": "catalog:",
|
| 42 |
+
"@types/node": "catalog:",
|
| 43 |
+
"@typescript/native-preview": "catalog:",
|
| 44 |
+
"tw-animate-css": "1.4.0",
|
| 45 |
+
"typescript": "catalog:",
|
| 46 |
+
"vite": "catalog:",
|
| 47 |
+
"vite-plugin-icons-spritesheet": "3.0.1",
|
| 48 |
+
"vite-plugin-solid": "catalog:"
|
| 49 |
+
},
|
| 50 |
+
"dependencies": {
|
| 51 |
+
"@corvu/drawer": "catalog:",
|
| 52 |
+
"@dnd-kit/abstract": "0.5.0",
|
| 53 |
+
"@dnd-kit/dom": "0.5.0",
|
| 54 |
+
"@dnd-kit/helpers": "0.5.0",
|
| 55 |
+
"@dnd-kit/solid": "0.5.0",
|
| 56 |
+
"@kobalte/core": "catalog:",
|
| 57 |
+
"@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-v2.tgz",
|
| 58 |
+
"@opencode-ai/core": "workspace:*",
|
| 59 |
+
"@opencode-ai/schema": "workspace:*",
|
| 60 |
+
"@opencode-ai/sdk": "workspace:*",
|
| 61 |
+
"@opencode-ai/session-ui": "workspace:*",
|
| 62 |
+
"@opencode-ai/ui": "workspace:*",
|
| 63 |
+
"@pierre/trees": "1.0.0-beta.4",
|
| 64 |
+
"@sentry/solid": "catalog:",
|
| 65 |
+
"@shikijs/transformers": "3.9.2",
|
| 66 |
+
"@solid-primitives/active-element": "2.1.3",
|
| 67 |
+
"@solid-primitives/audio": "1.4.2",
|
| 68 |
+
"@solid-primitives/event-bus": "1.1.2",
|
| 69 |
+
"@solid-primitives/event-listener": "2.4.5",
|
| 70 |
+
"@solid-primitives/i18n": "2.2.1",
|
| 71 |
+
"@solid-primitives/media": "2.3.3",
|
| 72 |
+
"@solid-primitives/resize-observer": "2.1.5",
|
| 73 |
+
"@solid-primitives/scheduled": "1.5.3",
|
| 74 |
+
"@solid-primitives/scroll": "2.1.3",
|
| 75 |
+
"@solid-primitives/storage": "catalog:",
|
| 76 |
+
"@solid-primitives/timer": "1.4.4",
|
| 77 |
+
"@solid-primitives/websocket": "1.3.1",
|
| 78 |
+
"@solidjs/meta": "catalog:",
|
| 79 |
+
"@solidjs/router": "catalog:",
|
| 80 |
+
"@tanstack/solid-query": "5.91.4",
|
| 81 |
+
"@tanstack/solid-virtual": "catalog:",
|
| 82 |
+
"@thisbeyond/solid-dnd": "0.7.5",
|
| 83 |
+
"diff": "catalog:",
|
| 84 |
+
"effect": "catalog:",
|
| 85 |
+
"fuzzysort": "catalog:",
|
| 86 |
+
"ghostty-web": "github:anomalyco/ghostty-web#83c0a07b8628b748aed073b232cb4b52a6ca11c1",
|
| 87 |
+
"luxon": "catalog:",
|
| 88 |
+
"marked": "catalog:",
|
| 89 |
+
"marked-shiki": "catalog:",
|
| 90 |
+
"remeda": "catalog:",
|
| 91 |
+
"shiki": "catalog:",
|
| 92 |
+
"solid-js": "catalog:",
|
| 93 |
+
"solid-list": "catalog:",
|
| 94 |
+
"solid-presence": "0.2.0",
|
| 95 |
+
"tailwindcss": "catalog:"
|
| 96 |
+
}
|
| 97 |
+
}
|
packages/app/playwright.config.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, devices } from "@playwright/test"
|
| 2 |
+
|
| 3 |
+
const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000)
|
| 4 |
+
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`
|
| 5 |
+
const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
| 6 |
+
const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
| 7 |
+
const command = `bun run dev -- --host 0.0.0.0 --port ${port}`
|
| 8 |
+
const reuse = !process.env.CI
|
| 9 |
+
const workers = Number(process.env.PLAYWRIGHT_WORKERS ?? (process.env.CI ? 5 : 0)) || undefined
|
| 10 |
+
export default defineConfig({
|
| 11 |
+
testDir: "./e2e",
|
| 12 |
+
testIgnore: process.env.OPENCODE_PERFORMANCE === "1" ? "performance/**/*.test.ts" : "performance/**",
|
| 13 |
+
outputDir: "./e2e/test-results",
|
| 14 |
+
timeout: 60_000,
|
| 15 |
+
expect: {
|
| 16 |
+
timeout: 10_000,
|
| 17 |
+
},
|
| 18 |
+
fullyParallel: process.env.PLAYWRIGHT_FULLY_PARALLEL === "1",
|
| 19 |
+
forbidOnly: !!process.env.CI,
|
| 20 |
+
retries: process.env.CI ? 2 : 0,
|
| 21 |
+
workers,
|
| 22 |
+
reporter: [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]],
|
| 23 |
+
webServer: {
|
| 24 |
+
command,
|
| 25 |
+
url: baseURL,
|
| 26 |
+
reuseExistingServer: reuse,
|
| 27 |
+
timeout: 120_000,
|
| 28 |
+
env: {
|
| 29 |
+
VITE_OPENCODE_SERVER_HOST: serverHost,
|
| 30 |
+
VITE_OPENCODE_SERVER_PORT: serverPort,
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
use: {
|
| 34 |
+
baseURL,
|
| 35 |
+
trace: "on-first-retry",
|
| 36 |
+
screenshot: "only-on-failure",
|
| 37 |
+
video: "retain-on-failure",
|
| 38 |
+
},
|
| 39 |
+
projects: [
|
| 40 |
+
{
|
| 41 |
+
name: "chromium",
|
| 42 |
+
use: { ...devices["Desktop Chrome"] },
|
| 43 |
+
},
|
| 44 |
+
],
|
| 45 |
+
})
|
packages/app/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/app/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/tsconfig",
|
| 3 |
+
"compilerOptions": {
|
| 4 |
+
"composite": true,
|
| 5 |
+
"target": "ESNext",
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"skipLibCheck": true,
|
| 8 |
+
"moduleResolution": "bundler",
|
| 9 |
+
"allowSyntheticDefaultImports": true,
|
| 10 |
+
"esModuleInterop": true,
|
| 11 |
+
"jsx": "preserve",
|
| 12 |
+
"jsxImportSource": "solid-js",
|
| 13 |
+
"allowJs": true,
|
| 14 |
+
"resolveJsonModule": true,
|
| 15 |
+
"strict": true,
|
| 16 |
+
"noEmit": false,
|
| 17 |
+
"emitDeclarationOnly": true,
|
| 18 |
+
"outDir": "node_modules/.ts-dist",
|
| 19 |
+
"isolatedModules": true,
|
| 20 |
+
"paths": {
|
| 21 |
+
"@/*": ["./src/*"]
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"include": ["src", "package.json"],
|
| 25 |
+
"exclude": ["dist", "ts-dist"]
|
| 26 |
+
}
|
packages/app/vite.config.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
| 2 |
+
import { defineConfig } from "vite"
|
| 3 |
+
import desktopPlugin from "./vite"
|
| 4 |
+
|
| 5 |
+
const sentry =
|
| 6 |
+
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
|
| 7 |
+
? sentryVitePlugin({
|
| 8 |
+
authToken: process.env.SENTRY_AUTH_TOKEN,
|
| 9 |
+
org: process.env.SENTRY_ORG,
|
| 10 |
+
project: process.env.SENTRY_PROJECT,
|
| 11 |
+
telemetry: false,
|
| 12 |
+
release: {
|
| 13 |
+
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
|
| 14 |
+
},
|
| 15 |
+
sourcemaps: {
|
| 16 |
+
assets: "./dist/**",
|
| 17 |
+
filesToDeleteAfterUpload: "./dist/**/*.map",
|
| 18 |
+
},
|
| 19 |
+
})
|
| 20 |
+
: false
|
| 21 |
+
|
| 22 |
+
export default defineConfig({
|
| 23 |
+
plugins: [desktopPlugin, sentry] as any,
|
| 24 |
+
server: {
|
| 25 |
+
host: "0.0.0.0",
|
| 26 |
+
allowedHosts: true,
|
| 27 |
+
port: 3000,
|
| 28 |
+
},
|
| 29 |
+
build: {
|
| 30 |
+
target: "esnext",
|
| 31 |
+
sourcemap: true,
|
| 32 |
+
},
|
| 33 |
+
})
|
packages/app/vite.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { readFileSync } from "node:fs"
|
| 2 |
+
import solidPlugin from "vite-plugin-solid"
|
| 3 |
+
import tailwindcss from "@tailwindcss/vite"
|
| 4 |
+
import { fileURLToPath } from "url"
|
| 5 |
+
|
| 6 |
+
const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url))
|
| 7 |
+
|
| 8 |
+
const channel = (() => {
|
| 9 |
+
const raw = process.env.OPENCODE_CHANNEL
|
| 10 |
+
if (raw === "dev" || raw === "beta" || raw === "prod") return raw
|
| 11 |
+
if (process.env.OPENCODE_CHANNEL === "latest") return "prod"
|
| 12 |
+
return "dev"
|
| 13 |
+
})()
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* @type {import("vite").PluginOption}
|
| 17 |
+
*/
|
| 18 |
+
export default [
|
| 19 |
+
{
|
| 20 |
+
name: "opencode-desktop:config",
|
| 21 |
+
config() {
|
| 22 |
+
return {
|
| 23 |
+
resolve: {
|
| 24 |
+
alias: {
|
| 25 |
+
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
| 26 |
+
},
|
| 27 |
+
},
|
| 28 |
+
define: {
|
| 29 |
+
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
|
| 30 |
+
},
|
| 31 |
+
worker: {
|
| 32 |
+
format: "es",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
name: "opencode-desktop:theme-preload",
|
| 39 |
+
transformIndexHtml(html) {
|
| 40 |
+
return html.replace(
|
| 41 |
+
'<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>',
|
| 42 |
+
`<script id="oc-theme-preload-script">${readFileSync(theme, "utf8")}</script>`,
|
| 43 |
+
)
|
| 44 |
+
},
|
| 45 |
+
},
|
| 46 |
+
tailwindcss(),
|
| 47 |
+
solidPlugin(),
|
| 48 |
+
]
|
packages/core/bunfig.toml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[test]
|
| 2 |
+
preload = ["./test/preload.ts"]
|
packages/core/drizzle.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from "drizzle-kit"
|
| 2 |
+
|
| 3 |
+
export default defineConfig({
|
| 4 |
+
dialect: "sqlite",
|
| 5 |
+
schema: ["./src/**/*.sql.ts", "./src/**/sql.ts"],
|
| 6 |
+
out: "./migration",
|
| 7 |
+
dbCredentials: {
|
| 8 |
+
url: "/home/thdxr/.local/share/opencode/opencode.db",
|
| 9 |
+
},
|
| 10 |
+
})
|
packages/core/package.json
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/package.json",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"name": "@opencode-ai/core",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"private": true,
|
| 8 |
+
"scripts": {
|
| 9 |
+
"db": "bun drizzle-kit",
|
| 10 |
+
"migration": "bun run script/migration.ts",
|
| 11 |
+
"fix-node-pty": "bun run script/fix-node-pty.ts",
|
| 12 |
+
"test": "bun test --only-failures",
|
| 13 |
+
"typecheck": "tsgo --noEmit"
|
| 14 |
+
},
|
| 15 |
+
"bin": {
|
| 16 |
+
"opencode": "./bin/opencode"
|
| 17 |
+
},
|
| 18 |
+
"exports": {
|
| 19 |
+
"./effect/layer-node": "./src/effect/layer-node.ts",
|
| 20 |
+
"./effect/app-node": "./src/effect/app-node.ts",
|
| 21 |
+
"./session/runner": "./src/session/runner/index.ts",
|
| 22 |
+
"./system-context": "./src/system-context/index.ts",
|
| 23 |
+
"./*": "./src/*.ts"
|
| 24 |
+
},
|
| 25 |
+
"imports": {
|
| 26 |
+
"#sqlite": {
|
| 27 |
+
"bun": "./src/database/sqlite.bun.ts",
|
| 28 |
+
"node": "./src/database/sqlite.node.ts",
|
| 29 |
+
"default": "./src/database/sqlite.bun.ts"
|
| 30 |
+
},
|
| 31 |
+
"#pty": {
|
| 32 |
+
"bun": "./src/pty/pty.bun.ts",
|
| 33 |
+
"node": "./src/pty/pty.node.ts",
|
| 34 |
+
"default": "./src/pty/pty.bun.ts"
|
| 35 |
+
},
|
| 36 |
+
"#fff": {
|
| 37 |
+
"bun": "./src/filesystem/fff.bun.ts",
|
| 38 |
+
"node": "./src/filesystem/fff.node.ts",
|
| 39 |
+
"default": "./src/filesystem/fff.bun.ts"
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"devDependencies": {
|
| 43 |
+
"@tsconfig/bun": "catalog:",
|
| 44 |
+
"@types/bun": "catalog:",
|
| 45 |
+
"@types/cross-spawn": "catalog:",
|
| 46 |
+
"@types/node": "catalog:",
|
| 47 |
+
"@types/npm-package-arg": "6.1.4",
|
| 48 |
+
"@types/npmcli__arborist": "6.3.3",
|
| 49 |
+
"@types/semver": "catalog:",
|
| 50 |
+
"@types/turndown": "5.0.5",
|
| 51 |
+
"@types/which": "3.0.4",
|
| 52 |
+
"@parcel/watcher-darwin-arm64": "2.5.1",
|
| 53 |
+
"@parcel/watcher-darwin-x64": "2.5.1",
|
| 54 |
+
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
| 55 |
+
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
| 56 |
+
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
| 57 |
+
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
| 58 |
+
"@parcel/watcher-win32-arm64": "2.5.1",
|
| 59 |
+
"@parcel/watcher-win32-x64": "2.5.1",
|
| 60 |
+
"@opencode-ai/http-recorder": "workspace:*",
|
| 61 |
+
"drizzle-kit": "catalog:"
|
| 62 |
+
},
|
| 63 |
+
"dependencies": {
|
| 64 |
+
"@ai-sdk/alibaba": "1.0.17",
|
| 65 |
+
"@ai-sdk/amazon-bedrock": "4.0.166",
|
| 66 |
+
"@ai-sdk/anthropic": "3.0.111",
|
| 67 |
+
"@ai-sdk/azure": "3.0.93",
|
| 68 |
+
"@ai-sdk/cerebras": "2.0.41",
|
| 69 |
+
"@ai-sdk/cohere": "3.0.27",
|
| 70 |
+
"@ai-sdk/deepinfra": "2.0.41",
|
| 71 |
+
"@ai-sdk/gateway": "3.0.191",
|
| 72 |
+
"@ai-sdk/google": "3.0.73",
|
| 73 |
+
"@ai-sdk/google-vertex": "4.0.181",
|
| 74 |
+
"@ai-sdk/groq": "3.0.31",
|
| 75 |
+
"@ai-sdk/mistral": "3.0.51",
|
| 76 |
+
"@ai-sdk/openai": "3.0.88",
|
| 77 |
+
"@ai-sdk/openai-compatible": "2.0.41",
|
| 78 |
+
"@ai-sdk/perplexity": "3.0.26",
|
| 79 |
+
"@ai-sdk/provider": "3.0.16",
|
| 80 |
+
"@ai-sdk/provider-utils": "4.0.51",
|
| 81 |
+
"@ai-sdk/togetherai": "2.0.41",
|
| 82 |
+
"@ai-sdk/vercel": "2.0.39",
|
| 83 |
+
"@ai-sdk/xai": "3.0.102",
|
| 84 |
+
"@aws-sdk/credential-providers": "3.1057.0",
|
| 85 |
+
"@effect/opentelemetry": "catalog:",
|
| 86 |
+
"@effect/platform-node": "catalog:",
|
| 87 |
+
"@effect/sql-sqlite-bun": "catalog:",
|
| 88 |
+
"@lydell/node-pty": "catalog:",
|
| 89 |
+
"@ff-labs/fff-bun": "0.9.4",
|
| 90 |
+
"@npmcli/arborist": "9.4.0",
|
| 91 |
+
"@npmcli/config": "10.8.1",
|
| 92 |
+
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
| 93 |
+
"@opencode-ai/effect-sqlite-node": "workspace:*",
|
| 94 |
+
"@opencode-ai/llm": "workspace:*",
|
| 95 |
+
"@opencode-ai/schema": "workspace:*",
|
| 96 |
+
"@opencode-ai/plugin": "workspace:*",
|
| 97 |
+
"@opentelemetry/api": "1.9.0",
|
| 98 |
+
"@opentelemetry/context-async-hooks": "2.6.1",
|
| 99 |
+
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
| 100 |
+
"@opentelemetry/sdk-trace-base": "2.6.1",
|
| 101 |
+
"@parcel/watcher": "2.5.1",
|
| 102 |
+
"@silvia-odwyer/photon-node": "0.3.4",
|
| 103 |
+
"@openrouter/ai-sdk-provider": "2.9.0",
|
| 104 |
+
"ai-gateway-provider": "3.2.0",
|
| 105 |
+
"bun-pty": "0.4.8",
|
| 106 |
+
"cross-spawn": "catalog:",
|
| 107 |
+
"diff": "catalog:",
|
| 108 |
+
"drizzle-orm": "catalog:",
|
| 109 |
+
"effect": "catalog:",
|
| 110 |
+
"fuzzysort": "3.1.0",
|
| 111 |
+
"gitlab-ai-provider": "6.15.0",
|
| 112 |
+
"glob": "13.0.5",
|
| 113 |
+
"google-auth-library": "10.5.0",
|
| 114 |
+
"gray-matter": "4.0.3",
|
| 115 |
+
"htmlparser2": "8.0.2",
|
| 116 |
+
"immer": "11.1.4",
|
| 117 |
+
"ignore": "7.0.5",
|
| 118 |
+
"jsonc-parser": "3.3.1",
|
| 119 |
+
"mime-types": "3.0.2",
|
| 120 |
+
"minimatch": "10.2.5",
|
| 121 |
+
"npm-package-arg": "13.0.2",
|
| 122 |
+
"semver": "^7.6.3",
|
| 123 |
+
"turndown": "7.2.0",
|
| 124 |
+
"venice-ai-sdk-provider": "2.1.1",
|
| 125 |
+
"which": "6.0.1",
|
| 126 |
+
"xdg-basedir": "5.1.0",
|
| 127 |
+
"zod": "catalog:"
|
| 128 |
+
},
|
| 129 |
+
"overrides": {
|
| 130 |
+
"drizzle-orm": "catalog:"
|
| 131 |
+
}
|
| 132 |
+
}
|
packages/core/schema.json
ADDED
|
@@ -0,0 +1,2071 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "7",
|
| 3 |
+
"dialect": "sqlite",
|
| 4 |
+
"id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
|
| 5 |
+
"prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
|
| 6 |
+
"ddl": [
|
| 7 |
+
{
|
| 8 |
+
"name": "workspace",
|
| 9 |
+
"entityType": "tables"
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"name": "data_migration",
|
| 13 |
+
"entityType": "tables"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"name": "account_state",
|
| 17 |
+
"entityType": "tables"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"name": "account",
|
| 21 |
+
"entityType": "tables"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"name": "control_account",
|
| 25 |
+
"entityType": "tables"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"name": "credential",
|
| 29 |
+
"entityType": "tables"
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"name": "event_sequence",
|
| 33 |
+
"entityType": "tables"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"name": "event",
|
| 37 |
+
"entityType": "tables"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"name": "permission",
|
| 41 |
+
"entityType": "tables"
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"name": "project_directory",
|
| 45 |
+
"entityType": "tables"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"name": "project",
|
| 49 |
+
"entityType": "tables"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"name": "message",
|
| 53 |
+
"entityType": "tables"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"name": "part",
|
| 57 |
+
"entityType": "tables"
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"name": "session_context_epoch",
|
| 61 |
+
"entityType": "tables"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"name": "session_input",
|
| 65 |
+
"entityType": "tables"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"name": "session_message",
|
| 69 |
+
"entityType": "tables"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"name": "session",
|
| 73 |
+
"entityType": "tables"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"name": "todo",
|
| 77 |
+
"entityType": "tables"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"name": "session_share",
|
| 81 |
+
"entityType": "tables"
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"type": "text",
|
| 85 |
+
"notNull": false,
|
| 86 |
+
"autoincrement": false,
|
| 87 |
+
"default": null,
|
| 88 |
+
"generated": null,
|
| 89 |
+
"name": "id",
|
| 90 |
+
"entityType": "columns",
|
| 91 |
+
"table": "workspace"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"type": "text",
|
| 95 |
+
"notNull": true,
|
| 96 |
+
"autoincrement": false,
|
| 97 |
+
"default": null,
|
| 98 |
+
"generated": null,
|
| 99 |
+
"name": "type",
|
| 100 |
+
"entityType": "columns",
|
| 101 |
+
"table": "workspace"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"type": "text",
|
| 105 |
+
"notNull": true,
|
| 106 |
+
"autoincrement": false,
|
| 107 |
+
"default": "''",
|
| 108 |
+
"generated": null,
|
| 109 |
+
"name": "name",
|
| 110 |
+
"entityType": "columns",
|
| 111 |
+
"table": "workspace"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"type": "text",
|
| 115 |
+
"notNull": false,
|
| 116 |
+
"autoincrement": false,
|
| 117 |
+
"default": null,
|
| 118 |
+
"generated": null,
|
| 119 |
+
"name": "branch",
|
| 120 |
+
"entityType": "columns",
|
| 121 |
+
"table": "workspace"
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"type": "text",
|
| 125 |
+
"notNull": false,
|
| 126 |
+
"autoincrement": false,
|
| 127 |
+
"default": null,
|
| 128 |
+
"generated": null,
|
| 129 |
+
"name": "directory",
|
| 130 |
+
"entityType": "columns",
|
| 131 |
+
"table": "workspace"
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"type": "text",
|
| 135 |
+
"notNull": false,
|
| 136 |
+
"autoincrement": false,
|
| 137 |
+
"default": null,
|
| 138 |
+
"generated": null,
|
| 139 |
+
"name": "extra",
|
| 140 |
+
"entityType": "columns",
|
| 141 |
+
"table": "workspace"
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"type": "text",
|
| 145 |
+
"notNull": true,
|
| 146 |
+
"autoincrement": false,
|
| 147 |
+
"default": null,
|
| 148 |
+
"generated": null,
|
| 149 |
+
"name": "project_id",
|
| 150 |
+
"entityType": "columns",
|
| 151 |
+
"table": "workspace"
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"type": "integer",
|
| 155 |
+
"notNull": true,
|
| 156 |
+
"autoincrement": false,
|
| 157 |
+
"default": null,
|
| 158 |
+
"generated": null,
|
| 159 |
+
"name": "time_used",
|
| 160 |
+
"entityType": "columns",
|
| 161 |
+
"table": "workspace"
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"type": "text",
|
| 165 |
+
"notNull": false,
|
| 166 |
+
"autoincrement": false,
|
| 167 |
+
"default": null,
|
| 168 |
+
"generated": null,
|
| 169 |
+
"name": "name",
|
| 170 |
+
"entityType": "columns",
|
| 171 |
+
"table": "data_migration"
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"type": "integer",
|
| 175 |
+
"notNull": true,
|
| 176 |
+
"autoincrement": false,
|
| 177 |
+
"default": null,
|
| 178 |
+
"generated": null,
|
| 179 |
+
"name": "time_completed",
|
| 180 |
+
"entityType": "columns",
|
| 181 |
+
"table": "data_migration"
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"type": "integer",
|
| 185 |
+
"notNull": false,
|
| 186 |
+
"autoincrement": false,
|
| 187 |
+
"default": null,
|
| 188 |
+
"generated": null,
|
| 189 |
+
"name": "id",
|
| 190 |
+
"entityType": "columns",
|
| 191 |
+
"table": "account_state"
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"type": "text",
|
| 195 |
+
"notNull": false,
|
| 196 |
+
"autoincrement": false,
|
| 197 |
+
"default": null,
|
| 198 |
+
"generated": null,
|
| 199 |
+
"name": "active_account_id",
|
| 200 |
+
"entityType": "columns",
|
| 201 |
+
"table": "account_state"
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"type": "text",
|
| 205 |
+
"notNull": false,
|
| 206 |
+
"autoincrement": false,
|
| 207 |
+
"default": null,
|
| 208 |
+
"generated": null,
|
| 209 |
+
"name": "active_org_id",
|
| 210 |
+
"entityType": "columns",
|
| 211 |
+
"table": "account_state"
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"type": "text",
|
| 215 |
+
"notNull": false,
|
| 216 |
+
"autoincrement": false,
|
| 217 |
+
"default": null,
|
| 218 |
+
"generated": null,
|
| 219 |
+
"name": "id",
|
| 220 |
+
"entityType": "columns",
|
| 221 |
+
"table": "account"
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"type": "text",
|
| 225 |
+
"notNull": true,
|
| 226 |
+
"autoincrement": false,
|
| 227 |
+
"default": null,
|
| 228 |
+
"generated": null,
|
| 229 |
+
"name": "email",
|
| 230 |
+
"entityType": "columns",
|
| 231 |
+
"table": "account"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"type": "text",
|
| 235 |
+
"notNull": true,
|
| 236 |
+
"autoincrement": false,
|
| 237 |
+
"default": null,
|
| 238 |
+
"generated": null,
|
| 239 |
+
"name": "url",
|
| 240 |
+
"entityType": "columns",
|
| 241 |
+
"table": "account"
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"type": "text",
|
| 245 |
+
"notNull": true,
|
| 246 |
+
"autoincrement": false,
|
| 247 |
+
"default": null,
|
| 248 |
+
"generated": null,
|
| 249 |
+
"name": "access_token",
|
| 250 |
+
"entityType": "columns",
|
| 251 |
+
"table": "account"
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"type": "text",
|
| 255 |
+
"notNull": true,
|
| 256 |
+
"autoincrement": false,
|
| 257 |
+
"default": null,
|
| 258 |
+
"generated": null,
|
| 259 |
+
"name": "refresh_token",
|
| 260 |
+
"entityType": "columns",
|
| 261 |
+
"table": "account"
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"type": "integer",
|
| 265 |
+
"notNull": false,
|
| 266 |
+
"autoincrement": false,
|
| 267 |
+
"default": null,
|
| 268 |
+
"generated": null,
|
| 269 |
+
"name": "token_expiry",
|
| 270 |
+
"entityType": "columns",
|
| 271 |
+
"table": "account"
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"type": "integer",
|
| 275 |
+
"notNull": true,
|
| 276 |
+
"autoincrement": false,
|
| 277 |
+
"default": null,
|
| 278 |
+
"generated": null,
|
| 279 |
+
"name": "time_created",
|
| 280 |
+
"entityType": "columns",
|
| 281 |
+
"table": "account"
|
| 282 |
+
},
|
| 283 |
+
{
|
| 284 |
+
"type": "integer",
|
| 285 |
+
"notNull": true,
|
| 286 |
+
"autoincrement": false,
|
| 287 |
+
"default": null,
|
| 288 |
+
"generated": null,
|
| 289 |
+
"name": "time_updated",
|
| 290 |
+
"entityType": "columns",
|
| 291 |
+
"table": "account"
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"type": "text",
|
| 295 |
+
"notNull": true,
|
| 296 |
+
"autoincrement": false,
|
| 297 |
+
"default": null,
|
| 298 |
+
"generated": null,
|
| 299 |
+
"name": "email",
|
| 300 |
+
"entityType": "columns",
|
| 301 |
+
"table": "control_account"
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"type": "text",
|
| 305 |
+
"notNull": true,
|
| 306 |
+
"autoincrement": false,
|
| 307 |
+
"default": null,
|
| 308 |
+
"generated": null,
|
| 309 |
+
"name": "url",
|
| 310 |
+
"entityType": "columns",
|
| 311 |
+
"table": "control_account"
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"type": "text",
|
| 315 |
+
"notNull": true,
|
| 316 |
+
"autoincrement": false,
|
| 317 |
+
"default": null,
|
| 318 |
+
"generated": null,
|
| 319 |
+
"name": "access_token",
|
| 320 |
+
"entityType": "columns",
|
| 321 |
+
"table": "control_account"
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"type": "text",
|
| 325 |
+
"notNull": true,
|
| 326 |
+
"autoincrement": false,
|
| 327 |
+
"default": null,
|
| 328 |
+
"generated": null,
|
| 329 |
+
"name": "refresh_token",
|
| 330 |
+
"entityType": "columns",
|
| 331 |
+
"table": "control_account"
|
| 332 |
+
},
|
| 333 |
+
{
|
| 334 |
+
"type": "integer",
|
| 335 |
+
"notNull": false,
|
| 336 |
+
"autoincrement": false,
|
| 337 |
+
"default": null,
|
| 338 |
+
"generated": null,
|
| 339 |
+
"name": "token_expiry",
|
| 340 |
+
"entityType": "columns",
|
| 341 |
+
"table": "control_account"
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"type": "integer",
|
| 345 |
+
"notNull": true,
|
| 346 |
+
"autoincrement": false,
|
| 347 |
+
"default": null,
|
| 348 |
+
"generated": null,
|
| 349 |
+
"name": "active",
|
| 350 |
+
"entityType": "columns",
|
| 351 |
+
"table": "control_account"
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
"type": "integer",
|
| 355 |
+
"notNull": true,
|
| 356 |
+
"autoincrement": false,
|
| 357 |
+
"default": null,
|
| 358 |
+
"generated": null,
|
| 359 |
+
"name": "time_created",
|
| 360 |
+
"entityType": "columns",
|
| 361 |
+
"table": "control_account"
|
| 362 |
+
},
|
| 363 |
+
{
|
| 364 |
+
"type": "integer",
|
| 365 |
+
"notNull": true,
|
| 366 |
+
"autoincrement": false,
|
| 367 |
+
"default": null,
|
| 368 |
+
"generated": null,
|
| 369 |
+
"name": "time_updated",
|
| 370 |
+
"entityType": "columns",
|
| 371 |
+
"table": "control_account"
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"type": "text",
|
| 375 |
+
"notNull": false,
|
| 376 |
+
"autoincrement": false,
|
| 377 |
+
"default": null,
|
| 378 |
+
"generated": null,
|
| 379 |
+
"name": "id",
|
| 380 |
+
"entityType": "columns",
|
| 381 |
+
"table": "credential"
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"type": "text",
|
| 385 |
+
"notNull": false,
|
| 386 |
+
"autoincrement": false,
|
| 387 |
+
"default": null,
|
| 388 |
+
"generated": null,
|
| 389 |
+
"name": "integration_id",
|
| 390 |
+
"entityType": "columns",
|
| 391 |
+
"table": "credential"
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"type": "text",
|
| 395 |
+
"notNull": true,
|
| 396 |
+
"autoincrement": false,
|
| 397 |
+
"default": null,
|
| 398 |
+
"generated": null,
|
| 399 |
+
"name": "label",
|
| 400 |
+
"entityType": "columns",
|
| 401 |
+
"table": "credential"
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
"type": "text",
|
| 405 |
+
"notNull": true,
|
| 406 |
+
"autoincrement": false,
|
| 407 |
+
"default": null,
|
| 408 |
+
"generated": null,
|
| 409 |
+
"name": "value",
|
| 410 |
+
"entityType": "columns",
|
| 411 |
+
"table": "credential"
|
| 412 |
+
},
|
| 413 |
+
{
|
| 414 |
+
"type": "text",
|
| 415 |
+
"notNull": false,
|
| 416 |
+
"autoincrement": false,
|
| 417 |
+
"default": null,
|
| 418 |
+
"generated": null,
|
| 419 |
+
"name": "connector_id",
|
| 420 |
+
"entityType": "columns",
|
| 421 |
+
"table": "credential"
|
| 422 |
+
},
|
| 423 |
+
{
|
| 424 |
+
"type": "text",
|
| 425 |
+
"notNull": false,
|
| 426 |
+
"autoincrement": false,
|
| 427 |
+
"default": null,
|
| 428 |
+
"generated": null,
|
| 429 |
+
"name": "method_id",
|
| 430 |
+
"entityType": "columns",
|
| 431 |
+
"table": "credential"
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"type": "integer",
|
| 435 |
+
"notNull": false,
|
| 436 |
+
"autoincrement": false,
|
| 437 |
+
"default": null,
|
| 438 |
+
"generated": null,
|
| 439 |
+
"name": "active",
|
| 440 |
+
"entityType": "columns",
|
| 441 |
+
"table": "credential"
|
| 442 |
+
},
|
| 443 |
+
{
|
| 444 |
+
"type": "integer",
|
| 445 |
+
"notNull": true,
|
| 446 |
+
"autoincrement": false,
|
| 447 |
+
"default": null,
|
| 448 |
+
"generated": null,
|
| 449 |
+
"name": "time_created",
|
| 450 |
+
"entityType": "columns",
|
| 451 |
+
"table": "credential"
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"type": "integer",
|
| 455 |
+
"notNull": true,
|
| 456 |
+
"autoincrement": false,
|
| 457 |
+
"default": null,
|
| 458 |
+
"generated": null,
|
| 459 |
+
"name": "time_updated",
|
| 460 |
+
"entityType": "columns",
|
| 461 |
+
"table": "credential"
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"type": "text",
|
| 465 |
+
"notNull": false,
|
| 466 |
+
"autoincrement": false,
|
| 467 |
+
"default": null,
|
| 468 |
+
"generated": null,
|
| 469 |
+
"name": "aggregate_id",
|
| 470 |
+
"entityType": "columns",
|
| 471 |
+
"table": "event_sequence"
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"type": "integer",
|
| 475 |
+
"notNull": true,
|
| 476 |
+
"autoincrement": false,
|
| 477 |
+
"default": null,
|
| 478 |
+
"generated": null,
|
| 479 |
+
"name": "seq",
|
| 480 |
+
"entityType": "columns",
|
| 481 |
+
"table": "event_sequence"
|
| 482 |
+
},
|
| 483 |
+
{
|
| 484 |
+
"type": "text",
|
| 485 |
+
"notNull": false,
|
| 486 |
+
"autoincrement": false,
|
| 487 |
+
"default": null,
|
| 488 |
+
"generated": null,
|
| 489 |
+
"name": "owner_id",
|
| 490 |
+
"entityType": "columns",
|
| 491 |
+
"table": "event_sequence"
|
| 492 |
+
},
|
| 493 |
+
{
|
| 494 |
+
"type": "text",
|
| 495 |
+
"notNull": false,
|
| 496 |
+
"autoincrement": false,
|
| 497 |
+
"default": null,
|
| 498 |
+
"generated": null,
|
| 499 |
+
"name": "id",
|
| 500 |
+
"entityType": "columns",
|
| 501 |
+
"table": "event"
|
| 502 |
+
},
|
| 503 |
+
{
|
| 504 |
+
"type": "text",
|
| 505 |
+
"notNull": true,
|
| 506 |
+
"autoincrement": false,
|
| 507 |
+
"default": null,
|
| 508 |
+
"generated": null,
|
| 509 |
+
"name": "aggregate_id",
|
| 510 |
+
"entityType": "columns",
|
| 511 |
+
"table": "event"
|
| 512 |
+
},
|
| 513 |
+
{
|
| 514 |
+
"type": "integer",
|
| 515 |
+
"notNull": true,
|
| 516 |
+
"autoincrement": false,
|
| 517 |
+
"default": null,
|
| 518 |
+
"generated": null,
|
| 519 |
+
"name": "seq",
|
| 520 |
+
"entityType": "columns",
|
| 521 |
+
"table": "event"
|
| 522 |
+
},
|
| 523 |
+
{
|
| 524 |
+
"type": "text",
|
| 525 |
+
"notNull": true,
|
| 526 |
+
"autoincrement": false,
|
| 527 |
+
"default": null,
|
| 528 |
+
"generated": null,
|
| 529 |
+
"name": "type",
|
| 530 |
+
"entityType": "columns",
|
| 531 |
+
"table": "event"
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"type": "text",
|
| 535 |
+
"notNull": true,
|
| 536 |
+
"autoincrement": false,
|
| 537 |
+
"default": null,
|
| 538 |
+
"generated": null,
|
| 539 |
+
"name": "data",
|
| 540 |
+
"entityType": "columns",
|
| 541 |
+
"table": "event"
|
| 542 |
+
},
|
| 543 |
+
{
|
| 544 |
+
"type": "text",
|
| 545 |
+
"notNull": false,
|
| 546 |
+
"autoincrement": false,
|
| 547 |
+
"default": null,
|
| 548 |
+
"generated": null,
|
| 549 |
+
"name": "id",
|
| 550 |
+
"entityType": "columns",
|
| 551 |
+
"table": "permission"
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
"type": "text",
|
| 555 |
+
"notNull": true,
|
| 556 |
+
"autoincrement": false,
|
| 557 |
+
"default": null,
|
| 558 |
+
"generated": null,
|
| 559 |
+
"name": "project_id",
|
| 560 |
+
"entityType": "columns",
|
| 561 |
+
"table": "permission"
|
| 562 |
+
},
|
| 563 |
+
{
|
| 564 |
+
"type": "text",
|
| 565 |
+
"notNull": true,
|
| 566 |
+
"autoincrement": false,
|
| 567 |
+
"default": null,
|
| 568 |
+
"generated": null,
|
| 569 |
+
"name": "action",
|
| 570 |
+
"entityType": "columns",
|
| 571 |
+
"table": "permission"
|
| 572 |
+
},
|
| 573 |
+
{
|
| 574 |
+
"type": "text",
|
| 575 |
+
"notNull": true,
|
| 576 |
+
"autoincrement": false,
|
| 577 |
+
"default": null,
|
| 578 |
+
"generated": null,
|
| 579 |
+
"name": "resource",
|
| 580 |
+
"entityType": "columns",
|
| 581 |
+
"table": "permission"
|
| 582 |
+
},
|
| 583 |
+
{
|
| 584 |
+
"type": "integer",
|
| 585 |
+
"notNull": true,
|
| 586 |
+
"autoincrement": false,
|
| 587 |
+
"default": null,
|
| 588 |
+
"generated": null,
|
| 589 |
+
"name": "time_created",
|
| 590 |
+
"entityType": "columns",
|
| 591 |
+
"table": "permission"
|
| 592 |
+
},
|
| 593 |
+
{
|
| 594 |
+
"type": "integer",
|
| 595 |
+
"notNull": true,
|
| 596 |
+
"autoincrement": false,
|
| 597 |
+
"default": null,
|
| 598 |
+
"generated": null,
|
| 599 |
+
"name": "time_updated",
|
| 600 |
+
"entityType": "columns",
|
| 601 |
+
"table": "permission"
|
| 602 |
+
},
|
| 603 |
+
{
|
| 604 |
+
"type": "text",
|
| 605 |
+
"notNull": true,
|
| 606 |
+
"autoincrement": false,
|
| 607 |
+
"default": null,
|
| 608 |
+
"generated": null,
|
| 609 |
+
"name": "project_id",
|
| 610 |
+
"entityType": "columns",
|
| 611 |
+
"table": "project_directory"
|
| 612 |
+
},
|
| 613 |
+
{
|
| 614 |
+
"type": "text",
|
| 615 |
+
"notNull": true,
|
| 616 |
+
"autoincrement": false,
|
| 617 |
+
"default": null,
|
| 618 |
+
"generated": null,
|
| 619 |
+
"name": "directory",
|
| 620 |
+
"entityType": "columns",
|
| 621 |
+
"table": "project_directory"
|
| 622 |
+
},
|
| 623 |
+
{
|
| 624 |
+
"type": "text",
|
| 625 |
+
"notNull": false,
|
| 626 |
+
"autoincrement": false,
|
| 627 |
+
"default": null,
|
| 628 |
+
"generated": null,
|
| 629 |
+
"name": "type",
|
| 630 |
+
"entityType": "columns",
|
| 631 |
+
"table": "project_directory"
|
| 632 |
+
},
|
| 633 |
+
{
|
| 634 |
+
"type": "text",
|
| 635 |
+
"notNull": false,
|
| 636 |
+
"autoincrement": false,
|
| 637 |
+
"default": null,
|
| 638 |
+
"generated": null,
|
| 639 |
+
"name": "strategy",
|
| 640 |
+
"entityType": "columns",
|
| 641 |
+
"table": "project_directory"
|
| 642 |
+
},
|
| 643 |
+
{
|
| 644 |
+
"type": "integer",
|
| 645 |
+
"notNull": true,
|
| 646 |
+
"autoincrement": false,
|
| 647 |
+
"default": null,
|
| 648 |
+
"generated": null,
|
| 649 |
+
"name": "time_created",
|
| 650 |
+
"entityType": "columns",
|
| 651 |
+
"table": "project_directory"
|
| 652 |
+
},
|
| 653 |
+
{
|
| 654 |
+
"type": "text",
|
| 655 |
+
"notNull": false,
|
| 656 |
+
"autoincrement": false,
|
| 657 |
+
"default": null,
|
| 658 |
+
"generated": null,
|
| 659 |
+
"name": "id",
|
| 660 |
+
"entityType": "columns",
|
| 661 |
+
"table": "project"
|
| 662 |
+
},
|
| 663 |
+
{
|
| 664 |
+
"type": "text",
|
| 665 |
+
"notNull": true,
|
| 666 |
+
"autoincrement": false,
|
| 667 |
+
"default": null,
|
| 668 |
+
"generated": null,
|
| 669 |
+
"name": "worktree",
|
| 670 |
+
"entityType": "columns",
|
| 671 |
+
"table": "project"
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"type": "text",
|
| 675 |
+
"notNull": false,
|
| 676 |
+
"autoincrement": false,
|
| 677 |
+
"default": null,
|
| 678 |
+
"generated": null,
|
| 679 |
+
"name": "vcs",
|
| 680 |
+
"entityType": "columns",
|
| 681 |
+
"table": "project"
|
| 682 |
+
},
|
| 683 |
+
{
|
| 684 |
+
"type": "text",
|
| 685 |
+
"notNull": false,
|
| 686 |
+
"autoincrement": false,
|
| 687 |
+
"default": null,
|
| 688 |
+
"generated": null,
|
| 689 |
+
"name": "name",
|
| 690 |
+
"entityType": "columns",
|
| 691 |
+
"table": "project"
|
| 692 |
+
},
|
| 693 |
+
{
|
| 694 |
+
"type": "text",
|
| 695 |
+
"notNull": false,
|
| 696 |
+
"autoincrement": false,
|
| 697 |
+
"default": null,
|
| 698 |
+
"generated": null,
|
| 699 |
+
"name": "icon_url",
|
| 700 |
+
"entityType": "columns",
|
| 701 |
+
"table": "project"
|
| 702 |
+
},
|
| 703 |
+
{
|
| 704 |
+
"type": "text",
|
| 705 |
+
"notNull": false,
|
| 706 |
+
"autoincrement": false,
|
| 707 |
+
"default": null,
|
| 708 |
+
"generated": null,
|
| 709 |
+
"name": "icon_url_override",
|
| 710 |
+
"entityType": "columns",
|
| 711 |
+
"table": "project"
|
| 712 |
+
},
|
| 713 |
+
{
|
| 714 |
+
"type": "text",
|
| 715 |
+
"notNull": false,
|
| 716 |
+
"autoincrement": false,
|
| 717 |
+
"default": null,
|
| 718 |
+
"generated": null,
|
| 719 |
+
"name": "icon_color",
|
| 720 |
+
"entityType": "columns",
|
| 721 |
+
"table": "project"
|
| 722 |
+
},
|
| 723 |
+
{
|
| 724 |
+
"type": "integer",
|
| 725 |
+
"notNull": true,
|
| 726 |
+
"autoincrement": false,
|
| 727 |
+
"default": null,
|
| 728 |
+
"generated": null,
|
| 729 |
+
"name": "time_created",
|
| 730 |
+
"entityType": "columns",
|
| 731 |
+
"table": "project"
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"type": "integer",
|
| 735 |
+
"notNull": true,
|
| 736 |
+
"autoincrement": false,
|
| 737 |
+
"default": null,
|
| 738 |
+
"generated": null,
|
| 739 |
+
"name": "time_updated",
|
| 740 |
+
"entityType": "columns",
|
| 741 |
+
"table": "project"
|
| 742 |
+
},
|
| 743 |
+
{
|
| 744 |
+
"type": "integer",
|
| 745 |
+
"notNull": false,
|
| 746 |
+
"autoincrement": false,
|
| 747 |
+
"default": null,
|
| 748 |
+
"generated": null,
|
| 749 |
+
"name": "time_initialized",
|
| 750 |
+
"entityType": "columns",
|
| 751 |
+
"table": "project"
|
| 752 |
+
},
|
| 753 |
+
{
|
| 754 |
+
"type": "text",
|
| 755 |
+
"notNull": true,
|
| 756 |
+
"autoincrement": false,
|
| 757 |
+
"default": null,
|
| 758 |
+
"generated": null,
|
| 759 |
+
"name": "sandboxes",
|
| 760 |
+
"entityType": "columns",
|
| 761 |
+
"table": "project"
|
| 762 |
+
},
|
| 763 |
+
{
|
| 764 |
+
"type": "text",
|
| 765 |
+
"notNull": false,
|
| 766 |
+
"autoincrement": false,
|
| 767 |
+
"default": null,
|
| 768 |
+
"generated": null,
|
| 769 |
+
"name": "commands",
|
| 770 |
+
"entityType": "columns",
|
| 771 |
+
"table": "project"
|
| 772 |
+
},
|
| 773 |
+
{
|
| 774 |
+
"type": "text",
|
| 775 |
+
"notNull": false,
|
| 776 |
+
"autoincrement": false,
|
| 777 |
+
"default": null,
|
| 778 |
+
"generated": null,
|
| 779 |
+
"name": "id",
|
| 780 |
+
"entityType": "columns",
|
| 781 |
+
"table": "message"
|
| 782 |
+
},
|
| 783 |
+
{
|
| 784 |
+
"type": "text",
|
| 785 |
+
"notNull": true,
|
| 786 |
+
"autoincrement": false,
|
| 787 |
+
"default": null,
|
| 788 |
+
"generated": null,
|
| 789 |
+
"name": "session_id",
|
| 790 |
+
"entityType": "columns",
|
| 791 |
+
"table": "message"
|
| 792 |
+
},
|
| 793 |
+
{
|
| 794 |
+
"type": "integer",
|
| 795 |
+
"notNull": true,
|
| 796 |
+
"autoincrement": false,
|
| 797 |
+
"default": null,
|
| 798 |
+
"generated": null,
|
| 799 |
+
"name": "time_created",
|
| 800 |
+
"entityType": "columns",
|
| 801 |
+
"table": "message"
|
| 802 |
+
},
|
| 803 |
+
{
|
| 804 |
+
"type": "integer",
|
| 805 |
+
"notNull": true,
|
| 806 |
+
"autoincrement": false,
|
| 807 |
+
"default": null,
|
| 808 |
+
"generated": null,
|
| 809 |
+
"name": "time_updated",
|
| 810 |
+
"entityType": "columns",
|
| 811 |
+
"table": "message"
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"type": "text",
|
| 815 |
+
"notNull": true,
|
| 816 |
+
"autoincrement": false,
|
| 817 |
+
"default": null,
|
| 818 |
+
"generated": null,
|
| 819 |
+
"name": "data",
|
| 820 |
+
"entityType": "columns",
|
| 821 |
+
"table": "message"
|
| 822 |
+
},
|
| 823 |
+
{
|
| 824 |
+
"type": "text",
|
| 825 |
+
"notNull": false,
|
| 826 |
+
"autoincrement": false,
|
| 827 |
+
"default": null,
|
| 828 |
+
"generated": null,
|
| 829 |
+
"name": "id",
|
| 830 |
+
"entityType": "columns",
|
| 831 |
+
"table": "part"
|
| 832 |
+
},
|
| 833 |
+
{
|
| 834 |
+
"type": "text",
|
| 835 |
+
"notNull": true,
|
| 836 |
+
"autoincrement": false,
|
| 837 |
+
"default": null,
|
| 838 |
+
"generated": null,
|
| 839 |
+
"name": "message_id",
|
| 840 |
+
"entityType": "columns",
|
| 841 |
+
"table": "part"
|
| 842 |
+
},
|
| 843 |
+
{
|
| 844 |
+
"type": "text",
|
| 845 |
+
"notNull": true,
|
| 846 |
+
"autoincrement": false,
|
| 847 |
+
"default": null,
|
| 848 |
+
"generated": null,
|
| 849 |
+
"name": "session_id",
|
| 850 |
+
"entityType": "columns",
|
| 851 |
+
"table": "part"
|
| 852 |
+
},
|
| 853 |
+
{
|
| 854 |
+
"type": "integer",
|
| 855 |
+
"notNull": true,
|
| 856 |
+
"autoincrement": false,
|
| 857 |
+
"default": null,
|
| 858 |
+
"generated": null,
|
| 859 |
+
"name": "time_created",
|
| 860 |
+
"entityType": "columns",
|
| 861 |
+
"table": "part"
|
| 862 |
+
},
|
| 863 |
+
{
|
| 864 |
+
"type": "integer",
|
| 865 |
+
"notNull": true,
|
| 866 |
+
"autoincrement": false,
|
| 867 |
+
"default": null,
|
| 868 |
+
"generated": null,
|
| 869 |
+
"name": "time_updated",
|
| 870 |
+
"entityType": "columns",
|
| 871 |
+
"table": "part"
|
| 872 |
+
},
|
| 873 |
+
{
|
| 874 |
+
"type": "text",
|
| 875 |
+
"notNull": true,
|
| 876 |
+
"autoincrement": false,
|
| 877 |
+
"default": null,
|
| 878 |
+
"generated": null,
|
| 879 |
+
"name": "data",
|
| 880 |
+
"entityType": "columns",
|
| 881 |
+
"table": "part"
|
| 882 |
+
},
|
| 883 |
+
{
|
| 884 |
+
"type": "text",
|
| 885 |
+
"notNull": false,
|
| 886 |
+
"autoincrement": false,
|
| 887 |
+
"default": null,
|
| 888 |
+
"generated": null,
|
| 889 |
+
"name": "session_id",
|
| 890 |
+
"entityType": "columns",
|
| 891 |
+
"table": "session_context_epoch"
|
| 892 |
+
},
|
| 893 |
+
{
|
| 894 |
+
"type": "text",
|
| 895 |
+
"notNull": true,
|
| 896 |
+
"autoincrement": false,
|
| 897 |
+
"default": null,
|
| 898 |
+
"generated": null,
|
| 899 |
+
"name": "baseline",
|
| 900 |
+
"entityType": "columns",
|
| 901 |
+
"table": "session_context_epoch"
|
| 902 |
+
},
|
| 903 |
+
{
|
| 904 |
+
"type": "text",
|
| 905 |
+
"notNull": true,
|
| 906 |
+
"autoincrement": false,
|
| 907 |
+
"default": null,
|
| 908 |
+
"generated": null,
|
| 909 |
+
"name": "snapshot",
|
| 910 |
+
"entityType": "columns",
|
| 911 |
+
"table": "session_context_epoch"
|
| 912 |
+
},
|
| 913 |
+
{
|
| 914 |
+
"type": "integer",
|
| 915 |
+
"notNull": true,
|
| 916 |
+
"autoincrement": false,
|
| 917 |
+
"default": null,
|
| 918 |
+
"generated": null,
|
| 919 |
+
"name": "baseline_seq",
|
| 920 |
+
"entityType": "columns",
|
| 921 |
+
"table": "session_context_epoch"
|
| 922 |
+
},
|
| 923 |
+
{
|
| 924 |
+
"type": "text",
|
| 925 |
+
"notNull": false,
|
| 926 |
+
"autoincrement": false,
|
| 927 |
+
"default": null,
|
| 928 |
+
"generated": null,
|
| 929 |
+
"name": "id",
|
| 930 |
+
"entityType": "columns",
|
| 931 |
+
"table": "session_input"
|
| 932 |
+
},
|
| 933 |
+
{
|
| 934 |
+
"type": "text",
|
| 935 |
+
"notNull": true,
|
| 936 |
+
"autoincrement": false,
|
| 937 |
+
"default": null,
|
| 938 |
+
"generated": null,
|
| 939 |
+
"name": "session_id",
|
| 940 |
+
"entityType": "columns",
|
| 941 |
+
"table": "session_input"
|
| 942 |
+
},
|
| 943 |
+
{
|
| 944 |
+
"type": "text",
|
| 945 |
+
"notNull": true,
|
| 946 |
+
"autoincrement": false,
|
| 947 |
+
"default": null,
|
| 948 |
+
"generated": null,
|
| 949 |
+
"name": "prompt",
|
| 950 |
+
"entityType": "columns",
|
| 951 |
+
"table": "session_input"
|
| 952 |
+
},
|
| 953 |
+
{
|
| 954 |
+
"type": "text",
|
| 955 |
+
"notNull": true,
|
| 956 |
+
"autoincrement": false,
|
| 957 |
+
"default": null,
|
| 958 |
+
"generated": null,
|
| 959 |
+
"name": "delivery",
|
| 960 |
+
"entityType": "columns",
|
| 961 |
+
"table": "session_input"
|
| 962 |
+
},
|
| 963 |
+
{
|
| 964 |
+
"type": "integer",
|
| 965 |
+
"notNull": true,
|
| 966 |
+
"autoincrement": false,
|
| 967 |
+
"default": null,
|
| 968 |
+
"generated": null,
|
| 969 |
+
"name": "admitted_seq",
|
| 970 |
+
"entityType": "columns",
|
| 971 |
+
"table": "session_input"
|
| 972 |
+
},
|
| 973 |
+
{
|
| 974 |
+
"type": "integer",
|
| 975 |
+
"notNull": false,
|
| 976 |
+
"autoincrement": false,
|
| 977 |
+
"default": null,
|
| 978 |
+
"generated": null,
|
| 979 |
+
"name": "promoted_seq",
|
| 980 |
+
"entityType": "columns",
|
| 981 |
+
"table": "session_input"
|
| 982 |
+
},
|
| 983 |
+
{
|
| 984 |
+
"type": "integer",
|
| 985 |
+
"notNull": true,
|
| 986 |
+
"autoincrement": false,
|
| 987 |
+
"default": null,
|
| 988 |
+
"generated": null,
|
| 989 |
+
"name": "time_created",
|
| 990 |
+
"entityType": "columns",
|
| 991 |
+
"table": "session_input"
|
| 992 |
+
},
|
| 993 |
+
{
|
| 994 |
+
"type": "text",
|
| 995 |
+
"notNull": false,
|
| 996 |
+
"autoincrement": false,
|
| 997 |
+
"default": null,
|
| 998 |
+
"generated": null,
|
| 999 |
+
"name": "id",
|
| 1000 |
+
"entityType": "columns",
|
| 1001 |
+
"table": "session_message"
|
| 1002 |
+
},
|
| 1003 |
+
{
|
| 1004 |
+
"type": "text",
|
| 1005 |
+
"notNull": true,
|
| 1006 |
+
"autoincrement": false,
|
| 1007 |
+
"default": null,
|
| 1008 |
+
"generated": null,
|
| 1009 |
+
"name": "session_id",
|
| 1010 |
+
"entityType": "columns",
|
| 1011 |
+
"table": "session_message"
|
| 1012 |
+
},
|
| 1013 |
+
{
|
| 1014 |
+
"type": "text",
|
| 1015 |
+
"notNull": true,
|
| 1016 |
+
"autoincrement": false,
|
| 1017 |
+
"default": null,
|
| 1018 |
+
"generated": null,
|
| 1019 |
+
"name": "type",
|
| 1020 |
+
"entityType": "columns",
|
| 1021 |
+
"table": "session_message"
|
| 1022 |
+
},
|
| 1023 |
+
{
|
| 1024 |
+
"type": "integer",
|
| 1025 |
+
"notNull": true,
|
| 1026 |
+
"autoincrement": false,
|
| 1027 |
+
"default": null,
|
| 1028 |
+
"generated": null,
|
| 1029 |
+
"name": "seq",
|
| 1030 |
+
"entityType": "columns",
|
| 1031 |
+
"table": "session_message"
|
| 1032 |
+
},
|
| 1033 |
+
{
|
| 1034 |
+
"type": "integer",
|
| 1035 |
+
"notNull": true,
|
| 1036 |
+
"autoincrement": false,
|
| 1037 |
+
"default": null,
|
| 1038 |
+
"generated": null,
|
| 1039 |
+
"name": "time_created",
|
| 1040 |
+
"entityType": "columns",
|
| 1041 |
+
"table": "session_message"
|
| 1042 |
+
},
|
| 1043 |
+
{
|
| 1044 |
+
"type": "integer",
|
| 1045 |
+
"notNull": true,
|
| 1046 |
+
"autoincrement": false,
|
| 1047 |
+
"default": null,
|
| 1048 |
+
"generated": null,
|
| 1049 |
+
"name": "time_updated",
|
| 1050 |
+
"entityType": "columns",
|
| 1051 |
+
"table": "session_message"
|
| 1052 |
+
},
|
| 1053 |
+
{
|
| 1054 |
+
"type": "text",
|
| 1055 |
+
"notNull": true,
|
| 1056 |
+
"autoincrement": false,
|
| 1057 |
+
"default": null,
|
| 1058 |
+
"generated": null,
|
| 1059 |
+
"name": "data",
|
| 1060 |
+
"entityType": "columns",
|
| 1061 |
+
"table": "session_message"
|
| 1062 |
+
},
|
| 1063 |
+
{
|
| 1064 |
+
"type": "text",
|
| 1065 |
+
"notNull": false,
|
| 1066 |
+
"autoincrement": false,
|
| 1067 |
+
"default": null,
|
| 1068 |
+
"generated": null,
|
| 1069 |
+
"name": "id",
|
| 1070 |
+
"entityType": "columns",
|
| 1071 |
+
"table": "session"
|
| 1072 |
+
},
|
| 1073 |
+
{
|
| 1074 |
+
"type": "text",
|
| 1075 |
+
"notNull": true,
|
| 1076 |
+
"autoincrement": false,
|
| 1077 |
+
"default": null,
|
| 1078 |
+
"generated": null,
|
| 1079 |
+
"name": "project_id",
|
| 1080 |
+
"entityType": "columns",
|
| 1081 |
+
"table": "session"
|
| 1082 |
+
},
|
| 1083 |
+
{
|
| 1084 |
+
"type": "text",
|
| 1085 |
+
"notNull": false,
|
| 1086 |
+
"autoincrement": false,
|
| 1087 |
+
"default": null,
|
| 1088 |
+
"generated": null,
|
| 1089 |
+
"name": "workspace_id",
|
| 1090 |
+
"entityType": "columns",
|
| 1091 |
+
"table": "session"
|
| 1092 |
+
},
|
| 1093 |
+
{
|
| 1094 |
+
"type": "text",
|
| 1095 |
+
"notNull": false,
|
| 1096 |
+
"autoincrement": false,
|
| 1097 |
+
"default": null,
|
| 1098 |
+
"generated": null,
|
| 1099 |
+
"name": "parent_id",
|
| 1100 |
+
"entityType": "columns",
|
| 1101 |
+
"table": "session"
|
| 1102 |
+
},
|
| 1103 |
+
{
|
| 1104 |
+
"type": "text",
|
| 1105 |
+
"notNull": true,
|
| 1106 |
+
"autoincrement": false,
|
| 1107 |
+
"default": null,
|
| 1108 |
+
"generated": null,
|
| 1109 |
+
"name": "slug",
|
| 1110 |
+
"entityType": "columns",
|
| 1111 |
+
"table": "session"
|
| 1112 |
+
},
|
| 1113 |
+
{
|
| 1114 |
+
"type": "text",
|
| 1115 |
+
"notNull": true,
|
| 1116 |
+
"autoincrement": false,
|
| 1117 |
+
"default": null,
|
| 1118 |
+
"generated": null,
|
| 1119 |
+
"name": "directory",
|
| 1120 |
+
"entityType": "columns",
|
| 1121 |
+
"table": "session"
|
| 1122 |
+
},
|
| 1123 |
+
{
|
| 1124 |
+
"type": "text",
|
| 1125 |
+
"notNull": false,
|
| 1126 |
+
"autoincrement": false,
|
| 1127 |
+
"default": null,
|
| 1128 |
+
"generated": null,
|
| 1129 |
+
"name": "path",
|
| 1130 |
+
"entityType": "columns",
|
| 1131 |
+
"table": "session"
|
| 1132 |
+
},
|
| 1133 |
+
{
|
| 1134 |
+
"type": "text",
|
| 1135 |
+
"notNull": true,
|
| 1136 |
+
"autoincrement": false,
|
| 1137 |
+
"default": null,
|
| 1138 |
+
"generated": null,
|
| 1139 |
+
"name": "title",
|
| 1140 |
+
"entityType": "columns",
|
| 1141 |
+
"table": "session"
|
| 1142 |
+
},
|
| 1143 |
+
{
|
| 1144 |
+
"type": "text",
|
| 1145 |
+
"notNull": true,
|
| 1146 |
+
"autoincrement": false,
|
| 1147 |
+
"default": null,
|
| 1148 |
+
"generated": null,
|
| 1149 |
+
"name": "version",
|
| 1150 |
+
"entityType": "columns",
|
| 1151 |
+
"table": "session"
|
| 1152 |
+
},
|
| 1153 |
+
{
|
| 1154 |
+
"type": "text",
|
| 1155 |
+
"notNull": false,
|
| 1156 |
+
"autoincrement": false,
|
| 1157 |
+
"default": null,
|
| 1158 |
+
"generated": null,
|
| 1159 |
+
"name": "share_url",
|
| 1160 |
+
"entityType": "columns",
|
| 1161 |
+
"table": "session"
|
| 1162 |
+
},
|
| 1163 |
+
{
|
| 1164 |
+
"type": "integer",
|
| 1165 |
+
"notNull": false,
|
| 1166 |
+
"autoincrement": false,
|
| 1167 |
+
"default": null,
|
| 1168 |
+
"generated": null,
|
| 1169 |
+
"name": "summary_additions",
|
| 1170 |
+
"entityType": "columns",
|
| 1171 |
+
"table": "session"
|
| 1172 |
+
},
|
| 1173 |
+
{
|
| 1174 |
+
"type": "integer",
|
| 1175 |
+
"notNull": false,
|
| 1176 |
+
"autoincrement": false,
|
| 1177 |
+
"default": null,
|
| 1178 |
+
"generated": null,
|
| 1179 |
+
"name": "summary_deletions",
|
| 1180 |
+
"entityType": "columns",
|
| 1181 |
+
"table": "session"
|
| 1182 |
+
},
|
| 1183 |
+
{
|
| 1184 |
+
"type": "integer",
|
| 1185 |
+
"notNull": false,
|
| 1186 |
+
"autoincrement": false,
|
| 1187 |
+
"default": null,
|
| 1188 |
+
"generated": null,
|
| 1189 |
+
"name": "summary_files",
|
| 1190 |
+
"entityType": "columns",
|
| 1191 |
+
"table": "session"
|
| 1192 |
+
},
|
| 1193 |
+
{
|
| 1194 |
+
"type": "text",
|
| 1195 |
+
"notNull": false,
|
| 1196 |
+
"autoincrement": false,
|
| 1197 |
+
"default": null,
|
| 1198 |
+
"generated": null,
|
| 1199 |
+
"name": "summary_diffs",
|
| 1200 |
+
"entityType": "columns",
|
| 1201 |
+
"table": "session"
|
| 1202 |
+
},
|
| 1203 |
+
{
|
| 1204 |
+
"type": "text",
|
| 1205 |
+
"notNull": false,
|
| 1206 |
+
"autoincrement": false,
|
| 1207 |
+
"default": null,
|
| 1208 |
+
"generated": null,
|
| 1209 |
+
"name": "metadata",
|
| 1210 |
+
"entityType": "columns",
|
| 1211 |
+
"table": "session"
|
| 1212 |
+
},
|
| 1213 |
+
{
|
| 1214 |
+
"type": "real",
|
| 1215 |
+
"notNull": true,
|
| 1216 |
+
"autoincrement": false,
|
| 1217 |
+
"default": "0",
|
| 1218 |
+
"generated": null,
|
| 1219 |
+
"name": "cost",
|
| 1220 |
+
"entityType": "columns",
|
| 1221 |
+
"table": "session"
|
| 1222 |
+
},
|
| 1223 |
+
{
|
| 1224 |
+
"type": "integer",
|
| 1225 |
+
"notNull": true,
|
| 1226 |
+
"autoincrement": false,
|
| 1227 |
+
"default": "0",
|
| 1228 |
+
"generated": null,
|
| 1229 |
+
"name": "tokens_input",
|
| 1230 |
+
"entityType": "columns",
|
| 1231 |
+
"table": "session"
|
| 1232 |
+
},
|
| 1233 |
+
{
|
| 1234 |
+
"type": "integer",
|
| 1235 |
+
"notNull": true,
|
| 1236 |
+
"autoincrement": false,
|
| 1237 |
+
"default": "0",
|
| 1238 |
+
"generated": null,
|
| 1239 |
+
"name": "tokens_output",
|
| 1240 |
+
"entityType": "columns",
|
| 1241 |
+
"table": "session"
|
| 1242 |
+
},
|
| 1243 |
+
{
|
| 1244 |
+
"type": "integer",
|
| 1245 |
+
"notNull": true,
|
| 1246 |
+
"autoincrement": false,
|
| 1247 |
+
"default": "0",
|
| 1248 |
+
"generated": null,
|
| 1249 |
+
"name": "tokens_reasoning",
|
| 1250 |
+
"entityType": "columns",
|
| 1251 |
+
"table": "session"
|
| 1252 |
+
},
|
| 1253 |
+
{
|
| 1254 |
+
"type": "integer",
|
| 1255 |
+
"notNull": true,
|
| 1256 |
+
"autoincrement": false,
|
| 1257 |
+
"default": "0",
|
| 1258 |
+
"generated": null,
|
| 1259 |
+
"name": "tokens_cache_read",
|
| 1260 |
+
"entityType": "columns",
|
| 1261 |
+
"table": "session"
|
| 1262 |
+
},
|
| 1263 |
+
{
|
| 1264 |
+
"type": "integer",
|
| 1265 |
+
"notNull": true,
|
| 1266 |
+
"autoincrement": false,
|
| 1267 |
+
"default": "0",
|
| 1268 |
+
"generated": null,
|
| 1269 |
+
"name": "tokens_cache_write",
|
| 1270 |
+
"entityType": "columns",
|
| 1271 |
+
"table": "session"
|
| 1272 |
+
},
|
| 1273 |
+
{
|
| 1274 |
+
"type": "text",
|
| 1275 |
+
"notNull": false,
|
| 1276 |
+
"autoincrement": false,
|
| 1277 |
+
"default": null,
|
| 1278 |
+
"generated": null,
|
| 1279 |
+
"name": "revert",
|
| 1280 |
+
"entityType": "columns",
|
| 1281 |
+
"table": "session"
|
| 1282 |
+
},
|
| 1283 |
+
{
|
| 1284 |
+
"type": "text",
|
| 1285 |
+
"notNull": false,
|
| 1286 |
+
"autoincrement": false,
|
| 1287 |
+
"default": null,
|
| 1288 |
+
"generated": null,
|
| 1289 |
+
"name": "permission",
|
| 1290 |
+
"entityType": "columns",
|
| 1291 |
+
"table": "session"
|
| 1292 |
+
},
|
| 1293 |
+
{
|
| 1294 |
+
"type": "text",
|
| 1295 |
+
"notNull": false,
|
| 1296 |
+
"autoincrement": false,
|
| 1297 |
+
"default": null,
|
| 1298 |
+
"generated": null,
|
| 1299 |
+
"name": "agent",
|
| 1300 |
+
"entityType": "columns",
|
| 1301 |
+
"table": "session"
|
| 1302 |
+
},
|
| 1303 |
+
{
|
| 1304 |
+
"type": "text",
|
| 1305 |
+
"notNull": false,
|
| 1306 |
+
"autoincrement": false,
|
| 1307 |
+
"default": null,
|
| 1308 |
+
"generated": null,
|
| 1309 |
+
"name": "model",
|
| 1310 |
+
"entityType": "columns",
|
| 1311 |
+
"table": "session"
|
| 1312 |
+
},
|
| 1313 |
+
{
|
| 1314 |
+
"type": "integer",
|
| 1315 |
+
"notNull": true,
|
| 1316 |
+
"autoincrement": false,
|
| 1317 |
+
"default": null,
|
| 1318 |
+
"generated": null,
|
| 1319 |
+
"name": "time_created",
|
| 1320 |
+
"entityType": "columns",
|
| 1321 |
+
"table": "session"
|
| 1322 |
+
},
|
| 1323 |
+
{
|
| 1324 |
+
"type": "integer",
|
| 1325 |
+
"notNull": true,
|
| 1326 |
+
"autoincrement": false,
|
| 1327 |
+
"default": null,
|
| 1328 |
+
"generated": null,
|
| 1329 |
+
"name": "time_updated",
|
| 1330 |
+
"entityType": "columns",
|
| 1331 |
+
"table": "session"
|
| 1332 |
+
},
|
| 1333 |
+
{
|
| 1334 |
+
"type": "integer",
|
| 1335 |
+
"notNull": false,
|
| 1336 |
+
"autoincrement": false,
|
| 1337 |
+
"default": null,
|
| 1338 |
+
"generated": null,
|
| 1339 |
+
"name": "time_compacting",
|
| 1340 |
+
"entityType": "columns",
|
| 1341 |
+
"table": "session"
|
| 1342 |
+
},
|
| 1343 |
+
{
|
| 1344 |
+
"type": "integer",
|
| 1345 |
+
"notNull": false,
|
| 1346 |
+
"autoincrement": false,
|
| 1347 |
+
"default": null,
|
| 1348 |
+
"generated": null,
|
| 1349 |
+
"name": "time_archived",
|
| 1350 |
+
"entityType": "columns",
|
| 1351 |
+
"table": "session"
|
| 1352 |
+
},
|
| 1353 |
+
{
|
| 1354 |
+
"type": "text",
|
| 1355 |
+
"notNull": true,
|
| 1356 |
+
"autoincrement": false,
|
| 1357 |
+
"default": null,
|
| 1358 |
+
"generated": null,
|
| 1359 |
+
"name": "session_id",
|
| 1360 |
+
"entityType": "columns",
|
| 1361 |
+
"table": "todo"
|
| 1362 |
+
},
|
| 1363 |
+
{
|
| 1364 |
+
"type": "text",
|
| 1365 |
+
"notNull": true,
|
| 1366 |
+
"autoincrement": false,
|
| 1367 |
+
"default": null,
|
| 1368 |
+
"generated": null,
|
| 1369 |
+
"name": "content",
|
| 1370 |
+
"entityType": "columns",
|
| 1371 |
+
"table": "todo"
|
| 1372 |
+
},
|
| 1373 |
+
{
|
| 1374 |
+
"type": "text",
|
| 1375 |
+
"notNull": true,
|
| 1376 |
+
"autoincrement": false,
|
| 1377 |
+
"default": null,
|
| 1378 |
+
"generated": null,
|
| 1379 |
+
"name": "status",
|
| 1380 |
+
"entityType": "columns",
|
| 1381 |
+
"table": "todo"
|
| 1382 |
+
},
|
| 1383 |
+
{
|
| 1384 |
+
"type": "text",
|
| 1385 |
+
"notNull": true,
|
| 1386 |
+
"autoincrement": false,
|
| 1387 |
+
"default": null,
|
| 1388 |
+
"generated": null,
|
| 1389 |
+
"name": "priority",
|
| 1390 |
+
"entityType": "columns",
|
| 1391 |
+
"table": "todo"
|
| 1392 |
+
},
|
| 1393 |
+
{
|
| 1394 |
+
"type": "integer",
|
| 1395 |
+
"notNull": true,
|
| 1396 |
+
"autoincrement": false,
|
| 1397 |
+
"default": null,
|
| 1398 |
+
"generated": null,
|
| 1399 |
+
"name": "position",
|
| 1400 |
+
"entityType": "columns",
|
| 1401 |
+
"table": "todo"
|
| 1402 |
+
},
|
| 1403 |
+
{
|
| 1404 |
+
"type": "integer",
|
| 1405 |
+
"notNull": true,
|
| 1406 |
+
"autoincrement": false,
|
| 1407 |
+
"default": null,
|
| 1408 |
+
"generated": null,
|
| 1409 |
+
"name": "time_created",
|
| 1410 |
+
"entityType": "columns",
|
| 1411 |
+
"table": "todo"
|
| 1412 |
+
},
|
| 1413 |
+
{
|
| 1414 |
+
"type": "integer",
|
| 1415 |
+
"notNull": true,
|
| 1416 |
+
"autoincrement": false,
|
| 1417 |
+
"default": null,
|
| 1418 |
+
"generated": null,
|
| 1419 |
+
"name": "time_updated",
|
| 1420 |
+
"entityType": "columns",
|
| 1421 |
+
"table": "todo"
|
| 1422 |
+
},
|
| 1423 |
+
{
|
| 1424 |
+
"type": "text",
|
| 1425 |
+
"notNull": false,
|
| 1426 |
+
"autoincrement": false,
|
| 1427 |
+
"default": null,
|
| 1428 |
+
"generated": null,
|
| 1429 |
+
"name": "session_id",
|
| 1430 |
+
"entityType": "columns",
|
| 1431 |
+
"table": "session_share"
|
| 1432 |
+
},
|
| 1433 |
+
{
|
| 1434 |
+
"type": "text",
|
| 1435 |
+
"notNull": true,
|
| 1436 |
+
"autoincrement": false,
|
| 1437 |
+
"default": null,
|
| 1438 |
+
"generated": null,
|
| 1439 |
+
"name": "id",
|
| 1440 |
+
"entityType": "columns",
|
| 1441 |
+
"table": "session_share"
|
| 1442 |
+
},
|
| 1443 |
+
{
|
| 1444 |
+
"type": "text",
|
| 1445 |
+
"notNull": true,
|
| 1446 |
+
"autoincrement": false,
|
| 1447 |
+
"default": null,
|
| 1448 |
+
"generated": null,
|
| 1449 |
+
"name": "secret",
|
| 1450 |
+
"entityType": "columns",
|
| 1451 |
+
"table": "session_share"
|
| 1452 |
+
},
|
| 1453 |
+
{
|
| 1454 |
+
"type": "text",
|
| 1455 |
+
"notNull": true,
|
| 1456 |
+
"autoincrement": false,
|
| 1457 |
+
"default": null,
|
| 1458 |
+
"generated": null,
|
| 1459 |
+
"name": "url",
|
| 1460 |
+
"entityType": "columns",
|
| 1461 |
+
"table": "session_share"
|
| 1462 |
+
},
|
| 1463 |
+
{
|
| 1464 |
+
"type": "integer",
|
| 1465 |
+
"notNull": true,
|
| 1466 |
+
"autoincrement": false,
|
| 1467 |
+
"default": null,
|
| 1468 |
+
"generated": null,
|
| 1469 |
+
"name": "time_created",
|
| 1470 |
+
"entityType": "columns",
|
| 1471 |
+
"table": "session_share"
|
| 1472 |
+
},
|
| 1473 |
+
{
|
| 1474 |
+
"type": "integer",
|
| 1475 |
+
"notNull": true,
|
| 1476 |
+
"autoincrement": false,
|
| 1477 |
+
"default": null,
|
| 1478 |
+
"generated": null,
|
| 1479 |
+
"name": "time_updated",
|
| 1480 |
+
"entityType": "columns",
|
| 1481 |
+
"table": "session_share"
|
| 1482 |
+
},
|
| 1483 |
+
{
|
| 1484 |
+
"columns": ["project_id"],
|
| 1485 |
+
"tableTo": "project",
|
| 1486 |
+
"columnsTo": ["id"],
|
| 1487 |
+
"onUpdate": "NO ACTION",
|
| 1488 |
+
"onDelete": "CASCADE",
|
| 1489 |
+
"nameExplicit": false,
|
| 1490 |
+
"name": "fk_workspace_project_id_project_id_fk",
|
| 1491 |
+
"entityType": "fks",
|
| 1492 |
+
"table": "workspace"
|
| 1493 |
+
},
|
| 1494 |
+
{
|
| 1495 |
+
"columns": ["active_account_id"],
|
| 1496 |
+
"tableTo": "account",
|
| 1497 |
+
"columnsTo": ["id"],
|
| 1498 |
+
"onUpdate": "NO ACTION",
|
| 1499 |
+
"onDelete": "SET NULL",
|
| 1500 |
+
"nameExplicit": false,
|
| 1501 |
+
"name": "fk_account_state_active_account_id_account_id_fk",
|
| 1502 |
+
"entityType": "fks",
|
| 1503 |
+
"table": "account_state"
|
| 1504 |
+
},
|
| 1505 |
+
{
|
| 1506 |
+
"columns": ["aggregate_id"],
|
| 1507 |
+
"tableTo": "event_sequence",
|
| 1508 |
+
"columnsTo": ["aggregate_id"],
|
| 1509 |
+
"onUpdate": "NO ACTION",
|
| 1510 |
+
"onDelete": "CASCADE",
|
| 1511 |
+
"nameExplicit": false,
|
| 1512 |
+
"name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk",
|
| 1513 |
+
"entityType": "fks",
|
| 1514 |
+
"table": "event"
|
| 1515 |
+
},
|
| 1516 |
+
{
|
| 1517 |
+
"columns": ["project_id"],
|
| 1518 |
+
"tableTo": "project",
|
| 1519 |
+
"columnsTo": ["id"],
|
| 1520 |
+
"onUpdate": "NO ACTION",
|
| 1521 |
+
"onDelete": "CASCADE",
|
| 1522 |
+
"nameExplicit": false,
|
| 1523 |
+
"name": "fk_permission_project_id_project_id_fk",
|
| 1524 |
+
"entityType": "fks",
|
| 1525 |
+
"table": "permission"
|
| 1526 |
+
},
|
| 1527 |
+
{
|
| 1528 |
+
"columns": ["project_id"],
|
| 1529 |
+
"tableTo": "project",
|
| 1530 |
+
"columnsTo": ["id"],
|
| 1531 |
+
"onUpdate": "NO ACTION",
|
| 1532 |
+
"onDelete": "CASCADE",
|
| 1533 |
+
"nameExplicit": false,
|
| 1534 |
+
"name": "fk_project_directory_project_id_project_id_fk",
|
| 1535 |
+
"entityType": "fks",
|
| 1536 |
+
"table": "project_directory"
|
| 1537 |
+
},
|
| 1538 |
+
{
|
| 1539 |
+
"columns": ["session_id"],
|
| 1540 |
+
"tableTo": "session",
|
| 1541 |
+
"columnsTo": ["id"],
|
| 1542 |
+
"onUpdate": "NO ACTION",
|
| 1543 |
+
"onDelete": "CASCADE",
|
| 1544 |
+
"nameExplicit": false,
|
| 1545 |
+
"name": "fk_message_session_id_session_id_fk",
|
| 1546 |
+
"entityType": "fks",
|
| 1547 |
+
"table": "message"
|
| 1548 |
+
},
|
| 1549 |
+
{
|
| 1550 |
+
"columns": ["message_id"],
|
| 1551 |
+
"tableTo": "message",
|
| 1552 |
+
"columnsTo": ["id"],
|
| 1553 |
+
"onUpdate": "NO ACTION",
|
| 1554 |
+
"onDelete": "CASCADE",
|
| 1555 |
+
"nameExplicit": false,
|
| 1556 |
+
"name": "fk_part_message_id_message_id_fk",
|
| 1557 |
+
"entityType": "fks",
|
| 1558 |
+
"table": "part"
|
| 1559 |
+
},
|
| 1560 |
+
{
|
| 1561 |
+
"columns": ["session_id"],
|
| 1562 |
+
"tableTo": "session",
|
| 1563 |
+
"columnsTo": ["id"],
|
| 1564 |
+
"onUpdate": "NO ACTION",
|
| 1565 |
+
"onDelete": "CASCADE",
|
| 1566 |
+
"nameExplicit": false,
|
| 1567 |
+
"name": "fk_session_context_epoch_session_id_session_id_fk",
|
| 1568 |
+
"entityType": "fks",
|
| 1569 |
+
"table": "session_context_epoch"
|
| 1570 |
+
},
|
| 1571 |
+
{
|
| 1572 |
+
"columns": ["session_id"],
|
| 1573 |
+
"tableTo": "session",
|
| 1574 |
+
"columnsTo": ["id"],
|
| 1575 |
+
"onUpdate": "NO ACTION",
|
| 1576 |
+
"onDelete": "CASCADE",
|
| 1577 |
+
"nameExplicit": false,
|
| 1578 |
+
"name": "fk_session_input_session_id_session_id_fk",
|
| 1579 |
+
"entityType": "fks",
|
| 1580 |
+
"table": "session_input"
|
| 1581 |
+
},
|
| 1582 |
+
{
|
| 1583 |
+
"columns": ["session_id"],
|
| 1584 |
+
"tableTo": "session",
|
| 1585 |
+
"columnsTo": ["id"],
|
| 1586 |
+
"onUpdate": "NO ACTION",
|
| 1587 |
+
"onDelete": "CASCADE",
|
| 1588 |
+
"nameExplicit": false,
|
| 1589 |
+
"name": "fk_session_message_session_id_session_id_fk",
|
| 1590 |
+
"entityType": "fks",
|
| 1591 |
+
"table": "session_message"
|
| 1592 |
+
},
|
| 1593 |
+
{
|
| 1594 |
+
"columns": ["project_id"],
|
| 1595 |
+
"tableTo": "project",
|
| 1596 |
+
"columnsTo": ["id"],
|
| 1597 |
+
"onUpdate": "NO ACTION",
|
| 1598 |
+
"onDelete": "CASCADE",
|
| 1599 |
+
"nameExplicit": false,
|
| 1600 |
+
"name": "fk_session_project_id_project_id_fk",
|
| 1601 |
+
"entityType": "fks",
|
| 1602 |
+
"table": "session"
|
| 1603 |
+
},
|
| 1604 |
+
{
|
| 1605 |
+
"columns": ["session_id"],
|
| 1606 |
+
"tableTo": "session",
|
| 1607 |
+
"columnsTo": ["id"],
|
| 1608 |
+
"onUpdate": "NO ACTION",
|
| 1609 |
+
"onDelete": "CASCADE",
|
| 1610 |
+
"nameExplicit": false,
|
| 1611 |
+
"name": "fk_todo_session_id_session_id_fk",
|
| 1612 |
+
"entityType": "fks",
|
| 1613 |
+
"table": "todo"
|
| 1614 |
+
},
|
| 1615 |
+
{
|
| 1616 |
+
"columns": ["session_id"],
|
| 1617 |
+
"tableTo": "session",
|
| 1618 |
+
"columnsTo": ["id"],
|
| 1619 |
+
"onUpdate": "NO ACTION",
|
| 1620 |
+
"onDelete": "CASCADE",
|
| 1621 |
+
"nameExplicit": false,
|
| 1622 |
+
"name": "fk_session_share_session_id_session_id_fk",
|
| 1623 |
+
"entityType": "fks",
|
| 1624 |
+
"table": "session_share"
|
| 1625 |
+
},
|
| 1626 |
+
{
|
| 1627 |
+
"columns": ["email", "url"],
|
| 1628 |
+
"nameExplicit": false,
|
| 1629 |
+
"name": "control_account_pk",
|
| 1630 |
+
"entityType": "pks",
|
| 1631 |
+
"table": "control_account"
|
| 1632 |
+
},
|
| 1633 |
+
{
|
| 1634 |
+
"columns": ["project_id", "directory"],
|
| 1635 |
+
"nameExplicit": false,
|
| 1636 |
+
"name": "project_directory_pk",
|
| 1637 |
+
"entityType": "pks",
|
| 1638 |
+
"table": "project_directory"
|
| 1639 |
+
},
|
| 1640 |
+
{
|
| 1641 |
+
"columns": ["session_id", "position"],
|
| 1642 |
+
"nameExplicit": false,
|
| 1643 |
+
"name": "todo_pk",
|
| 1644 |
+
"entityType": "pks",
|
| 1645 |
+
"table": "todo"
|
| 1646 |
+
},
|
| 1647 |
+
{
|
| 1648 |
+
"columns": ["id"],
|
| 1649 |
+
"nameExplicit": false,
|
| 1650 |
+
"name": "workspace_pk",
|
| 1651 |
+
"table": "workspace",
|
| 1652 |
+
"entityType": "pks"
|
| 1653 |
+
},
|
| 1654 |
+
{
|
| 1655 |
+
"columns": ["name"],
|
| 1656 |
+
"nameExplicit": false,
|
| 1657 |
+
"name": "data_migration_pk",
|
| 1658 |
+
"table": "data_migration",
|
| 1659 |
+
"entityType": "pks"
|
| 1660 |
+
},
|
| 1661 |
+
{
|
| 1662 |
+
"columns": ["id"],
|
| 1663 |
+
"nameExplicit": false,
|
| 1664 |
+
"name": "account_state_pk",
|
| 1665 |
+
"table": "account_state",
|
| 1666 |
+
"entityType": "pks"
|
| 1667 |
+
},
|
| 1668 |
+
{
|
| 1669 |
+
"columns": ["id"],
|
| 1670 |
+
"nameExplicit": false,
|
| 1671 |
+
"name": "account_pk",
|
| 1672 |
+
"table": "account",
|
| 1673 |
+
"entityType": "pks"
|
| 1674 |
+
},
|
| 1675 |
+
{
|
| 1676 |
+
"columns": ["id"],
|
| 1677 |
+
"nameExplicit": false,
|
| 1678 |
+
"name": "credential_pk",
|
| 1679 |
+
"table": "credential",
|
| 1680 |
+
"entityType": "pks"
|
| 1681 |
+
},
|
| 1682 |
+
{
|
| 1683 |
+
"columns": ["aggregate_id"],
|
| 1684 |
+
"nameExplicit": false,
|
| 1685 |
+
"name": "event_sequence_pk",
|
| 1686 |
+
"table": "event_sequence",
|
| 1687 |
+
"entityType": "pks"
|
| 1688 |
+
},
|
| 1689 |
+
{
|
| 1690 |
+
"columns": ["id"],
|
| 1691 |
+
"nameExplicit": false,
|
| 1692 |
+
"name": "event_pk",
|
| 1693 |
+
"table": "event",
|
| 1694 |
+
"entityType": "pks"
|
| 1695 |
+
},
|
| 1696 |
+
{
|
| 1697 |
+
"columns": ["id"],
|
| 1698 |
+
"nameExplicit": false,
|
| 1699 |
+
"name": "permission_pk",
|
| 1700 |
+
"table": "permission",
|
| 1701 |
+
"entityType": "pks"
|
| 1702 |
+
},
|
| 1703 |
+
{
|
| 1704 |
+
"columns": ["id"],
|
| 1705 |
+
"nameExplicit": false,
|
| 1706 |
+
"name": "project_pk",
|
| 1707 |
+
"table": "project",
|
| 1708 |
+
"entityType": "pks"
|
| 1709 |
+
},
|
| 1710 |
+
{
|
| 1711 |
+
"columns": ["id"],
|
| 1712 |
+
"nameExplicit": false,
|
| 1713 |
+
"name": "message_pk",
|
| 1714 |
+
"table": "message",
|
| 1715 |
+
"entityType": "pks"
|
| 1716 |
+
},
|
| 1717 |
+
{
|
| 1718 |
+
"columns": ["id"],
|
| 1719 |
+
"nameExplicit": false,
|
| 1720 |
+
"name": "part_pk",
|
| 1721 |
+
"table": "part",
|
| 1722 |
+
"entityType": "pks"
|
| 1723 |
+
},
|
| 1724 |
+
{
|
| 1725 |
+
"columns": ["session_id"],
|
| 1726 |
+
"nameExplicit": false,
|
| 1727 |
+
"name": "session_context_epoch_pk",
|
| 1728 |
+
"table": "session_context_epoch",
|
| 1729 |
+
"entityType": "pks"
|
| 1730 |
+
},
|
| 1731 |
+
{
|
| 1732 |
+
"columns": ["id"],
|
| 1733 |
+
"nameExplicit": false,
|
| 1734 |
+
"name": "session_input_pk",
|
| 1735 |
+
"table": "session_input",
|
| 1736 |
+
"entityType": "pks"
|
| 1737 |
+
},
|
| 1738 |
+
{
|
| 1739 |
+
"columns": ["id"],
|
| 1740 |
+
"nameExplicit": false,
|
| 1741 |
+
"name": "session_message_pk",
|
| 1742 |
+
"table": "session_message",
|
| 1743 |
+
"entityType": "pks"
|
| 1744 |
+
},
|
| 1745 |
+
{
|
| 1746 |
+
"columns": ["id"],
|
| 1747 |
+
"nameExplicit": false,
|
| 1748 |
+
"name": "session_pk",
|
| 1749 |
+
"table": "session",
|
| 1750 |
+
"entityType": "pks"
|
| 1751 |
+
},
|
| 1752 |
+
{
|
| 1753 |
+
"columns": ["session_id"],
|
| 1754 |
+
"nameExplicit": false,
|
| 1755 |
+
"name": "session_share_pk",
|
| 1756 |
+
"table": "session_share",
|
| 1757 |
+
"entityType": "pks"
|
| 1758 |
+
},
|
| 1759 |
+
{
|
| 1760 |
+
"columns": [
|
| 1761 |
+
{
|
| 1762 |
+
"value": "aggregate_id",
|
| 1763 |
+
"isExpression": false
|
| 1764 |
+
},
|
| 1765 |
+
{
|
| 1766 |
+
"value": "seq",
|
| 1767 |
+
"isExpression": false
|
| 1768 |
+
}
|
| 1769 |
+
],
|
| 1770 |
+
"isUnique": true,
|
| 1771 |
+
"where": null,
|
| 1772 |
+
"origin": "manual",
|
| 1773 |
+
"name": "event_aggregate_seq_idx",
|
| 1774 |
+
"entityType": "indexes",
|
| 1775 |
+
"table": "event"
|
| 1776 |
+
},
|
| 1777 |
+
{
|
| 1778 |
+
"columns": [
|
| 1779 |
+
{
|
| 1780 |
+
"value": "aggregate_id",
|
| 1781 |
+
"isExpression": false
|
| 1782 |
+
},
|
| 1783 |
+
{
|
| 1784 |
+
"value": "type",
|
| 1785 |
+
"isExpression": false
|
| 1786 |
+
},
|
| 1787 |
+
{
|
| 1788 |
+
"value": "seq",
|
| 1789 |
+
"isExpression": false
|
| 1790 |
+
}
|
| 1791 |
+
],
|
| 1792 |
+
"isUnique": false,
|
| 1793 |
+
"where": null,
|
| 1794 |
+
"origin": "manual",
|
| 1795 |
+
"name": "event_aggregate_type_seq_idx",
|
| 1796 |
+
"entityType": "indexes",
|
| 1797 |
+
"table": "event"
|
| 1798 |
+
},
|
| 1799 |
+
{
|
| 1800 |
+
"columns": [
|
| 1801 |
+
{
|
| 1802 |
+
"value": "project_id",
|
| 1803 |
+
"isExpression": false
|
| 1804 |
+
},
|
| 1805 |
+
{
|
| 1806 |
+
"value": "action",
|
| 1807 |
+
"isExpression": false
|
| 1808 |
+
},
|
| 1809 |
+
{
|
| 1810 |
+
"value": "resource",
|
| 1811 |
+
"isExpression": false
|
| 1812 |
+
}
|
| 1813 |
+
],
|
| 1814 |
+
"isUnique": true,
|
| 1815 |
+
"where": null,
|
| 1816 |
+
"origin": "manual",
|
| 1817 |
+
"name": "permission_project_action_resource_idx",
|
| 1818 |
+
"entityType": "indexes",
|
| 1819 |
+
"table": "permission"
|
| 1820 |
+
},
|
| 1821 |
+
{
|
| 1822 |
+
"columns": [
|
| 1823 |
+
{
|
| 1824 |
+
"value": "session_id",
|
| 1825 |
+
"isExpression": false
|
| 1826 |
+
},
|
| 1827 |
+
{
|
| 1828 |
+
"value": "time_created",
|
| 1829 |
+
"isExpression": false
|
| 1830 |
+
},
|
| 1831 |
+
{
|
| 1832 |
+
"value": "id",
|
| 1833 |
+
"isExpression": false
|
| 1834 |
+
}
|
| 1835 |
+
],
|
| 1836 |
+
"isUnique": false,
|
| 1837 |
+
"where": null,
|
| 1838 |
+
"origin": "manual",
|
| 1839 |
+
"name": "message_session_time_created_id_idx",
|
| 1840 |
+
"entityType": "indexes",
|
| 1841 |
+
"table": "message"
|
| 1842 |
+
},
|
| 1843 |
+
{
|
| 1844 |
+
"columns": [
|
| 1845 |
+
{
|
| 1846 |
+
"value": "message_id",
|
| 1847 |
+
"isExpression": false
|
| 1848 |
+
},
|
| 1849 |
+
{
|
| 1850 |
+
"value": "id",
|
| 1851 |
+
"isExpression": false
|
| 1852 |
+
}
|
| 1853 |
+
],
|
| 1854 |
+
"isUnique": false,
|
| 1855 |
+
"where": null,
|
| 1856 |
+
"origin": "manual",
|
| 1857 |
+
"name": "part_message_id_id_idx",
|
| 1858 |
+
"entityType": "indexes",
|
| 1859 |
+
"table": "part"
|
| 1860 |
+
},
|
| 1861 |
+
{
|
| 1862 |
+
"columns": [
|
| 1863 |
+
{
|
| 1864 |
+
"value": "session_id",
|
| 1865 |
+
"isExpression": false
|
| 1866 |
+
}
|
| 1867 |
+
],
|
| 1868 |
+
"isUnique": false,
|
| 1869 |
+
"where": null,
|
| 1870 |
+
"origin": "manual",
|
| 1871 |
+
"name": "part_session_idx",
|
| 1872 |
+
"entityType": "indexes",
|
| 1873 |
+
"table": "part"
|
| 1874 |
+
},
|
| 1875 |
+
{
|
| 1876 |
+
"columns": [
|
| 1877 |
+
{
|
| 1878 |
+
"value": "session_id",
|
| 1879 |
+
"isExpression": false
|
| 1880 |
+
},
|
| 1881 |
+
{
|
| 1882 |
+
"value": "promoted_seq",
|
| 1883 |
+
"isExpression": false
|
| 1884 |
+
},
|
| 1885 |
+
{
|
| 1886 |
+
"value": "delivery",
|
| 1887 |
+
"isExpression": false
|
| 1888 |
+
},
|
| 1889 |
+
{
|
| 1890 |
+
"value": "admitted_seq",
|
| 1891 |
+
"isExpression": false
|
| 1892 |
+
}
|
| 1893 |
+
],
|
| 1894 |
+
"isUnique": false,
|
| 1895 |
+
"where": null,
|
| 1896 |
+
"origin": "manual",
|
| 1897 |
+
"name": "session_input_session_pending_delivery_seq_idx",
|
| 1898 |
+
"entityType": "indexes",
|
| 1899 |
+
"table": "session_input"
|
| 1900 |
+
},
|
| 1901 |
+
{
|
| 1902 |
+
"columns": [
|
| 1903 |
+
{
|
| 1904 |
+
"value": "session_id",
|
| 1905 |
+
"isExpression": false
|
| 1906 |
+
},
|
| 1907 |
+
{
|
| 1908 |
+
"value": "admitted_seq",
|
| 1909 |
+
"isExpression": false
|
| 1910 |
+
}
|
| 1911 |
+
],
|
| 1912 |
+
"isUnique": true,
|
| 1913 |
+
"where": null,
|
| 1914 |
+
"origin": "manual",
|
| 1915 |
+
"name": "session_input_session_admitted_seq_idx",
|
| 1916 |
+
"entityType": "indexes",
|
| 1917 |
+
"table": "session_input"
|
| 1918 |
+
},
|
| 1919 |
+
{
|
| 1920 |
+
"columns": [
|
| 1921 |
+
{
|
| 1922 |
+
"value": "session_id",
|
| 1923 |
+
"isExpression": false
|
| 1924 |
+
},
|
| 1925 |
+
{
|
| 1926 |
+
"value": "promoted_seq",
|
| 1927 |
+
"isExpression": false
|
| 1928 |
+
}
|
| 1929 |
+
],
|
| 1930 |
+
"isUnique": true,
|
| 1931 |
+
"where": null,
|
| 1932 |
+
"origin": "manual",
|
| 1933 |
+
"name": "session_input_session_promoted_seq_idx",
|
| 1934 |
+
"entityType": "indexes",
|
| 1935 |
+
"table": "session_input"
|
| 1936 |
+
},
|
| 1937 |
+
{
|
| 1938 |
+
"columns": [
|
| 1939 |
+
{
|
| 1940 |
+
"value": "session_id",
|
| 1941 |
+
"isExpression": false
|
| 1942 |
+
},
|
| 1943 |
+
{
|
| 1944 |
+
"value": "seq",
|
| 1945 |
+
"isExpression": false
|
| 1946 |
+
}
|
| 1947 |
+
],
|
| 1948 |
+
"isUnique": true,
|
| 1949 |
+
"where": null,
|
| 1950 |
+
"origin": "manual",
|
| 1951 |
+
"name": "session_message_session_seq_idx",
|
| 1952 |
+
"entityType": "indexes",
|
| 1953 |
+
"table": "session_message"
|
| 1954 |
+
},
|
| 1955 |
+
{
|
| 1956 |
+
"columns": [
|
| 1957 |
+
{
|
| 1958 |
+
"value": "session_id",
|
| 1959 |
+
"isExpression": false
|
| 1960 |
+
},
|
| 1961 |
+
{
|
| 1962 |
+
"value": "type",
|
| 1963 |
+
"isExpression": false
|
| 1964 |
+
},
|
| 1965 |
+
{
|
| 1966 |
+
"value": "seq",
|
| 1967 |
+
"isExpression": false
|
| 1968 |
+
}
|
| 1969 |
+
],
|
| 1970 |
+
"isUnique": false,
|
| 1971 |
+
"where": null,
|
| 1972 |
+
"origin": "manual",
|
| 1973 |
+
"name": "session_message_session_type_seq_idx",
|
| 1974 |
+
"entityType": "indexes",
|
| 1975 |
+
"table": "session_message"
|
| 1976 |
+
},
|
| 1977 |
+
{
|
| 1978 |
+
"columns": [
|
| 1979 |
+
{
|
| 1980 |
+
"value": "session_id",
|
| 1981 |
+
"isExpression": false
|
| 1982 |
+
},
|
| 1983 |
+
{
|
| 1984 |
+
"value": "time_created",
|
| 1985 |
+
"isExpression": false
|
| 1986 |
+
},
|
| 1987 |
+
{
|
| 1988 |
+
"value": "id",
|
| 1989 |
+
"isExpression": false
|
| 1990 |
+
}
|
| 1991 |
+
],
|
| 1992 |
+
"isUnique": false,
|
| 1993 |
+
"where": null,
|
| 1994 |
+
"origin": "manual",
|
| 1995 |
+
"name": "session_message_session_time_created_id_idx",
|
| 1996 |
+
"entityType": "indexes",
|
| 1997 |
+
"table": "session_message"
|
| 1998 |
+
},
|
| 1999 |
+
{
|
| 2000 |
+
"columns": [
|
| 2001 |
+
{
|
| 2002 |
+
"value": "time_created",
|
| 2003 |
+
"isExpression": false
|
| 2004 |
+
}
|
| 2005 |
+
],
|
| 2006 |
+
"isUnique": false,
|
| 2007 |
+
"where": null,
|
| 2008 |
+
"origin": "manual",
|
| 2009 |
+
"name": "session_message_time_created_idx",
|
| 2010 |
+
"entityType": "indexes",
|
| 2011 |
+
"table": "session_message"
|
| 2012 |
+
},
|
| 2013 |
+
{
|
| 2014 |
+
"columns": [
|
| 2015 |
+
{
|
| 2016 |
+
"value": "project_id",
|
| 2017 |
+
"isExpression": false
|
| 2018 |
+
}
|
| 2019 |
+
],
|
| 2020 |
+
"isUnique": false,
|
| 2021 |
+
"where": null,
|
| 2022 |
+
"origin": "manual",
|
| 2023 |
+
"name": "session_project_idx",
|
| 2024 |
+
"entityType": "indexes",
|
| 2025 |
+
"table": "session"
|
| 2026 |
+
},
|
| 2027 |
+
{
|
| 2028 |
+
"columns": [
|
| 2029 |
+
{
|
| 2030 |
+
"value": "workspace_id",
|
| 2031 |
+
"isExpression": false
|
| 2032 |
+
}
|
| 2033 |
+
],
|
| 2034 |
+
"isUnique": false,
|
| 2035 |
+
"where": null,
|
| 2036 |
+
"origin": "manual",
|
| 2037 |
+
"name": "session_workspace_idx",
|
| 2038 |
+
"entityType": "indexes",
|
| 2039 |
+
"table": "session"
|
| 2040 |
+
},
|
| 2041 |
+
{
|
| 2042 |
+
"columns": [
|
| 2043 |
+
{
|
| 2044 |
+
"value": "parent_id",
|
| 2045 |
+
"isExpression": false
|
| 2046 |
+
}
|
| 2047 |
+
],
|
| 2048 |
+
"isUnique": false,
|
| 2049 |
+
"where": null,
|
| 2050 |
+
"origin": "manual",
|
| 2051 |
+
"name": "session_parent_idx",
|
| 2052 |
+
"entityType": "indexes",
|
| 2053 |
+
"table": "session"
|
| 2054 |
+
},
|
| 2055 |
+
{
|
| 2056 |
+
"columns": [
|
| 2057 |
+
{
|
| 2058 |
+
"value": "session_id",
|
| 2059 |
+
"isExpression": false
|
| 2060 |
+
}
|
| 2061 |
+
],
|
| 2062 |
+
"isUnique": false,
|
| 2063 |
+
"where": null,
|
| 2064 |
+
"origin": "manual",
|
| 2065 |
+
"name": "todo_session_idx",
|
| 2066 |
+
"entityType": "indexes",
|
| 2067 |
+
"table": "todo"
|
| 2068 |
+
}
|
| 2069 |
+
],
|
| 2070 |
+
"renames": []
|
| 2071 |
+
}
|
packages/core/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/core/tsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/tsconfig",
|
| 3 |
+
"extends": "@tsconfig/bun/tsconfig.json",
|
| 4 |
+
"compilerOptions": {
|
| 5 |
+
"noUncheckedIndexedAccess": false
|
| 6 |
+
}
|
| 7 |
+
}
|
packages/effect-drizzle-sqlite/AGENTS.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Effect Drizzle SQLite
|
| 2 |
+
|
| 3 |
+
This package vendors a Drizzle Effect SQLite adapter for this repo.
|
| 4 |
+
|
| 5 |
+
- Keep this package generic: Drizzle + Effect + SQLite only.
|
| 6 |
+
- Do not add opencode-specific tables, paths, migrations, post-commit hooks, or domain storage APIs here.
|
| 7 |
+
- Runtime code should depend on generic `effect/unstable/sql/SqlClient`, not a specific SQLite driver.
|
| 8 |
+
- Concrete SQLite clients such as `@effect/sql-sqlite-bun` belong in tests or examples unless this package intentionally adds a driver-specific helper.
|
| 9 |
+
- Preserve Drizzle adapter naming and behavior where possible so this can be replaced by upstream `drizzle-orm/effect-sqlite` later.
|
| 10 |
+
- If touching copied Drizzle internals, compare with current `drizzle-orm@1.0.0-rc.2` declarations and runtime JS.
|
| 11 |
+
- If touching Effect APIs, verify against `/Users/kit/code/open-source/effect-smol`.
|
| 12 |
+
|
| 13 |
+
Useful entry points:
|
| 14 |
+
|
| 15 |
+
- `src/effect-sqlite/driver.ts`: creates the Effect-backed Drizzle database with `make` and `makeWithDefaults`.
|
| 16 |
+
- `src/effect-sqlite/session.ts`: adapts generic Effect `SqlClient` execution and transactions to Drizzle SQLite sessions.
|
| 17 |
+
- `src/sqlite-core/effect/*`: Effect-yieldable SQLite query builders.
|
| 18 |
+
- `src/internal/drizzle-utils.ts`: local typed shims for Drizzle runtime internals that RC2 does not expose in declarations.
|
| 19 |
+
- `examples/basic.ts`: minimal usage example with Bun SQLite.
|
packages/effect-drizzle-sqlite/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/package.json",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"name": "@opencode-ai/effect-drizzle-sqlite",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"private": true,
|
| 8 |
+
"scripts": {
|
| 9 |
+
"test": "bun test --timeout 30000 --only-failures",
|
| 10 |
+
"typecheck": "tsgo --noEmit"
|
| 11 |
+
},
|
| 12 |
+
"exports": {
|
| 13 |
+
".": "./src/index.ts",
|
| 14 |
+
"./effect-sqlite": "./src/effect-sqlite/index.ts",
|
| 15 |
+
"./effect-sqlite/migrator": "./src/effect-sqlite/migrator.ts",
|
| 16 |
+
"./sqlite-core/effect": "./src/sqlite-core/effect/index.ts"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@effect/sql-sqlite-bun": "catalog:",
|
| 20 |
+
"@tsconfig/bun": "catalog:",
|
| 21 |
+
"@types/bun": "catalog:",
|
| 22 |
+
"@typescript/native-preview": "catalog:"
|
| 23 |
+
},
|
| 24 |
+
"dependencies": {
|
| 25 |
+
"drizzle-orm": "catalog:",
|
| 26 |
+
"effect": "catalog:"
|
| 27 |
+
}
|
| 28 |
+
}
|
packages/enterprise/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dist
|
| 2 |
+
.wrangler
|
| 3 |
+
.output
|
| 4 |
+
.vercel
|
| 5 |
+
.netlify
|
| 6 |
+
|
| 7 |
+
# Environment
|
| 8 |
+
.env
|
| 9 |
+
.env*.local
|
| 10 |
+
|
| 11 |
+
# dependencies
|
| 12 |
+
/node_modules
|
| 13 |
+
|
| 14 |
+
# IDEs and editors
|
| 15 |
+
/.idea
|
| 16 |
+
.project
|
| 17 |
+
.classpath
|
| 18 |
+
*.launch
|
| 19 |
+
.settings/
|
| 20 |
+
|
| 21 |
+
# Temp
|
| 22 |
+
gitignore
|
| 23 |
+
|
| 24 |
+
# System Files
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
packages/enterprise/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SolidStart
|
| 2 |
+
|
| 3 |
+
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
|
| 4 |
+
|
| 5 |
+
## Creating a project
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# create a new project in the current directory
|
| 9 |
+
npm init solid@latest
|
| 10 |
+
|
| 11 |
+
# create a new project in my-app
|
| 12 |
+
npm init solid@latest my-app
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
## Developing
|
| 16 |
+
|
| 17 |
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
npm run dev
|
| 21 |
+
|
| 22 |
+
# or start the server and open the app in a new browser tab
|
| 23 |
+
npm run dev -- --open
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
## Building
|
| 27 |
+
|
| 28 |
+
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
|
| 29 |
+
|
| 30 |
+
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
|
| 31 |
+
|
| 32 |
+
## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)
|
packages/enterprise/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@opencode-ai/enterprise",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"typecheck": "tsgo --noEmit",
|
| 9 |
+
"dev": "vite dev",
|
| 10 |
+
"build": "vite build",
|
| 11 |
+
"build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build",
|
| 12 |
+
"start": "vite start",
|
| 13 |
+
"shell-prod": "sst shell --target Teams --stage production"
|
| 14 |
+
},
|
| 15 |
+
"dependencies": {
|
| 16 |
+
"@opencode-ai/core": "workspace:*",
|
| 17 |
+
"@opencode-ai/session-ui": "workspace:*",
|
| 18 |
+
"@opencode-ai/ui": "workspace:*",
|
| 19 |
+
"aws4fetch": "^1.0.20",
|
| 20 |
+
"@pierre/diffs": "catalog:",
|
| 21 |
+
"@solidjs/router": "catalog:",
|
| 22 |
+
"@solidjs/start": "catalog:",
|
| 23 |
+
"@solidjs/meta": "catalog:",
|
| 24 |
+
"@hono/standard-validator": "catalog:",
|
| 25 |
+
"hono": "catalog:",
|
| 26 |
+
"hono-openapi": "catalog:",
|
| 27 |
+
"js-base64": "3.7.7",
|
| 28 |
+
"luxon": "catalog:",
|
| 29 |
+
"nitro": "3.0.1-alpha.1",
|
| 30 |
+
"solid-js": "catalog:",
|
| 31 |
+
"zod": "catalog:"
|
| 32 |
+
},
|
| 33 |
+
"devDependencies": {
|
| 34 |
+
"@cloudflare/workers-types": "catalog:",
|
| 35 |
+
"@tailwindcss/vite": "catalog:",
|
| 36 |
+
"@typescript/native-preview": "catalog:",
|
| 37 |
+
"@types/bun": "catalog:",
|
| 38 |
+
"@types/luxon": "catalog:",
|
| 39 |
+
"tailwindcss": "catalog:",
|
| 40 |
+
"typescript": "catalog:",
|
| 41 |
+
"vite": "catalog:"
|
| 42 |
+
},
|
| 43 |
+
"engines": {
|
| 44 |
+
"node": ">=22"
|
| 45 |
+
}
|
| 46 |
+
}
|
packages/enterprise/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/enterprise/test-debug.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Share } from "./src/core/share"
|
| 2 |
+
import { Storage } from "./src/core/storage"
|
| 3 |
+
|
| 4 |
+
async function test() {
|
| 5 |
+
const shareInfo = await Share.create({ sessionID: "test-debug-" + Date.now() })
|
| 6 |
+
|
| 7 |
+
const batch1: Share.Data[] = [
|
| 8 |
+
{ type: "part", data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello" } },
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
const batch2: Share.Data[] = [
|
| 12 |
+
{
|
| 13 |
+
type: "part",
|
| 14 |
+
data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello Updated" },
|
| 15 |
+
},
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
await Share.sync({
|
| 19 |
+
share: { id: shareInfo.id, secret: shareInfo.secret },
|
| 20 |
+
data: batch1,
|
| 21 |
+
})
|
| 22 |
+
|
| 23 |
+
await Share.sync({
|
| 24 |
+
share: { id: shareInfo.id, secret: shareInfo.secret },
|
| 25 |
+
data: batch2,
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
const events = await Storage.list({ prefix: ["share_event", shareInfo.id] })
|
| 29 |
+
console.log("Events (raw):", events)
|
| 30 |
+
console.log("Events (reversed):", events.toReversed())
|
| 31 |
+
|
| 32 |
+
for (const event of events.toReversed()) {
|
| 33 |
+
const data = await Storage.read(event)
|
| 34 |
+
console.log("Event data (reversed order):", event, data)
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
void test()
|
packages/enterprise/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ESNext",
|
| 4 |
+
"module": "ESNext",
|
| 5 |
+
"moduleResolution": "bundler",
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"allowSyntheticDefaultImports": true,
|
| 8 |
+
"esModuleInterop": true,
|
| 9 |
+
"jsx": "preserve",
|
| 10 |
+
"jsxImportSource": "solid-js",
|
| 11 |
+
"allowJs": true,
|
| 12 |
+
"noEmit": true,
|
| 13 |
+
"strict": true,
|
| 14 |
+
"types": ["@cloudflare/workers-types", "vite/client", "bun"],
|
| 15 |
+
"isolatedModules": true,
|
| 16 |
+
"paths": {
|
| 17 |
+
"~/*": ["./src/*"]
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
}
|
packages/enterprise/vite.config.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, PluginOption } from "vite"
|
| 2 |
+
import { solidStart } from "@solidjs/start/config"
|
| 3 |
+
import { nitro } from "nitro/vite"
|
| 4 |
+
import tailwindcss from "@tailwindcss/vite"
|
| 5 |
+
|
| 6 |
+
const nitroConfig: any = (() => {
|
| 7 |
+
const target = process.env.OPENCODE_DEPLOYMENT_TARGET
|
| 8 |
+
if (target === "cloudflare") {
|
| 9 |
+
return {
|
| 10 |
+
compatibilityDate: "2024-09-19",
|
| 11 |
+
preset: "cloudflare-module",
|
| 12 |
+
cloudflare: {
|
| 13 |
+
nodeCompat: true,
|
| 14 |
+
},
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
return {}
|
| 18 |
+
})()
|
| 19 |
+
|
| 20 |
+
export default defineConfig({
|
| 21 |
+
plugins: [
|
| 22 |
+
tailwindcss(),
|
| 23 |
+
solidStart() as PluginOption,
|
| 24 |
+
nitro({
|
| 25 |
+
...nitroConfig,
|
| 26 |
+
baseURL: process.env.OPENCODE_BASE_URL,
|
| 27 |
+
}),
|
| 28 |
+
],
|
| 29 |
+
server: {
|
| 30 |
+
host: "0.0.0.0",
|
| 31 |
+
allowedHosts: true,
|
| 32 |
+
port: 3002,
|
| 33 |
+
},
|
| 34 |
+
worker: {
|
| 35 |
+
format: "es",
|
| 36 |
+
},
|
| 37 |
+
})
|
packages/http-recorder/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 opencode
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
packages/http-recorder/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @opencode-ai/http-recorder
|
| 2 |
+
|
| 3 |
+
Record real Effect HTTP and WebSocket traffic once, then replay it from deterministic JSON cassettes.
|
| 4 |
+
|
| 5 |
+
Use it for provider integrations, retries, polling, multi-step flows, and any test where hand-written HTTP mocks hide too much of the real request shape.
|
| 6 |
+
|
| 7 |
+
> Public beta. The API depends on Effect 4 beta and may change with Effect's unstable transport modules.
|
| 8 |
+
|
| 9 |
+
## Install
|
| 10 |
+
|
| 11 |
+
```sh
|
| 12 |
+
bun add effect@4.0.0-beta.74
|
| 13 |
+
bun add -d @opencode-ai/http-recorder@beta @effect/vitest vitest
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
The package supports Node.js 22+ and Bun. It is not intended for browsers, workers, or Deno.
|
| 17 |
+
|
| 18 |
+
Effect `4.0.0-beta.74` has a known declaration error (`SchemaErrorTypeId` is missing). Until that upstream declaration is fixed, TypeScript consumers need:
|
| 19 |
+
|
| 20 |
+
```json
|
| 21 |
+
{
|
| 22 |
+
"compilerOptions": {
|
| 23 |
+
"skipLibCheck": true
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Quick Start
|
| 29 |
+
|
| 30 |
+
```ts
|
| 31 |
+
import { assert, describe, it } from "@effect/vitest"
|
| 32 |
+
import { Effect, Schema } from "effect"
|
| 33 |
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
| 34 |
+
import { HttpRecorder } from "@opencode-ai/http-recorder"
|
| 35 |
+
|
| 36 |
+
const User = Schema.Struct({
|
| 37 |
+
id: Schema.Number,
|
| 38 |
+
name: Schema.String,
|
| 39 |
+
})
|
| 40 |
+
|
| 41 |
+
const getUser = Effect.gen(function* () {
|
| 42 |
+
const http = yield* HttpClient.HttpClient
|
| 43 |
+
const response = yield* http.execute(HttpClientRequest.get("https://jsonplaceholder.typicode.com/users/1"))
|
| 44 |
+
return yield* Schema.decodeUnknownEffect(User)(yield* response.json)
|
| 45 |
+
})
|
| 46 |
+
|
| 47 |
+
describe("getUser", () => {
|
| 48 |
+
it.effect("loads a user", () =>
|
| 49 |
+
Effect.gen(function* () {
|
| 50 |
+
const user = yield* getUser
|
| 51 |
+
|
| 52 |
+
assert.strictEqual(user.id, 1)
|
| 53 |
+
assert.strictEqual(user.name, "Leanne Graham")
|
| 54 |
+
}).pipe(Effect.provide(HttpRecorder.http("users/get-one"))),
|
| 55 |
+
)
|
| 56 |
+
})
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
Run the test with Vitest. The first local run calls the real API and records:
|
| 60 |
+
|
| 61 |
+
```sh
|
| 62 |
+
bunx vitest run users.test.ts
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
```text
|
| 66 |
+
test/fixtures/recordings/users/get-one.json
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
Later runs replay that cassette without contacting the upstream server. When `CI=true`, missing cassettes fail instead of recording.
|
| 70 |
+
|
| 71 |
+
```mermaid
|
| 72 |
+
flowchart TD
|
| 73 |
+
Run[Run test] --> Recorded{Cassette recorded?}
|
| 74 |
+
Recorded -->|Yes| Replay[Replay cassette]
|
| 75 |
+
Recorded -->|No, local| Record[Call service and record cassette]
|
| 76 |
+
Recorded -->|No, CI| Fail[Fail: cassette missing]
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
Application code does not need to know whether a response is live or replayed.
|
| 80 |
+
|
| 81 |
+
## API
|
| 82 |
+
|
| 83 |
+
```ts
|
| 84 |
+
HttpRecorder.http(name, options?)
|
| 85 |
+
HttpRecorder.socket(name, options?)
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
That is the complete public API. `http` provides a fetch-backed recorded `HttpClient`. `socket` decorates a standard Effect `Socket.Socket` supplied beneath it.
|
| 89 |
+
|
| 90 |
+
## WebSockets
|
| 91 |
+
|
| 92 |
+
WebSocket cassettes preserve one ordered transcript of client and server text or binary frames. Replay follows that chronology: server frames are released until the next recorded client frame, then replay waits for the application to send the matching frame before continuing.
|
| 93 |
+
|
| 94 |
+
```ts
|
| 95 |
+
import { assert, it } from "@effect/vitest"
|
| 96 |
+
import { NodeSocket } from "@effect/platform-node"
|
| 97 |
+
import { Effect, Layer } from "effect"
|
| 98 |
+
import { Socket } from "effect/unstable/socket"
|
| 99 |
+
import { HttpRecorder } from "@opencode-ai/http-recorder"
|
| 100 |
+
|
| 101 |
+
const echo = Effect.gen(function* () {
|
| 102 |
+
const socket = yield* Socket.Socket
|
| 103 |
+
const write = yield* socket.writer
|
| 104 |
+
|
| 105 |
+
yield* socket.runString(
|
| 106 |
+
(message) =>
|
| 107 |
+
Effect.gen(function* () {
|
| 108 |
+
assert.strictEqual(message, "hello")
|
| 109 |
+
yield* write(new Socket.CloseEvent(1000))
|
| 110 |
+
}),
|
| 111 |
+
{ onOpen: write("hello") },
|
| 112 |
+
)
|
| 113 |
+
})
|
| 114 |
+
|
| 115 |
+
const recordedSocket = HttpRecorder.socket("echo/hello").pipe(
|
| 116 |
+
Layer.provide(
|
| 117 |
+
NodeSocket.layerWebSocket("wss://ws.postman-echo.com/raw", {
|
| 118 |
+
closeCodeIsError: (code) => code !== 1000,
|
| 119 |
+
}),
|
| 120 |
+
),
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
it.effect("exchanges WebSocket frames", () => echo.pipe(Effect.provide(recordedSocket)))
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
The application owns the WebSocket URL and protocols through normal Effect layer wiring. The recorder wraps that socket without duplicating its URL in recorder configuration. Provide separate socket layers for separate endpoints or concurrent connections.
|
| 127 |
+
|
| 128 |
+
Text frames use the same JSON-field and body redaction as HTTP bodies. Binary frames are stored losslessly as base64. Client and server frame kinds must match during replay.
|
| 129 |
+
|
| 130 |
+
## Refresh A Cassette
|
| 131 |
+
|
| 132 |
+
Delete exactly the recordings you want to replace, then rerun their tests:
|
| 133 |
+
|
| 134 |
+
```sh
|
| 135 |
+
rm test/fixtures/recordings/users/get-one.json
|
| 136 |
+
bun run test users.test.ts
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
There is intentionally no public overwrite mode. Deletion makes the set of recordings being refreshed visible and reviewable.
|
| 140 |
+
|
| 141 |
+
## Redaction
|
| 142 |
+
|
| 143 |
+
Secure defaults remove most headers and redact common credentials in headers, URLs, and JSON bodies. Extend those defaults at layer construction:
|
| 144 |
+
|
| 145 |
+
```ts
|
| 146 |
+
HttpRecorder.http("anthropic/messages", {
|
| 147 |
+
redact: {
|
| 148 |
+
headers: ["x-project-token"],
|
| 149 |
+
allowRequestHeaders: ["anthropic-version"],
|
| 150 |
+
queryParameters: ["session-id"],
|
| 151 |
+
jsonFields: ["user_id"],
|
| 152 |
+
url: (url) => url.replace(/\/accounts\/[^/]+/, "/accounts/{account}"),
|
| 153 |
+
body: (body) => body.replaceAll(/usr_[a-z0-9]+/g, "usr_redacted"),
|
| 154 |
+
},
|
| 155 |
+
})
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
| Option | Purpose |
|
| 159 |
+
| ---------------------- | -------------------------------------------------------------------- |
|
| 160 |
+
| `headers` | Add sensitive header names. They are retained as `[REDACTED]`. |
|
| 161 |
+
| `allowRequestHeaders` | Preserve additional non-sensitive request headers for matching. |
|
| 162 |
+
| `allowResponseHeaders` | Preserve additional non-sensitive response headers for replay. |
|
| 163 |
+
| `queryParameters` | Add sensitive URL query parameter names. |
|
| 164 |
+
| `jsonFields` | Recursively redact matching JSON keys in requests and responses. |
|
| 165 |
+
| `url` | Stabilize a URL after built-in redaction. |
|
| 166 |
+
| `body` | Stabilize request and response bodies after built-in JSON redaction. |
|
| 167 |
+
|
| 168 |
+
Before writing, the recorder scans the complete cassette for common credential formats and values from credential-like environment variables. Unsafe cassettes fail without replacing an existing recording.
|
| 169 |
+
|
| 170 |
+
Redaction is defense in depth, not a substitute for review. Inspect cassette diffs before committing them.
|
| 171 |
+
|
| 172 |
+
## Matching And Ordering
|
| 173 |
+
|
| 174 |
+
A cassette contains an ordered sequence of interactions. The first runtime request is checked against the first recorded request, the second against the second, and so on.
|
| 175 |
+
|
| 176 |
+
This strict ordering correctly models repeated identical requests whose responses change, including retries, polling, and cache tests. JSON object keys are canonicalized before matching.
|
| 177 |
+
|
| 178 |
+
Concurrent requests are recorded in request-start order even when their responses complete out of order.
|
| 179 |
+
|
| 180 |
+
Supply a custom equivalence rule when a request contains intentionally volatile data:
|
| 181 |
+
|
| 182 |
+
```ts
|
| 183 |
+
HttpRecorder.http("events/create", {
|
| 184 |
+
match: (incoming, recorded) =>
|
| 185 |
+
incoming.method === recorded.method && new URL(incoming.url).pathname === new URL(recorded.url).pathname,
|
| 186 |
+
})
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## Configuration
|
| 190 |
+
|
| 191 |
+
```ts
|
| 192 |
+
interface RecorderOptions {
|
| 193 |
+
readonly directory?: string
|
| 194 |
+
readonly metadata?: Record<string, unknown>
|
| 195 |
+
readonly redact?: RedactOptions
|
| 196 |
+
readonly match?: RequestMatcher
|
| 197 |
+
}
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
`directory` defaults to `<cwd>/test/fixtures/recordings`.
|
| 201 |
+
|
| 202 |
+
## Cassettes
|
| 203 |
+
|
| 204 |
+
Cassettes are readable JSON files intended to be committed with your tests. HTTP interactions are stored in request order. WebSocket cassettes preserve the observed order of client and server frames. Text stays readable; binary bodies and frames are stored losslessly as base64.
|
| 205 |
+
|
| 206 |
+
## Current Limits
|
| 207 |
+
|
| 208 |
+
- Responses are buffered while recording and replaying, so this beta is not suitable for tests that assert streaming timing, cancellation, or backpressure.
|
| 209 |
+
- WebSocket replay preserves frame chronology and content, not real network timing or backpressure.
|
| 210 |
+
- WebSocket V1 cassettes do not reproduce terminal close codes, close reasons, or transport failures. Failed and interrupted live runs are not recorded.
|
| 211 |
+
- WebSocket transcripts are retained in memory until the connection finishes; avoid using this beta for unbounded sessions.
|
| 212 |
+
- The package currently requires the exact Effect beta listed above.
|
| 213 |
+
- Cassette format version `1` has no migration tooling yet.
|
| 214 |
+
|
| 215 |
+
## License
|
| 216 |
+
|
| 217 |
+
MIT
|
packages/http-recorder/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/package.json",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"name": "@opencode-ai/http-recorder",
|
| 5 |
+
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
| 6 |
+
"type": "module",
|
| 7 |
+
"license": "MIT",
|
| 8 |
+
"repository": {
|
| 9 |
+
"type": "git",
|
| 10 |
+
"url": "git+https://github.com/anomalyco/opencode.git",
|
| 11 |
+
"directory": "packages/http-recorder"
|
| 12 |
+
},
|
| 13 |
+
"homepage": "https://github.com/anomalyco/opencode/tree/dev/packages/http-recorder",
|
| 14 |
+
"bugs": "https://github.com/anomalyco/opencode/issues",
|
| 15 |
+
"keywords": [
|
| 16 |
+
"effect",
|
| 17 |
+
"http",
|
| 18 |
+
"recording",
|
| 19 |
+
"replay",
|
| 20 |
+
"testing",
|
| 21 |
+
"vcr"
|
| 22 |
+
],
|
| 23 |
+
"engines": {
|
| 24 |
+
"node": ">=22"
|
| 25 |
+
},
|
| 26 |
+
"publishConfig": {
|
| 27 |
+
"access": "public"
|
| 28 |
+
},
|
| 29 |
+
"scripts": {
|
| 30 |
+
"test": "bun test --timeout 30000 --only-failures",
|
| 31 |
+
"typecheck": "tsgo --noEmit",
|
| 32 |
+
"build": "bun ./script/build.ts",
|
| 33 |
+
"verify:package": "bun ./script/verify-package.ts"
|
| 34 |
+
},
|
| 35 |
+
"exports": {
|
| 36 |
+
".": "./src/index.ts",
|
| 37 |
+
"./internal": "./src/internal.ts"
|
| 38 |
+
},
|
| 39 |
+
"files": [
|
| 40 |
+
"dist",
|
| 41 |
+
"README.md",
|
| 42 |
+
"CHANGELOG.md",
|
| 43 |
+
"LICENSE"
|
| 44 |
+
],
|
| 45 |
+
"devDependencies": {
|
| 46 |
+
"@tsconfig/node22": "catalog:",
|
| 47 |
+
"@types/bun": "catalog:",
|
| 48 |
+
"@types/node": "catalog:",
|
| 49 |
+
"@typescript/native-preview": "catalog:",
|
| 50 |
+
"effect": "catalog:",
|
| 51 |
+
"typescript": "catalog:"
|
| 52 |
+
},
|
| 53 |
+
"dependencies": {
|
| 54 |
+
"@effect/platform-node": "4.0.0-beta.83",
|
| 55 |
+
"@effect/platform-node-shared": "4.0.0-beta.83"
|
| 56 |
+
},
|
| 57 |
+
"peerDependencies": {
|
| 58 |
+
"effect": "4.0.0-beta.83"
|
| 59 |
+
}
|
| 60 |
+
}
|
packages/http-recorder/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/http-recorder/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/tsconfig",
|
| 3 |
+
"extends": "@tsconfig/node22/tsconfig.json",
|
| 4 |
+
"compilerOptions": {
|
| 5 |
+
"rootDir": "src",
|
| 6 |
+
"outDir": "dist",
|
| 7 |
+
"module": "nodenext",
|
| 8 |
+
"moduleResolution": "nodenext",
|
| 9 |
+
"declaration": true,
|
| 10 |
+
"stripInternal": true,
|
| 11 |
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
| 12 |
+
"noUncheckedIndexedAccess": false,
|
| 13 |
+
"plugins": [
|
| 14 |
+
{
|
| 15 |
+
"name": "@effect/language-service",
|
| 16 |
+
"transform": "@effect/language-service/transform",
|
| 17 |
+
"namespaceImportPackages": ["effect", "@effect/*"]
|
| 18 |
+
}
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
"include": ["src"]
|
| 22 |
+
}
|
packages/httpapi-codegen/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @opencode-ai/httpapi-codegen
|
| 2 |
+
|
| 3 |
+
Build-time source generation for domain-oriented Promise and Effect APIs derived directly from `HttpApi` and Effect Schema contracts.
|
| 4 |
+
|
| 5 |
+
The package is private while its API is explored. Its tests are the executable specification for the generator. It must remain independent of OpenCode Core and use synthetic `HttpApi` fixtures.
|
| 6 |
+
|
| 7 |
+
## Settled rules
|
| 8 |
+
|
| 9 |
+
- Reflect one authoritative `HttpApi` into a shared contract with `compile(Api)`.
|
| 10 |
+
- Emit clients independently with `emitPromise(contract)` and `emitEffect(contract)`.
|
| 11 |
+
- Give each emitter its own public type projection; the shared contract, not a generated type package, is the common source.
|
| 12 |
+
- Generate a rich Effect client with decoded Effect-native values, runtime schemas, preserved transformations, and `HttpApiClient`.
|
| 13 |
+
- Generate a zero-Effect Promise client with structural wire-oriented values, direct `fetch`, and syntax parsing without runtime structural validation.
|
| 14 |
+
- Keep the Promise surface domain-oriented rather than Hey API compatible: methods return unwrapped values and reject with tagged declared errors or `ClientError`.
|
| 15 |
+
- Return Promise streams as lazy `AsyncIterable` values and Effect streams as `Stream` values. Neither runtime reconnects automatically.
|
| 16 |
+
|
| 17 |
+
- Flatten path, query, header, and payload fields into one input object.
|
| 18 |
+
- Reject duplicate field names across input channels.
|
| 19 |
+
- Emit no method argument for zero fields, an optional object when every field is optional, and a required object when any field is required.
|
| 20 |
+
- Unwrap exact `{ data: A }` success envelopes.
|
| 21 |
+
- Map no-content success to `void`.
|
| 22 |
+
- Preserve other single success values.
|
| 23 |
+
- Reject ambiguous multiple-success contracts.
|
| 24 |
+
- Expose streaming success as `Stream`, not `Effect<Stream>`.
|
| 25 |
+
- Reject schemas whose wire/domain transformation cannot be generated exactly.
|
| 26 |
+
- Map transport, unexpected-status, and response-decoding failures to one stable generated `ClientError`.
|
| 27 |
+
- Commit generated source for review; CI regenerates and fails when the worktree changes.
|
| 28 |
+
- Track generated files in `.httpapi-codegen.json` so regeneration removes only stale files previously owned by the generator.
|
| 29 |
+
|
| 30 |
+
## Boundary
|
| 31 |
+
|
| 32 |
+
This package generates only client APIs derived from `HttpApi`. It does not generate embedded-only capabilities. Networked and embedded OpenCode use the same generated Effect client against network and in-memory `HttpClient` transports respectively; the embedded host structurally extends that client with same-process capabilities.
|
| 33 |
+
|
| 34 |
+
Codegen generates every endpoint in the `HttpApi` it receives. OpenCode owns the product decision by composing the exact remote API before invoking the generator; the generic package has no endpoint filtering policy.
|
| 35 |
+
|
| 36 |
+
The existing public `generate(Api, { directory })` operation writes the rich Effect output and remains an Effect requiring `FileSystem`. The staged API uses pure `compile(Api)`, `emitEffect(contract)`, and `emitPromise(contract)` phases before `write(output, directory)`. Compiler tests inspect virtual files directly; writer tests use `FileSystem.makeNoop`.
|
| 37 |
+
|
| 38 |
+
Generation formats TypeScript with Prettier before writing. Output paths are flat, unique, and checked against traversal, reserved manifest names, and existing symbolic links.
|
| 39 |
+
|
| 40 |
+
Portable Effect output uses one self-contained module per `HttpApiGroup`, plus root client and index modules. Promise output uses shared type and client modules, while imported Effect output keeps adapters in the root client module. Schema dependencies may be duplicated across portable Effect group modules. Cross-group schema partitioning is deferred until measured output or bundle cost requires it.
|
| 41 |
+
|
| 42 |
+
Codegen preserves transport identifiers internally. `compile` may explicitly map consumer-facing group names, and endpoint operation IDs are projected to their final dot-delimited segment. The generator performs no other implicit product-specific naming or public-name annotation mapping.
|
packages/httpapi-codegen/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/package.json",
|
| 3 |
+
"name": "@opencode-ai/httpapi-codegen",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"exports": {
|
| 7 |
+
".": "./src/index.ts"
|
| 8 |
+
},
|
| 9 |
+
"scripts": {
|
| 10 |
+
"test": "bun test --timeout 5000 --only-failures",
|
| 11 |
+
"typecheck": "tsgo --noEmit"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"effect": "catalog:",
|
| 15 |
+
"prettier": "3.6.2"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@tsconfig/bun": "catalog:",
|
| 19 |
+
"@types/bun": "catalog:",
|
| 20 |
+
"@typescript/native-preview": "catalog:"
|
| 21 |
+
}
|
| 22 |
+
}
|
packages/httpapi-codegen/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/httpapi-codegen/tsconfig.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/tsconfig",
|
| 3 |
+
"extends": "@tsconfig/bun/tsconfig.json",
|
| 4 |
+
"compilerOptions": {
|
| 5 |
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
| 6 |
+
"noUncheckedIndexedAccess": false
|
| 7 |
+
}
|
| 8 |
+
}
|
packages/identity/mark-192x192.png
ADDED
|
packages/identity/mark-512x512-light.png
ADDED
|
packages/identity/mark-512x512.png
ADDED
|
packages/identity/mark-96x96.png
ADDED
|
packages/identity/mark-light.svg
ADDED
|
|
packages/identity/mark.svg
ADDED
|
|
packages/llm/AGENTS.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LLM Package Guide
|
| 2 |
+
|
| 3 |
+
## Effect
|
| 4 |
+
|
| 5 |
+
- Prefer `HttpClient.HttpClient` / `HttpClientResponse.HttpClientResponse` over web `fetch` / `Response` at package boundaries.
|
| 6 |
+
- Use `Stream.Stream` for streaming data flow. Avoid ad hoc async generators or manual web reader loops unless an Effect `Stream` API cannot model the behavior.
|
| 7 |
+
- Use Effect Schema codecs for JSON encode/decode (`Schema.fromJsonString(...)`) instead of direct `JSON.parse` / `JSON.stringify` in implementation code.
|
| 8 |
+
- In `Effect.gen`, yield yieldable errors directly (`return yield* new MyError(...)`) instead of `Effect.fail(new MyError(...))`.
|
| 9 |
+
- Use `Effect.void` instead of `Effect.succeed(undefined)` when the successful value is intentionally void.
|
| 10 |
+
|
| 11 |
+
## Conventions
|
| 12 |
+
|
| 13 |
+
Per-type constructors live on the type, not as top-level re-exports. Use `Message.system(...)`, `Message.user(...)`, `Message.assistant(...)`, `Message.tool(...)`, `Model.make(...)`, `ToolDefinition.make(...)`, `ToolCallPart.make(...)`, `ToolResultPart.make(...)`, `ToolChoice.make(...)`, `ToolChoice.named(...)`, `SystemPart.make(...)`, and `GenerationOptions.make(...)` directly. The top-level `LLM` namespace is reserved for request-shaped call APIs: `LLM.request`, `LLM.generate`, `LLM.stream`, `LLM.updateRequest`, and `LLM.generateObject`. Two ways to construct the same thing is one too many.
|
| 14 |
+
|
| 15 |
+
## Tests
|
| 16 |
+
|
| 17 |
+
- Use `testEffect(...)` from `test/lib/effect.ts` for tests requiring Effect layers.
|
| 18 |
+
- Keep provider tests fixture-first. Live provider calls must stay behind `RECORD=true` and required API-key checks.
|
| 19 |
+
|
| 20 |
+
## Architecture
|
| 21 |
+
|
| 22 |
+
This package is an Effect Schema-first LLM core. The Schema classes in `src/schema/` are the canonical runtime data model. Convenience functions in `src/llm.ts` are thin constructors that return those same Schema class instances; they should improve callsites without creating a second model.
|
| 23 |
+
|
| 24 |
+
Primary in-repo integration point:
|
| 25 |
+
|
| 26 |
+
- `packages/opencode/src/session/llm.ts` is the session-owned orchestration layer that decides whether a request uses AI SDK or this package's native route runtime.
|
| 27 |
+
- `packages/opencode/src/session/llm/native-request.ts` is the lowering adapter from opencode's session/AI SDK-shaped data into this package's `LLMRequest` model.
|
| 28 |
+
- `packages/opencode/src/session/llm/native-runtime.ts` is the execution adapter that calls raw `LLMClient.stream(request)` and bridges one provider turn of opencode tool calls through this package's typed dispatcher.
|
| 29 |
+
- `packages/opencode/src/session/llm/ai-sdk.ts` keeps the default AI SDK path compatible by converting AI SDK stream parts into this package's shared `LLMEvent`s.
|
| 30 |
+
|
| 31 |
+
Keep this package independent of session concerns. Session auth, permissions, plugins, telemetry headers, and runtime selection belong in `packages/opencode/src/session/llm.ts` and its local adapters.
|
| 32 |
+
|
| 33 |
+
### Request Flow
|
| 34 |
+
|
| 35 |
+
The intended callsite is:
|
| 36 |
+
|
| 37 |
+
```ts
|
| 38 |
+
const request = LLM.request({
|
| 39 |
+
model: OpenAI.configure({ apiKey }).responses("gpt-4o-mini"),
|
| 40 |
+
system: "You are concise.",
|
| 41 |
+
prompt: "Say hello.",
|
| 42 |
+
})
|
| 43 |
+
|
| 44 |
+
const response = yield * LLMClient.generate(request)
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
`LLM.request(...)` builds an `LLMRequest`. `LLMClient.generate(...)` reads the executable route carried by `request.model.route`, builds the provider-native body, asks the route's transport for a real `HttpClientRequest.HttpClientRequest`, sends it through `RequestExecutor.Service`, parses the provider stream into common `LLMEvent`s, and finally returns an `LLMResponse`.
|
| 48 |
+
|
| 49 |
+
Use `LLMClient.stream(request)` when callers want incremental `LLMEvent`s. Use `LLMClient.generate(request)` when callers want those same events collected into an `LLMResponse`. Use `LLMClient.prepare<Body>(request)` to compile a request through the route pipeline without sending it — the optional `Body` type argument narrows `.body` to the route's native shape (e.g. `prepare<OpenAIChatBody>(...)` returns a `PreparedRequestOf<OpenAIChatBody>`). The runtime body is identical; the generic is a type-level assertion.
|
| 50 |
+
|
| 51 |
+
Filter or narrow `LLMEvent` streams with `LLMEvent.is.*` (camelCase guards, e.g. `events.filter(LLMEvent.is.toolCall)`). The kebab-case `LLMEvent.guards["tool-call"]` form also works but prefer `is.*` in new code.
|
| 52 |
+
|
| 53 |
+
### Routes
|
| 54 |
+
|
| 55 |
+
A route is the registered, runnable composition of four orthogonal pieces:
|
| 56 |
+
|
| 57 |
+
- **`Protocol`** (`src/route/protocol.ts`) — semantic API contract. Owns request body construction (`body.from`), the body schema (`body.schema`), the streaming-event schema (`stream.event`), and the event-to-`LLMEvent` state machine (`stream.step`). `Route.make(...)` validates and JSON-encodes the body from `body.schema` and decodes frames with `stream.event`. Examples: `OpenAIChat.protocol`, `OpenAIResponses.protocol`, `AnthropicMessages.protocol`, `Gemini.protocol`, `BedrockConverse.protocol`.
|
| 58 |
+
- **`Endpoint`** (`src/route/endpoint.ts`) — URL construction. The host, path, and route query live on the endpoint. `Endpoint.path("/chat/completions", { baseURL })` is the common case; pass a function for paths that embed the model id or a body field (e.g. `Endpoint.path(({ body }) => `/model/${body.modelId}/converse-stream`)`).
|
| 59 |
+
- **`Auth`** (`src/route/auth.ts`) — per-request transport authentication. Provider facades configure credentials onto the route before model selection, usually via `Auth.bearer(apiKey)` or `Auth.header(name, apiKey)`. Routes that need per-request signing (Bedrock SigV4, future Vertex IAM, Azure AAD) implement `Auth` as a function that signs the body and merges signed headers into the result.
|
| 60 |
+
- **`Framing`** (`src/route/framing.ts`) — bytes → frames. SSE (`Framing.sse`) is shared; Bedrock keeps its AWS event-stream framing as a typed `Framing<object>` value alongside its protocol.
|
| 61 |
+
|
| 62 |
+
Compose them via `Route.make(...)`:
|
| 63 |
+
|
| 64 |
+
```ts
|
| 65 |
+
export const route = Route.make({
|
| 66 |
+
id: "openai-chat",
|
| 67 |
+
provider: "openai",
|
| 68 |
+
protocol: OpenAIChat.protocol,
|
| 69 |
+
endpoint: Endpoint.path("/chat/completions", {
|
| 70 |
+
baseURL: "https://api.openai.com/v1",
|
| 71 |
+
}),
|
| 72 |
+
auth: Auth.bearer(),
|
| 73 |
+
framing: Framing.sse,
|
| 74 |
+
})
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Route defaults are request-shaping defaults such as `headers`, `limits`, `generation`, `providerOptions`, and `http`. Endpoint host/query belongs on the route endpoint. Selected `Model` values carry only model id, provider id, and the configured route value. Model capability/catalog metadata lives outside this package; protocol support is enforced by request lowering and typed `LLMError`s.
|
| 78 |
+
|
| 79 |
+
The four-axis decomposition is the reason DeepSeek, TogetherAI, Cerebras, Baseten, Fireworks, and DeepInfra all reuse `OpenAIChat.protocol` verbatim — each provider deployment is a 5-15 line `Route.make(...)` call instead of a 300-400 line route clone. Bug fixes in one protocol propagate to every consumer of that protocol in a single commit.
|
| 80 |
+
|
| 81 |
+
When a provider ships a non-HTTP transport (OpenAI's WebSocket Responses backend, hypothetical bidirectional streaming APIs), the seam is `Transport` — `WebSocketTransport.jsonTransport.with(...)` constructs an IO template whose `prepare` receives the route endpoint/auth at compile time, builds a WebSocket URL and message, and whose `frames` yields decoded text from the socket. Same protocol and endpoint source, different transport.
|
| 82 |
+
|
| 83 |
+
### URL Construction
|
| 84 |
+
|
| 85 |
+
`Endpoint` owns `{ baseURL, path, query }`. Each protocol route includes a canonical endpoint when the provider has one (e.g. `https://api.openai.com/v1`); provider helpers override endpoint fields by configuring the route before selecting a model. Routes that have no canonical URL (OpenAI-compatible Chat, GitHub Copilot) require configuration before execution.
|
| 86 |
+
|
| 87 |
+
For providers where the URL is derived from typed inputs (Azure resource name, Bedrock region), the provider helper configures the route endpoint before calling `.model(...)`. Use `AtLeastOne<T>` from `route/auth-options.ts` for inputs that accept either of two derivation paths (Azure: `resourceName` or `baseURL`).
|
| 88 |
+
|
| 89 |
+
### Provider Facades
|
| 90 |
+
|
| 91 |
+
Provider-facing APIs are configured facades over route values. Endpoint/auth/resource/API-version setup happens before model selection, and model selectors accept only a model or deployment id:
|
| 92 |
+
|
| 93 |
+
```ts
|
| 94 |
+
const openai = OpenAI.configure({ apiKey, baseURL })
|
| 95 |
+
const model = openai.responses("gpt-4o-mini")
|
| 96 |
+
|
| 97 |
+
const azure = Azure.configure({ resourceName, apiKey, apiVersion: "v1" })
|
| 98 |
+
const deployment = azure.responses("my-deployment")
|
| 99 |
+
|
| 100 |
+
const gateway = CloudflareAIGateway.configure({ accountId, gatewayId, gatewayApiKey, apiKey })
|
| 101 |
+
const proxied = gateway.model("openai/gpt-4o-mini")
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
Keep provider facades small and explicit:
|
| 105 |
+
|
| 106 |
+
- Use branded `ProviderID.make(...)` and `ModelID.make(...)` where ids are constructed directly.
|
| 107 |
+
- Use `model` for the default API path and named methods for provider-native alternatives such as OpenAI `responses`, `responsesWebSocket`, and `chat`.
|
| 108 |
+
- Put provider-specific setup on `.configure(...)`; do not add `model(id, overrides)` as a duplicate construction path.
|
| 109 |
+
- Export lower-level `routes` arrays separately only when advanced internal wiring needs them.
|
| 110 |
+
- Prefer `apiKey` as provider-specific sugar and `auth` as the explicit override; keep them mutually exclusive in provider option types with `ProviderAuthOption`.
|
| 111 |
+
- Resolve `apiKey` → `Auth` with `AuthOptions.bearer(options, "<PROVIDER>_API_KEY")` (it honors an explicit `auth` override and falls back to `Auth.config(envVar)` so missing keys surface a typed `Authentication` error rather than a runtime crash).
|
| 112 |
+
- Use separate top-level facades for products with different required setup, such as `CloudflareAIGateway` and `CloudflareWorkersAI`.
|
| 113 |
+
|
| 114 |
+
`Provider.make(...)` remains available for simple static provider definitions, but new built-in providers should prefer plain configured facades unless a helper removes real duplication without adding runtime behavior.
|
| 115 |
+
|
| 116 |
+
### Folder layout
|
| 117 |
+
|
| 118 |
+
```
|
| 119 |
+
packages/llm/src/
|
| 120 |
+
schema/ canonical Schema model, split by concern
|
| 121 |
+
ids.ts branded IDs, literal types, ProviderMetadata
|
| 122 |
+
options.ts Generation/Provider/Http options, Limits, Model, cache policy
|
| 123 |
+
messages.ts content parts, Message, ToolDefinition, LLMRequest
|
| 124 |
+
events.ts Usage, individual events, LLMEvent, PreparedRequest, LLMResponse
|
| 125 |
+
errors.ts error reasons, LLMError, ToolFailure
|
| 126 |
+
index.ts barrel
|
| 127 |
+
llm.ts request constructors and convenience helpers
|
| 128 |
+
route/
|
| 129 |
+
index.ts @opencode-ai/llm/route advanced barrel
|
| 130 |
+
client.ts Route.make + LLMClient.prepare/stream/generate
|
| 131 |
+
executor.ts RequestExecutor service + transport error mapping
|
| 132 |
+
protocol.ts Protocol type + Protocol.make
|
| 133 |
+
endpoint.ts Endpoint type + Endpoint.path
|
| 134 |
+
auth.ts Auth type + Auth.bearer / Auth.apiKeyHeader / Auth.passthrough
|
| 135 |
+
auth-options.ts ProviderAuthOption shape, AuthOptions.bearer, AtLeastOne helper
|
| 136 |
+
framing.ts Framing type + Framing.sse
|
| 137 |
+
transport/ transport implementations
|
| 138 |
+
index.ts Transport type + HttpTransport / WebSocketTransport namespaces
|
| 139 |
+
http.ts HttpTransport.httpJson — POST + framing
|
| 140 |
+
websocket.ts WebSocketTransport.json + WebSocketExecutor service
|
| 141 |
+
protocols/
|
| 142 |
+
shared.ts ProviderShared toolkit used inside protocol impls
|
| 143 |
+
openai-chat.ts protocol + route (compose OpenAIChat.protocol)
|
| 144 |
+
openai-responses.ts
|
| 145 |
+
anthropic-messages.ts
|
| 146 |
+
gemini.ts
|
| 147 |
+
bedrock-converse.ts
|
| 148 |
+
bedrock-event-stream.ts framing for AWS event-stream binary frames
|
| 149 |
+
openai-compatible-chat.ts route that reuses OpenAIChat.protocol, no canonical URL
|
| 150 |
+
utils/ per-protocol helpers (auth, cache, media, tool-stream, ...)
|
| 151 |
+
providers/
|
| 152 |
+
openai-compatible.ts generic compatible helper + family model helpers
|
| 153 |
+
openai-compatible-profile.ts family defaults (deepseek, togetherai, ...)
|
| 154 |
+
azure.ts / amazon-bedrock.ts / cloudflare.ts / github-copilot.ts / google.ts / xai.ts / openai.ts / anthropic.ts / openrouter.ts
|
| 155 |
+
tool.ts typed tool() helper
|
| 156 |
+
tool-runtime.ts narrow one-call typed tool dispatcher
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
The dependency arrow points down: `providers/*.ts` files import protocol routes and auth-option utilities; protocol modules import `endpoint`, `auth`, `framing`, and transport pieces. Protocols do not import provider facades. Lower-level modules know nothing about provider catalog metadata.
|
| 160 |
+
|
| 161 |
+
### Shared protocol helpers
|
| 162 |
+
|
| 163 |
+
`ProviderShared` exports a small toolkit used inside protocol implementations to keep them focused on provider-native shapes:
|
| 164 |
+
|
| 165 |
+
- `joinText(parts)` — joins an array of `TextPart` (or anything with a `.text`) with newlines. Use this anywhere a protocol flattens text content into a single string for a provider field.
|
| 166 |
+
- `parseToolInput(route, name, raw)` — Schema-decodes a tool-call argument string with the canonical "Invalid JSON input for `<route>` tool call `<name>`" error message. Treats empty input as `{}`.
|
| 167 |
+
- `parseJson(route, raw, message)` — generic JSON-via-Schema decode for non-tool bodies.
|
| 168 |
+
- `eventError(route, message, ...)` — typed `InvalidProviderOutput` constructor for stream-time decode failures.
|
| 169 |
+
- `validateWith(decoder)` — maps Schema decode errors to `InvalidRequest`. `Route.make(...)` uses this for body validation; lower-level routes can reuse it.
|
| 170 |
+
- `matchToolChoice(provider, choice, branches)` — branches over `LLMRequest["toolChoice"]` for provider-specific lowering.
|
| 171 |
+
|
| 172 |
+
If you find yourself copying a 3-to-5-line snippet between two protocols, lift it into `ProviderShared` next to these helpers rather than duplicating.
|
| 173 |
+
|
| 174 |
+
### Chronological System Updates
|
| 175 |
+
|
| 176 |
+
`LLMRequest.system` is the initial privileged prompt that applies ahead of the conversation. `Message.system(...)` is a separate, provider-neutral chronological operator update inside `LLMRequest.messages`; it applies only from its position in history onward and accepts text content only.
|
| 177 |
+
|
| 178 |
+
Native chronological system messages are route/model-specific. Anthropic Messages lowers them natively for Claude Opus 4.8 (`claude-opus-4-8`). Other routes and models intentionally lower the update in place into ordinary user-compatible text using this stable escaped representation:
|
| 179 |
+
|
| 180 |
+
```text
|
| 181 |
+
<system-update>
|
| 182 |
+
...
|
| 183 |
+
</system-update>
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
The wrapped-user fallback preserves ordering while visibly lowering authority. Never silently pass a raw chronological `role: "system"` through a route that might reject it. Do not insert raw retrieved documents, tool output, or web content into privileged chronological system updates; keep untrusted content in ordinary user/tool channels.
|
| 187 |
+
|
| 188 |
+
### Tools
|
| 189 |
+
|
| 190 |
+
Tool loops are represented in common messages and events:
|
| 191 |
+
|
| 192 |
+
```ts
|
| 193 |
+
const call = ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })
|
| 194 |
+
const result = Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } })
|
| 195 |
+
|
| 196 |
+
const followUp = LLM.request({
|
| 197 |
+
model,
|
| 198 |
+
messages: [Message.user("Weather?"), Message.assistant([call]), result],
|
| 199 |
+
})
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
Routes lower these into provider-native assistant tool-call messages and tool-result messages. Streaming providers should emit `tool-input-delta` events while arguments arrive, then a final `tool-call` event with parsed input.
|
| 203 |
+
|
| 204 |
+
### Tool dispatch
|
| 205 |
+
|
| 206 |
+
`LLM.stream(request)` and `LLM.generate(request)` each run exactly one provider turn. Add tool schemas to `request.tools` with `Tool.toDefinitions(tools)`. When a caller wants the package's typed one-call execution behavior, pass each canonical local `tool-call` event to `ToolRuntime.dispatch(tools, call)`.
|
| 207 |
+
|
| 208 |
+
```ts
|
| 209 |
+
const get_weather = tool({
|
| 210 |
+
description: "Get current weather for a city",
|
| 211 |
+
parameters: Schema.Struct({ city: Schema.String }),
|
| 212 |
+
success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }),
|
| 213 |
+
execute: ({ city }) =>
|
| 214 |
+
Effect.gen(function* () {
|
| 215 |
+
// city: string — typed from parameters Schema
|
| 216 |
+
const data = yield* WeatherApi.fetch(city)
|
| 217 |
+
return { temperature: data.temp, condition: data.cond }
|
| 218 |
+
// return type checked against success Schema
|
| 219 |
+
}),
|
| 220 |
+
})
|
| 221 |
+
|
| 222 |
+
const tools = { get_weather, get_time, ... }
|
| 223 |
+
const events = yield* LLM.stream(
|
| 224 |
+
LLM.updateRequest(request, { tools: Tool.toDefinitions(tools) }),
|
| 225 |
+
).pipe(Stream.runCollect)
|
| 226 |
+
|
| 227 |
+
const call = Array.from(events).find(LLMEvent.is.toolCall)
|
| 228 |
+
if (call && !call.providerExecuted) {
|
| 229 |
+
const dispatched = yield* ToolRuntime.dispatch(tools, call)
|
| 230 |
+
// Persist call + dispatched.result, then construct the next request explicitly.
|
| 231 |
+
}
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
The dispatcher:
|
| 235 |
+
|
| 236 |
+
- On `tool-call`: looks up the named tool, decodes input against `parameters` Schema, dispatches to the typed `execute`, encodes the result against `success` Schema, and returns canonical `tool-result` events.
|
| 237 |
+
- Does not stream providers, construct Session events, schedule fibers, append history, count steps, or continue model rounds.
|
| 238 |
+
- Leaves persistence and continuation to the enclosing product flow.
|
| 239 |
+
|
| 240 |
+
Handler dependencies (services, permissions, plugin hooks, abort handling) are closed over by the consumer at tool-construction time. Build the tools record inside an `Effect.gen` once and reuse it across many dispatches.
|
| 241 |
+
|
| 242 |
+
Errors must be expressed as `ToolFailure`. The runtime catches it and emits a `tool-error` event, then a `tool-result` of `type: "error"`, so the model can self-correct on the next step. Anything that is not a `ToolFailure` is treated as a defect and fails the stream. Three recoverable error paths produce `tool-error` events:
|
| 243 |
+
|
| 244 |
+
- The model called an unknown tool name.
|
| 245 |
+
- Input failed the `parameters` Schema.
|
| 246 |
+
- The handler returned a `ToolFailure`.
|
| 247 |
+
|
| 248 |
+
Provider-defined / hosted tools (Anthropic `web_search` / `code_execution` / `web_fetch`, OpenAI Responses `web_search_call` / `file_search_call` / `code_interpreter_call` / `mcp_call` / `local_shell_call` / `image_generation_call` / `computer_use_call`) pass through the runtime untouched:
|
| 249 |
+
|
| 250 |
+
- Routes surface the model's call as a `tool-call` event with `providerExecuted: true`, and the provider's result as a matching `tool-result` event with `providerExecuted: true`.
|
| 251 |
+
- Callers detect `providerExecuted` on `tool-call` and **skip local dispatch** — no handler is invoked and no `tool-error` is raised for "unknown tool". The provider already executed it.
|
| 252 |
+
- Callers that continue should retain both events in explicit history when the protocol requires it. Anthropic encodes them back as `server_tool_use` + `web_search_tool_result` (or `code_execution_tool_result` / `web_fetch_tool_result`) blocks; OpenAI Responses callers typically use `previous_response_id` instead of resending hosted-tool items.
|
| 253 |
+
|
| 254 |
+
Add provider-defined tools to `request.tools` (no runtime entry needed). The matching route must know how to lower the tool definition into the provider-native shape; right now Anthropic accepts `web_search` / `code_execution` / `web_fetch` and OpenAI Responses accepts the hosted tool names listed above.
|
| 255 |
+
|
| 256 |
+
## Protocol File Style
|
| 257 |
+
|
| 258 |
+
Protocol files should look self-similar. Provider quirks belong behind named helpers so a new route can be reviewed by comparing the same sections across files.
|
| 259 |
+
|
| 260 |
+
### Section order
|
| 261 |
+
|
| 262 |
+
Use this order for every protocol module:
|
| 263 |
+
|
| 264 |
+
1. Public model input
|
| 265 |
+
2. Request body schema
|
| 266 |
+
3. Streaming event schema
|
| 267 |
+
4. Parser state
|
| 268 |
+
5. Request body construction (`fromRequest`)
|
| 269 |
+
6. Stream parsing (`step` and per-event handlers)
|
| 270 |
+
7. Protocol and route
|
| 271 |
+
8. Protocol route export
|
| 272 |
+
|
| 273 |
+
### Rules
|
| 274 |
+
|
| 275 |
+
- Keep protocol files focused on the protocol. Move provider-specific projection, signing, media normalization, or other bulky transformations into `src/protocols/utils/*`.
|
| 276 |
+
- Use `Effect.fn("Provider.fromRequest")` for request body construction entrypoints. Use `Effect.fn(...)` for event handlers that yield effects; keep purely synchronous handlers as plain functions returning a `StepResult` that the dispatcher lifts via `Effect.succeed(...)`.
|
| 277 |
+
- Parser state owns terminal information. The state machine records finish reason, usage, and pending tool calls; emit one terminal `finish` event (or `provider-error`) for each completed response. If a provider splits reason and usage across events, merge them in parser state before flushing.
|
| 278 |
+
- Emit exactly one terminal `finish` event for a completed response, normally after a matching `step-finish`. Use `stream.terminal` to stop reading when the provider has a completion sentinel; use `stream.onHalt` when the final event must be flushed after the framed stream ends.
|
| 279 |
+
- Use shared helpers for repeated protocol policy such as text joining, usage totals, JSON parsing, and tool-call accumulation. `ToolStream` (`protocols/utils/tool-stream.ts`) accumulates streamed tool-call arguments uniformly.
|
| 280 |
+
- Make intentional provider differences explicit in helper names or comments. If two protocol files differ visually, the reason should be obvious from the names.
|
| 281 |
+
- Prefer dispatched per-event handlers (`onMessageStart`, `onContentBlockDelta`, ...) called from a small top-level `step` switch over a long if-chain. The dispatcher keeps the event surface visible at a glance.
|
| 282 |
+
- Keep tests in the same conceptual order as the protocol: basic prepare, tools prepare, unsupported lowering, text/usage parsing, tool streaming, finish reasons, provider errors.
|
| 283 |
+
|
| 284 |
+
### Review checklist
|
| 285 |
+
|
| 286 |
+
- Can the file be skimmed side-by-side with `openai-chat.ts` without hunting for equivalent sections?
|
| 287 |
+
- Are provider quirks named, isolated, and covered by focused tests?
|
| 288 |
+
- Does request body construction validate unsupported common content at the protocol boundary?
|
| 289 |
+
- Does stream parsing emit stable common events without leaking provider event order to callers?
|
| 290 |
+
- Does `toolChoice: "none"` behavior read as intentional?
|
| 291 |
+
|
| 292 |
+
## Recording Tests
|
| 293 |
+
|
| 294 |
+
Recorded tests use one cassette file per scenario. A cassette holds an ordered array of `{ request, response }` interactions, so multi-step flows (tool loops, retries, polling) record into a single file. Use `recordedTests({ prefix, requires })` and let the helper derive cassette names from test names:
|
| 295 |
+
|
| 296 |
+
```ts
|
| 297 |
+
const recorded = recordedTests({ prefix: "openai-chat", requires: ["OPENAI_API_KEY"] })
|
| 298 |
+
|
| 299 |
+
recorded.effect("streams text", () =>
|
| 300 |
+
Effect.gen(function* () {
|
| 301 |
+
// test body
|
| 302 |
+
}),
|
| 303 |
+
)
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
Replay is the default. `RECORD=true` records fresh cassettes and requires the listed env vars. Cassettes are written as pretty-printed JSON so multi-interaction diffs stay reviewable.
|
| 307 |
+
|
| 308 |
+
Pass `provider`, `protocol`, and optional `tags` to `recordedTests(...)` / `recorded.effect.with(...)` so cassettes carry searchable metadata. Use recorded-test filters to replay or record a narrow subset without rewriting a whole file:
|
| 309 |
+
|
| 310 |
+
- `RECORDED_PROVIDER=openai` matches tests tagged with `provider:openai`; comma-separated values are allowed.
|
| 311 |
+
- `RECORDED_PREFIX=openai-chat` matches cassette groups by `recordedTests({ prefix })`; comma-separated values are allowed.
|
| 312 |
+
- `RECORDED_TAGS=tool` requires all listed tags to be present, e.g. `RECORDED_TAGS=provider:togetherai,tool`.
|
| 313 |
+
- `RECORDED_TEST="streams text"` matches by test name, kebab-case test id, or cassette path.
|
| 314 |
+
|
| 315 |
+
Filters apply in replay and record mode. Combine them with `RECORD=true` when refreshing only one provider or scenario.
|
| 316 |
+
|
| 317 |
+
**Binary response bodies.** Most providers stream text (SSE, JSON). The recorder treats known textual media types (`text/*`, JSON/XML structured types, JavaScript, forms, YAML, and SVG) as text and stores every other response as base64 with `bodyEncoding: "base64"`. This preserves binary formats such as AWS event-stream frames without a lossy UTF-8 round trip.
|
| 318 |
+
|
| 319 |
+
**Matching strategy.** Replay walks the cassette in record order via an internal cursor: the Nth runtime request is served by the Nth recorded interaction, and each one is validated by comparing method, URL, allow-listed headers, and the canonical JSON body. This handles tool loops (each round's request differs as history grows) and retry/polling scenarios (successive byte-identical requests with different responses) uniformly. If a test reorders its requests, re-record the cassette. `scriptedResponses` (in `test/lib/http.ts`) is the deterministic counterpart for tests that don't need a live provider; it scripts response bodies in order without reading from disk.
|
| 320 |
+
|
| 321 |
+
Do not blanket re-record an entire test file when adding one cassette. `RECORD=true` rewrites every recorded case that runs, and provider streams contain volatile IDs, timestamps, fingerprints, and obfuscation fields. Prefer deleting the one cassette you intend to refresh, or run a focused test pattern that only registers the scenario you want to record. Keep stable existing cassettes unchanged unless their request shape or expected behavior changed.
|
packages/llm/DESIGN.md
ADDED
|
@@ -0,0 +1,1114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Library Design
|
| 2 |
+
|
| 3 |
+
> Discussion draft. This document describes the intended replacement for the
|
| 4 |
+
> current private `@opencode-ai/llm` API. Names and exact TypeScript signatures
|
| 5 |
+
> are illustrative until implementation, but the domain boundaries and defaults
|
| 6 |
+
> are deliberate.
|
| 7 |
+
|
| 8 |
+
## Status
|
| 9 |
+
|
| 10 |
+
- Proposed package: `@opencode-ai/ai`
|
| 11 |
+
- Initial stable domain: `LLM`
|
| 12 |
+
- Release posture: pre-1.0, with a stable-core intent
|
| 13 |
+
- Migration posture: clean break; do not preserve compatibility aliases
|
| 14 |
+
- Primary audience: general-purpose TypeScript developers using Effect
|
| 15 |
+
- Secondary audience: OpenCode and other durable agent runtimes
|
| 16 |
+
|
| 17 |
+
The package name leaves room for future domains such as embeddings, images, and
|
| 18 |
+
speech. Those domains are not part of this design and should not be forced into
|
| 19 |
+
the LLM run/turn model.
|
| 20 |
+
|
| 21 |
+
## Goals
|
| 22 |
+
|
| 23 |
+
1. Make a useful model call require very little code.
|
| 24 |
+
2. Make the default behavior good enough that most callers do not configure it.
|
| 25 |
+
3. Let advanced callers inspect, transform, or replace every important stage.
|
| 26 |
+
4. Keep provider quirks behind provider and protocol boundaries.
|
| 27 |
+
5. Preserve one provider turn as an explicit primitive for durable runtimes.
|
| 28 |
+
6. Keep serializable request data separate from process-local execution behavior.
|
| 29 |
+
7. Make unsupported combinations fail locally with useful typed errors.
|
| 30 |
+
8. Stay Effect-native without making package-specific service provisioning part
|
| 31 |
+
of every call site.
|
| 32 |
+
|
| 33 |
+
## Non-goals
|
| 34 |
+
|
| 35 |
+
- A global provider or model registry
|
| 36 |
+
- Durable agent orchestration or persistence
|
| 37 |
+
- Session history ownership
|
| 38 |
+
- Permission handling
|
| 39 |
+
- Cost billing or accounting guarantees
|
| 40 |
+
- Runtime model-catalog network requests
|
| 41 |
+
- Compatibility with the current private API
|
| 42 |
+
- Designing embeddings, image generation, speech, or transcription now
|
| 43 |
+
|
| 44 |
+
## Design Principles
|
| 45 |
+
|
| 46 |
+
### Progressive disclosure
|
| 47 |
+
|
| 48 |
+
The API has four layers:
|
| 49 |
+
|
| 50 |
+
1. **Run a model** with `LLM.generate` or `LLM.stream`.
|
| 51 |
+
2. **Control one provider turn** with `LLM.generateTurn` or `LLM.streamTurn`.
|
| 52 |
+
3. **Customize execution** with model defaults, call options, hooks, and provider
|
| 53 |
+
configuration.
|
| 54 |
+
4. **Author providers** with experimental provider definitions and protocols.
|
| 55 |
+
|
| 56 |
+
Normal documentation should teach only the first layer initially.
|
| 57 |
+
|
| 58 |
+
### Values over registries
|
| 59 |
+
|
| 60 |
+
Provider definitions, configured providers, models, protocols, tools, and hooks
|
| 61 |
+
are immutable values. Importing a provider does not register anything globally.
|
| 62 |
+
|
| 63 |
+
### Portable data, local behavior
|
| 64 |
+
|
| 65 |
+
Requests, messages, tool definitions, events, usage, and result projections are
|
| 66 |
+
plain immutable data with schemas. Configured models, executable tools, hooks,
|
| 67 |
+
and provider definitions may contain functions and Effect requirements and are
|
| 68 |
+
not serializable.
|
| 69 |
+
|
| 70 |
+
### Strong defaults, explicit overrides
|
| 71 |
+
|
| 72 |
+
Defaults should make common calls correct without hiding where behavior comes
|
| 73 |
+
from. Overrides compose in a documented order and never require patching
|
| 74 |
+
installed dependencies.
|
| 75 |
+
|
| 76 |
+
## Domain Model
|
| 77 |
+
|
| 78 |
+
### Provider Definition
|
| 79 |
+
|
| 80 |
+
An immutable, declarative description of a provider integration. It owns model
|
| 81 |
+
selection, option schemas, catalog corrections, protocols, and provider-wide
|
| 82 |
+
hooks. It is an experimental provider-authoring API.
|
| 83 |
+
|
| 84 |
+
### Configured Provider
|
| 85 |
+
|
| 86 |
+
A provider definition bound to deployment concerns such as credentials,
|
| 87 |
+
endpoint, transport, and provider headers.
|
| 88 |
+
|
| 89 |
+
`configure(...)` is intentionally deployment-only. It does not establish hidden
|
| 90 |
+
generation defaults.
|
| 91 |
+
|
| 92 |
+
### Model
|
| 93 |
+
|
| 94 |
+
A process-local executable model value selected from a configured provider. It
|
| 95 |
+
contains identity, capabilities, pricing metadata, provider-specific option
|
| 96 |
+
types, reusable request-behavior defaults, and hidden execution behavior.
|
| 97 |
+
|
| 98 |
+
Normal users do not need to learn the current `Route` composite. Protocol,
|
| 99 |
+
endpoint, auth, transport, and hooks are bound behind `Model`.
|
| 100 |
+
|
| 101 |
+
### Request
|
| 102 |
+
|
| 103 |
+
Portable, model-independent input for a model call. It may contain system
|
| 104 |
+
instructions, messages, tool definitions, generation controls, output intent,
|
| 105 |
+
cache policy, and metadata. It does not contain a configured model, executable
|
| 106 |
+
tool handlers, or hooks.
|
| 107 |
+
|
| 108 |
+
### Provider Turn
|
| 109 |
+
|
| 110 |
+
Exactly one request to a model provider and its normalized response. It does not
|
| 111 |
+
execute local tools or continue the conversation.
|
| 112 |
+
|
| 113 |
+
### Model Run
|
| 114 |
+
|
| 115 |
+
A complete interaction consisting of one or more provider turns. A run executes
|
| 116 |
+
local tools, appends their results, and continues until the model completes or a
|
| 117 |
+
stopping condition matches.
|
| 118 |
+
|
| 119 |
+
### TurnResult
|
| 120 |
+
|
| 121 |
+
The result of exactly one provider turn.
|
| 122 |
+
|
| 123 |
+
### GenerateResult
|
| 124 |
+
|
| 125 |
+
The result of a complete model run. It preserves every turn, tool activity,
|
| 126 |
+
aggregate usage, and estimated cost while exposing shortcuts to the final output.
|
| 127 |
+
|
| 128 |
+
### Protocol
|
| 129 |
+
|
| 130 |
+
The provider-wire contract that lowers portable requests into provider-native
|
| 131 |
+
bodies and raises provider-native stream events into normalized turn events.
|
| 132 |
+
Protocols are public, reusable, fully inspectable, and immutably patchable, but
|
| 133 |
+
the entire protocol-authoring API is experimental.
|
| 134 |
+
|
| 135 |
+
## Happy Path
|
| 136 |
+
|
| 137 |
+
### Effect
|
| 138 |
+
|
| 139 |
+
```ts
|
| 140 |
+
import { Effect } from "effect"
|
| 141 |
+
import { LLM } from "@opencode-ai/ai"
|
| 142 |
+
import { OpenAI } from "@opencode-ai/ai/providers/openai"
|
| 143 |
+
|
| 144 |
+
// Environment-based credentials are a provider default. No LLMClient layer is
|
| 145 |
+
// required: the Effect exposes standard runtime dependencies directly.
|
| 146 |
+
const model = OpenAI.model("gpt-4.1-mini")
|
| 147 |
+
|
| 148 |
+
const program = Effect.gen(function* () {
|
| 149 |
+
const result = yield* LLM.generate({
|
| 150 |
+
model,
|
| 151 |
+
system: "You are concise.",
|
| 152 |
+
prompt: "Explain Effect in one sentence.",
|
| 153 |
+
})
|
| 154 |
+
|
| 155 |
+
// `generate` always returns GenerateResult, even when the run has one turn.
|
| 156 |
+
console.log(result.text)
|
| 157 |
+
console.log(result.turns.length) // 1
|
| 158 |
+
console.log(result.usage)
|
| 159 |
+
console.log(result.cost) // Estimated cost, or undefined if any turn is unpriced.
|
| 160 |
+
})
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
The required Effect environment should contain standard services plus services
|
| 164 |
+
required by tools and hooks. It should not contain an `LLMClient` wrapper service.
|
| 165 |
+
|
| 166 |
+
### Current API
|
| 167 |
+
|
| 168 |
+
The current README appears similarly small but omits the package-specific service
|
| 169 |
+
and layer required at runtime:
|
| 170 |
+
|
| 171 |
+
```ts
|
| 172 |
+
// Current API: this request contains an executable model/route value.
|
| 173 |
+
const request = LLM.request({
|
| 174 |
+
model: OpenAI.configure({ apiKey }).responses("gpt-4o-mini"),
|
| 175 |
+
prompt: "Say hello.",
|
| 176 |
+
})
|
| 177 |
+
|
| 178 |
+
// Current API: this performs one provider turn, despite the broad name.
|
| 179 |
+
const response = yield * LLM.generate(request)
|
| 180 |
+
|
| 181 |
+
// Current API: execution also needs LLMClient.layer and RequestExecutor services.
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
The proposal removes mandatory request construction, removes package-specific
|
| 185 |
+
runtime provisioning, and makes `generate` mean a complete run.
|
| 186 |
+
|
| 187 |
+
## Provider And Model Selection
|
| 188 |
+
|
| 189 |
+
### Environment defaults
|
| 190 |
+
|
| 191 |
+
```ts
|
| 192 |
+
import { OpenAI } from "@opencode-ai/ai/providers/openai"
|
| 193 |
+
|
| 194 |
+
// Open strings receive autocomplete for IDs from the generated models.dev
|
| 195 |
+
// snapshot but continue to accept newly released and fine-tuned model IDs.
|
| 196 |
+
const model = OpenAI.model("gpt-4.1-mini")
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
### Deployment configuration
|
| 200 |
+
|
| 201 |
+
```ts
|
| 202 |
+
const openai = OpenAI.configure({
|
| 203 |
+
apiKey,
|
| 204 |
+
baseURL: "https://gateway.example.com/openai/v1",
|
| 205 |
+
headers: {
|
| 206 |
+
"x-tenant": "acme",
|
| 207 |
+
},
|
| 208 |
+
})
|
| 209 |
+
|
| 210 |
+
const model = openai.model("gpt-4.1-mini")
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
`configure(...)` owns deployment concerns only:
|
| 214 |
+
|
| 215 |
+
- Credentials and authentication
|
| 216 |
+
- Base URL and deployment location
|
| 217 |
+
- Transport selection
|
| 218 |
+
- Provider/deployment headers
|
| 219 |
+
- Other provider-specific connection setup
|
| 220 |
+
|
| 221 |
+
It does not own temperature, maximum output tokens, cache policy, retry policy,
|
| 222 |
+
tools, output schema, or system instructions.
|
| 223 |
+
|
| 224 |
+
### Reusable model defaults
|
| 225 |
+
|
| 226 |
+
```ts
|
| 227 |
+
const model = OpenAI.model("gpt-4.1-mini", {
|
| 228 |
+
generation: {
|
| 229 |
+
temperature: 0.2,
|
| 230 |
+
maxTokens: 2_000,
|
| 231 |
+
},
|
| 232 |
+
cache: "auto",
|
| 233 |
+
provider: {
|
| 234 |
+
store: false,
|
| 235 |
+
},
|
| 236 |
+
})
|
| 237 |
+
```
|
| 238 |
+
|
| 239 |
+
The second argument may default request behavior but not prompt/history or
|
| 240 |
+
executable tools. Call-level values override model defaults.
|
| 241 |
+
|
| 242 |
+
Provider-specific options are inferred from the concrete model:
|
| 243 |
+
|
| 244 |
+
```ts
|
| 245 |
+
yield *
|
| 246 |
+
LLM.generate({
|
| 247 |
+
model: OpenAI.model("gpt-4.1-mini"),
|
| 248 |
+
prompt: "Hello",
|
| 249 |
+
provider: {
|
| 250 |
+
store: false,
|
| 251 |
+
// OpenAI-specific autocomplete here; no `{ openai: ... }` nesting.
|
| 252 |
+
},
|
| 253 |
+
})
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
Code choosing between providers dynamically must narrow the model before using
|
| 257 |
+
provider-specific options. Portable generation controls remain available without
|
| 258 |
+
narrowing.
|
| 259 |
+
|
| 260 |
+
### Current API
|
| 261 |
+
|
| 262 |
+
```ts
|
| 263 |
+
// Current API mixes deployment configuration and reusable request behavior.
|
| 264 |
+
const model = OpenAI.configure({
|
| 265 |
+
apiKey,
|
| 266 |
+
generation: { maxTokens: 160 },
|
| 267 |
+
providerOptions: {
|
| 268 |
+
openai: { store: false },
|
| 269 |
+
},
|
| 270 |
+
}).model("gpt-4o-mini")
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
The proposal separates deployment configuration from selected-model behavior and
|
| 274 |
+
removes provider-keyed option bags when a concrete model already identifies the
|
| 275 |
+
provider.
|
| 276 |
+
|
| 277 |
+
## Requests
|
| 278 |
+
|
| 279 |
+
### Inline input
|
| 280 |
+
|
| 281 |
+
```ts
|
| 282 |
+
const result =
|
| 283 |
+
yield *
|
| 284 |
+
LLM.generate({
|
| 285 |
+
model,
|
| 286 |
+
system: "You are concise.",
|
| 287 |
+
prompt: "Summarize this pull request.",
|
| 288 |
+
generation: { maxTokens: 500 },
|
| 289 |
+
})
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
### Reusable portable request
|
| 293 |
+
|
| 294 |
+
```ts
|
| 295 |
+
const request = LLM.request({
|
| 296 |
+
system: "You are concise.",
|
| 297 |
+
prompt: "Summarize this pull request.",
|
| 298 |
+
generation: { maxTokens: 500 },
|
| 299 |
+
})
|
| 300 |
+
|
| 301 |
+
// Bind process-local execution behavior only when running.
|
| 302 |
+
const result = yield * LLM.generate({ model, request })
|
| 303 |
+
```
|
| 304 |
+
|
| 305 |
+
`LLM.request(...)` returns a plain immutable object. Use ordinary object spread
|
| 306 |
+
to derive another request:
|
| 307 |
+
|
| 308 |
+
```ts
|
| 309 |
+
const longer = {
|
| 310 |
+
...request,
|
| 311 |
+
generation: {
|
| 312 |
+
...request.generation,
|
| 313 |
+
maxTokens: 1_000,
|
| 314 |
+
},
|
| 315 |
+
}
|
| 316 |
+
```
|
| 317 |
+
|
| 318 |
+
There is no `LLM.updateRequest(...)` helper and no request Schema class.
|
| 319 |
+
|
| 320 |
+
### Conversation history
|
| 321 |
+
|
| 322 |
+
```ts
|
| 323 |
+
import { Message } from "@opencode-ai/ai"
|
| 324 |
+
|
| 325 |
+
const request = LLM.request({
|
| 326 |
+
system: "You are concise.",
|
| 327 |
+
messages: [
|
| 328 |
+
Message.user("What is Effect?"),
|
| 329 |
+
Message.assistant("A TypeScript library for typed functional effects."),
|
| 330 |
+
Message.user("Why would I use it?"),
|
| 331 |
+
],
|
| 332 |
+
})
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
Message helpers return plain immutable data. Object literals remain valid when
|
| 336 |
+
they satisfy the same input type.
|
| 337 |
+
|
| 338 |
+
`system` stays separate from chronological messages because it is the initial
|
| 339 |
+
privileged instruction. A chronological system message represents an instruction
|
| 340 |
+
change at a specific point in history.
|
| 341 |
+
|
| 342 |
+
## Complete Runs
|
| 343 |
+
|
| 344 |
+
### Automatic local tool loop
|
| 345 |
+
|
| 346 |
+
```ts
|
| 347 |
+
import { Effect, Schema } from "effect"
|
| 348 |
+
import { LLM, Tool } from "@opencode-ai/ai"
|
| 349 |
+
|
| 350 |
+
const tools = {
|
| 351 |
+
getWeather: Tool.make({
|
| 352 |
+
description: "Get current weather for a city.",
|
| 353 |
+
parameters: Schema.Struct({ city: Schema.String }),
|
| 354 |
+
success: Schema.Struct({ forecast: Schema.String }),
|
| 355 |
+
|
| 356 |
+
// Tool service requirements and typed errors flow into LLM.generate's
|
| 357 |
+
// Effect environment/error model instead of being erased.
|
| 358 |
+
execute: ({ city }) => Weather.get(city),
|
| 359 |
+
|
| 360 |
+
// Expected domain failures need an explicit model-visible representation.
|
| 361 |
+
formatError: (error) => ({
|
| 362 |
+
type: "text",
|
| 363 |
+
text: `Weather lookup failed: ${error.message}`,
|
| 364 |
+
}),
|
| 365 |
+
}),
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
const result =
|
| 369 |
+
yield *
|
| 370 |
+
LLM.generate({
|
| 371 |
+
model,
|
| 372 |
+
prompt: "What is the weather in London?",
|
| 373 |
+
tools,
|
| 374 |
+
})
|
| 375 |
+
|
| 376 |
+
// The runtime advertises definitions, dispatches calls, records results, and
|
| 377 |
+
// continues provider turns automatically.
|
| 378 |
+
console.log(result.text)
|
| 379 |
+
console.log(result.turns)
|
| 380 |
+
console.log(result.toolExecutions)
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
The default stopping condition is equivalent to:
|
| 384 |
+
|
| 385 |
+
```ts
|
| 386 |
+
stopWhen: StopWhen.turnCount(20)
|
| 387 |
+
```
|
| 388 |
+
|
| 389 |
+
This matches the Vercel AI SDK `ToolLoopAgent` default. Reaching the limit is a
|
| 390 |
+
successful result with `stopReason: "max-turns"`, not an Effect failure.
|
| 391 |
+
|
| 392 |
+
### Custom stopping
|
| 393 |
+
|
| 394 |
+
```ts
|
| 395 |
+
const result =
|
| 396 |
+
yield *
|
| 397 |
+
LLM.generate({
|
| 398 |
+
model,
|
| 399 |
+
prompt,
|
| 400 |
+
tools,
|
| 401 |
+
stopWhen: StopWhen.any(StopWhen.turnCount(8), StopWhen.hasToolCall("finalize")),
|
| 402 |
+
})
|
| 403 |
+
```
|
| 404 |
+
|
| 405 |
+
`stopWhen` accepts one predicate. Composition is explicit through combinators
|
| 406 |
+
such as `StopWhen.any`, `StopWhen.all`, and `StopWhen.not`.
|
| 407 |
+
|
| 408 |
+
Successful run stop reasons are closed:
|
| 409 |
+
|
| 410 |
+
```ts
|
| 411 |
+
type RunStopReason = "completed" | "max-turns" | "stop-condition"
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
### Tool concurrency
|
| 415 |
+
|
| 416 |
+
Independent tool calls emitted in one turn run concurrently with a bounded,
|
| 417 |
+
configurable concurrency limit. Results are appended in deterministic emitted
|
| 418 |
+
order. The runtime does not infer dependencies between tool calls; the model must
|
| 419 |
+
request dependent calls in separate turns.
|
| 420 |
+
|
| 421 |
+
Tools may declare an optional timeout. The overall run timeout still applies.
|
| 422 |
+
|
| 423 |
+
### Current API
|
| 424 |
+
|
| 425 |
+
Today callers must manually bridge every layer:
|
| 426 |
+
|
| 427 |
+
```ts
|
| 428 |
+
const request = LLM.request({
|
| 429 |
+
model,
|
| 430 |
+
prompt,
|
| 431 |
+
tools: Tool.toDefinitions(tools),
|
| 432 |
+
})
|
| 433 |
+
|
| 434 |
+
const events = yield * LLM.stream(request).pipe(Stream.runCollect)
|
| 435 |
+
const call = Array.from(events).find(LLMEvent.is.toolCall)
|
| 436 |
+
|
| 437 |
+
if (call && !call.providerExecuted) {
|
| 438 |
+
const dispatched = yield * ToolRuntime.dispatch(tools, call)
|
| 439 |
+
const followUp = LLM.updateRequest(request, {
|
| 440 |
+
messages: [...request.messages, Message.assistant([call]), Message.tool({ ...call, result: dispatched.result })],
|
| 441 |
+
})
|
| 442 |
+
// Caller must invoke the provider again and repeat the loop.
|
| 443 |
+
}
|
| 444 |
+
```
|
| 445 |
+
|
| 446 |
+
That explicit flow remains possible through turn APIs, but it is no longer the
|
| 447 |
+
only tool experience.
|
| 448 |
+
|
| 449 |
+
## One Provider Turn
|
| 450 |
+
|
| 451 |
+
OpenCode and other durable runtimes need to own persistence, tool settlement,
|
| 452 |
+
and continuation. They use the explicit turn API:
|
| 453 |
+
|
| 454 |
+
```ts
|
| 455 |
+
const result =
|
| 456 |
+
yield *
|
| 457 |
+
LLM.generateTurn({
|
| 458 |
+
model,
|
| 459 |
+
request,
|
| 460 |
+
// Definitions only. generateTurn never dispatches local handlers.
|
| 461 |
+
tools: {
|
| 462 |
+
getWeather: Tool.definition({
|
| 463 |
+
description: "Get current weather for a city.",
|
| 464 |
+
parameters: WeatherInput,
|
| 465 |
+
}),
|
| 466 |
+
},
|
| 467 |
+
})
|
| 468 |
+
|
| 469 |
+
// Persist the TurnResult and settle calls durably before the next turn.
|
| 470 |
+
for (const call of result.toolCalls) {
|
| 471 |
+
// Application-owned dispatch and persistence.
|
| 472 |
+
}
|
| 473 |
+
```
|
| 474 |
+
|
| 475 |
+
`generateTurn` and `streamTurn` make exactly one provider request. They never
|
| 476 |
+
execute a local tool and never continue automatically.
|
| 477 |
+
|
| 478 |
+
This separation is load-bearing:
|
| 479 |
+
|
| 480 |
+
- `generate` / `stream`: complete Model Run
|
| 481 |
+
- `generateTurn` / `streamTurn`: one Provider Turn
|
| 482 |
+
|
| 483 |
+
## Portable Tool Definitions
|
| 484 |
+
|
| 485 |
+
A portable request may declare serializable definitions, but executable handlers
|
| 486 |
+
are bound at run time:
|
| 487 |
+
|
| 488 |
+
```ts
|
| 489 |
+
const request = LLM.request({
|
| 490 |
+
prompt: "What is the weather in London?",
|
| 491 |
+
tools: {
|
| 492 |
+
getWeather: Tool.definition({
|
| 493 |
+
description: "Get current weather for a city.",
|
| 494 |
+
parameters: WeatherInput,
|
| 495 |
+
}),
|
| 496 |
+
},
|
| 497 |
+
})
|
| 498 |
+
|
| 499 |
+
const result =
|
| 500 |
+
yield *
|
| 501 |
+
LLM.generate({
|
| 502 |
+
model,
|
| 503 |
+
request,
|
| 504 |
+
tools: {
|
| 505 |
+
getWeather: Tool.make({
|
| 506 |
+
description: "Get current weather for a city.",
|
| 507 |
+
parameters: WeatherInput,
|
| 508 |
+
success: WeatherOutput,
|
| 509 |
+
execute: getWeather,
|
| 510 |
+
formatError,
|
| 511 |
+
}),
|
| 512 |
+
},
|
| 513 |
+
})
|
| 514 |
+
```
|
| 515 |
+
|
| 516 |
+
Definitions and handlers match by record key. Before the first provider call,
|
| 517 |
+
the runtime validates that every local definition has a compatible executable
|
| 518 |
+
binding. Missing or incompatible bindings fail with a typed tool-binding error.
|
| 519 |
+
|
| 520 |
+
Provider-hosted tools are distinct typed values:
|
| 521 |
+
|
| 522 |
+
```ts
|
| 523 |
+
const result =
|
| 524 |
+
yield *
|
| 525 |
+
LLM.generate({
|
| 526 |
+
model: OpenAI.model("gpt-4.1"),
|
| 527 |
+
prompt: "Find today's relevant announcements.",
|
| 528 |
+
tools: {
|
| 529 |
+
search: OpenAI.tool.webSearch({ searchContextSize: "medium" }),
|
| 530 |
+
},
|
| 531 |
+
})
|
| 532 |
+
```
|
| 533 |
+
|
| 534 |
+
Hosted tools do not pretend to have local handlers, and callers do not inspect a
|
| 535 |
+
`providerExecuted` boolean to decide whether dispatch is safe.
|
| 536 |
+
|
| 537 |
+
## Streaming
|
| 538 |
+
|
| 539 |
+
### Run stream
|
| 540 |
+
|
| 541 |
+
`LLM.stream` returns an Effect `Stream<RunEvent, LLMError, Requirements>`.
|
| 542 |
+
Run events explicitly expose orchestration boundaries:
|
| 543 |
+
|
| 544 |
+
```ts
|
| 545 |
+
const program = LLM.stream({ model, prompt, tools }).pipe(
|
| 546 |
+
Stream.tap((event) =>
|
| 547 |
+
Effect.sync(() => {
|
| 548 |
+
switch (event.type) {
|
| 549 |
+
case "run-start":
|
| 550 |
+
break
|
| 551 |
+
case "turn-start":
|
| 552 |
+
break
|
| 553 |
+
case "turn-event":
|
| 554 |
+
// Normalized text, reasoning, tool-call, usage, and finish events.
|
| 555 |
+
if (event.event.type === "text-delta") {
|
| 556 |
+
process.stdout.write(event.event.text)
|
| 557 |
+
}
|
| 558 |
+
break
|
| 559 |
+
case "tool-start":
|
| 560 |
+
break
|
| 561 |
+
case "tool-finish":
|
| 562 |
+
break
|
| 563 |
+
case "turn-finish":
|
| 564 |
+
break
|
| 565 |
+
case "run-finish":
|
| 566 |
+
// Contains the same full GenerateResult returned by LLM.generate.
|
| 567 |
+
console.log(event.result.usage)
|
| 568 |
+
break
|
| 569 |
+
}
|
| 570 |
+
}),
|
| 571 |
+
),
|
| 572 |
+
Stream.runDrain,
|
| 573 |
+
)
|
| 574 |
+
```
|
| 575 |
+
|
| 576 |
+
Exact event tag spelling remains an implementation detail to finalize, but the
|
| 577 |
+
algebra is settled:
|
| 578 |
+
|
| 579 |
+
- A separate `RunEvent` union for run, turn, and tool lifecycle
|
| 580 |
+
- A focused `TurnEvent` union for normalized provider output
|
| 581 |
+
- `streamTurn` emits only `TurnEvent`
|
| 582 |
+
- The terminal run event contains the full `GenerateResult`
|
| 583 |
+
|
| 584 |
+
External cancellation remains Effect interruption. It does not fabricate a
|
| 585 |
+
successful result with an `interrupted` stop reason.
|
| 586 |
+
|
| 587 |
+
## Structured Output
|
| 588 |
+
|
| 589 |
+
Structured output is an option on `generate`, not a separate operation:
|
| 590 |
+
|
| 591 |
+
```ts
|
| 592 |
+
const Weather = Schema.Struct({
|
| 593 |
+
city: Schema.String,
|
| 594 |
+
forecast: Schema.String,
|
| 595 |
+
highCelsius: Schema.Number,
|
| 596 |
+
})
|
| 597 |
+
|
| 598 |
+
const result =
|
| 599 |
+
yield *
|
| 600 |
+
LLM.generate({
|
| 601 |
+
model,
|
| 602 |
+
prompt: "Give me today's weather for London.",
|
| 603 |
+
output: Weather,
|
| 604 |
+
})
|
| 605 |
+
|
| 606 |
+
// Inferred from Weather.
|
| 607 |
+
result.output.city
|
| 608 |
+
```
|
| 609 |
+
|
| 610 |
+
The model declaration and protocol select the best reliable strategy:
|
| 611 |
+
|
| 612 |
+
1. Provider-native structured output when supported and reliable
|
| 613 |
+
2. Forced tool output when required as a compatibility fallback
|
| 614 |
+
3. Typed unsupported-capability failure before network execution when neither is
|
| 615 |
+
available
|
| 616 |
+
|
| 617 |
+
Advanced callers may override the strategy when exact provider semantics matter.
|
| 618 |
+
|
| 619 |
+
### Current API
|
| 620 |
+
|
| 621 |
+
```ts
|
| 622 |
+
// Current API is a separate operation and always forces a synthetic tool.
|
| 623 |
+
const result =
|
| 624 |
+
yield *
|
| 625 |
+
LLM.generateObject({
|
| 626 |
+
model,
|
| 627 |
+
prompt,
|
| 628 |
+
schema: Weather,
|
| 629 |
+
})
|
| 630 |
+
```
|
| 631 |
+
|
| 632 |
+
The proposal unifies generation and lets capabilities choose the strategy rather
|
| 633 |
+
than permanently encoding one cross-provider workaround.
|
| 634 |
+
|
| 635 |
+
## Model Catalog
|
| 636 |
+
|
| 637 |
+
`models.dev` is the release-time source for:
|
| 638 |
+
|
| 639 |
+
- Model ID suggestions
|
| 640 |
+
- Capabilities and modalities
|
| 641 |
+
- Context and output limits
|
| 642 |
+
- Pricing
|
| 643 |
+
- Other available model metadata
|
| 644 |
+
|
| 645 |
+
The package ships a generated, versioned snapshot. Normal execution performs no
|
| 646 |
+
catalog network requests.
|
| 647 |
+
|
| 648 |
+
Provider definitions may correct generated metadata where protocol-specific
|
| 649 |
+
knowledge is more accurate. Precedence is:
|
| 650 |
+
|
| 651 |
+
```text
|
| 652 |
+
models.dev snapshot
|
| 653 |
+
< provider-definition correction
|
| 654 |
+
< provider configuration override
|
| 655 |
+
< model-selection override
|
| 656 |
+
< call override
|
| 657 |
+
```
|
| 658 |
+
|
| 659 |
+
Unknown model IDs inherit only capabilities guaranteed by the selected protocol.
|
| 660 |
+
Unsupported request capabilities fail before network execution unless the caller
|
| 661 |
+
explicitly overrides the model declaration.
|
| 662 |
+
|
| 663 |
+
## Usage And Cost
|
| 664 |
+
|
| 665 |
+
`GenerateResult` aggregates normalized usage across every turn, including cache
|
| 666 |
+
read/write usage where providers report it.
|
| 667 |
+
|
| 668 |
+
It also exposes estimated cost using the generated models.dev pricing snapshot:
|
| 669 |
+
|
| 670 |
+
```ts
|
| 671 |
+
result.usage.inputTokens
|
| 672 |
+
result.usage.outputTokens
|
| 673 |
+
result.usage.cacheReadInputTokens
|
| 674 |
+
result.usage.cacheWriteInputTokens
|
| 675 |
+
|
| 676 |
+
result.cost?.total
|
| 677 |
+
result.cost?.currency // e.g. "USD"
|
| 678 |
+
```
|
| 679 |
+
|
| 680 |
+
Cost is an estimate, not a billing guarantee. If reliable pricing is unavailable
|
| 681 |
+
for any turn, aggregate run cost is unavailable rather than partial or silently
|
| 682 |
+
zero. Per-turn metadata should retain the catalog/pricing identity used so an
|
| 683 |
+
estimate can be explained.
|
| 684 |
+
|
| 685 |
+
## Caching
|
| 686 |
+
|
| 687 |
+
Prompt caching remains `"auto"` by default. The library places protocol-aware
|
| 688 |
+
cache boundaries where explicit caching is supported and does nothing on the wire
|
| 689 |
+
where providers cache implicitly.
|
| 690 |
+
|
| 691 |
+
```ts
|
| 692 |
+
yield *
|
| 693 |
+
LLM.generate({
|
| 694 |
+
model,
|
| 695 |
+
prompt,
|
| 696 |
+
cache: "none", // Explicit opt-out.
|
| 697 |
+
})
|
| 698 |
+
```
|
| 699 |
+
|
| 700 |
+
Granular cache policy remains available as an advanced request option.
|
| 701 |
+
|
| 702 |
+
## Retries, Timeouts, And Cancellation
|
| 703 |
+
|
| 704 |
+
### Retries
|
| 705 |
+
|
| 706 |
+
The default retry policy is deliberately conservative:
|
| 707 |
+
|
| 708 |
+
- Retry bounded transient transport and rate-limit failures
|
| 709 |
+
- Retry only before observable output
|
| 710 |
+
- Never silently retry after ambiguous tool execution or other side effects
|
| 711 |
+
- Allow each call to override or disable retry behavior
|
| 712 |
+
|
| 713 |
+
Retry configuration is call-scoped only. Provider and model configuration do not
|
| 714 |
+
silently inherit custom retry policies.
|
| 715 |
+
|
| 716 |
+
### Timeouts
|
| 717 |
+
|
| 718 |
+
```ts
|
| 719 |
+
yield *
|
| 720 |
+
LLM.generate({
|
| 721 |
+
model,
|
| 722 |
+
prompt,
|
| 723 |
+
timeout: "2 minutes", // Entire run, including tools.
|
| 724 |
+
turnTimeout: "30 seconds", // Each provider turn.
|
| 725 |
+
tools,
|
| 726 |
+
})
|
| 727 |
+
```
|
| 728 |
+
|
| 729 |
+
Exact Duration input spelling follows Effect conventions. Individual tools may
|
| 730 |
+
also declare optional timeouts.
|
| 731 |
+
|
| 732 |
+
### Cancellation
|
| 733 |
+
|
| 734 |
+
- Effect API: fiber interruption
|
| 735 |
+
- Promise API: `AbortSignal`, rejecting with a recognizable abort error
|
| 736 |
+
- Cancellation is not a successful run stop reason
|
| 737 |
+
|
| 738 |
+
## Hooks
|
| 739 |
+
|
| 740 |
+
Stable high-level hooks exist at five named stages:
|
| 741 |
+
|
| 742 |
+
1. Canonical request
|
| 743 |
+
2. Provider-native body
|
| 744 |
+
3. Prepared transport request
|
| 745 |
+
4. Normalized event
|
| 746 |
+
5. Error
|
| 747 |
+
|
| 748 |
+
Hooks are Effectful. They may transform the stage value or fail with a typed
|
| 749 |
+
error. They may not secretly short-circuit execution, synthesize a response,
|
| 750 |
+
retry, or redirect control flow.
|
| 751 |
+
|
| 752 |
+
```ts
|
| 753 |
+
const model = OpenAI.model("gpt-4.1", {
|
| 754 |
+
hooks: {
|
| 755 |
+
request: (request) =>
|
| 756 |
+
Effect.succeed({
|
| 757 |
+
...request,
|
| 758 |
+
metadata: { ...request.metadata, tenant: "acme" },
|
| 759 |
+
}),
|
| 760 |
+
body: (body, context) => auditBody(body, context),
|
| 761 |
+
transport: (request) => signInternalGatewayRequest(request),
|
| 762 |
+
event: (event) => redactProviderMetadata(event),
|
| 763 |
+
error: (error) => classifyInternalError(error),
|
| 764 |
+
},
|
| 765 |
+
})
|
| 766 |
+
```
|
| 767 |
+
|
| 768 |
+
Hook scopes compose in this order:
|
| 769 |
+
|
| 770 |
+
```text
|
| 771 |
+
provider-definition hooks -> model hooks -> call hooks
|
| 772 |
+
```
|
| 773 |
+
|
| 774 |
+
Each hook sees the prior hook's output. Replacement requires an explicit
|
| 775 |
+
definition-level patch, not accidental last-writer-wins semantics.
|
| 776 |
+
|
| 777 |
+
Provider-definition hooks are authored by provider integrations. They are not
|
| 778 |
+
passed through `Provider.configure(...)`, which remains deployment-only.
|
| 779 |
+
|
| 780 |
+
## HTTP And Provider Escape Hatches
|
| 781 |
+
|
| 782 |
+
The request customization ladder is:
|
| 783 |
+
|
| 784 |
+
1. Portable generation controls
|
| 785 |
+
2. Model-typed `provider` options
|
| 786 |
+
3. Stable staged hooks
|
| 787 |
+
4. Serializable HTTP/body overlays
|
| 788 |
+
5. Experimental provider-definition or protocol patching
|
| 789 |
+
|
| 790 |
+
```ts
|
| 791 |
+
yield *
|
| 792 |
+
LLM.generate({
|
| 793 |
+
model,
|
| 794 |
+
prompt,
|
| 795 |
+
http: {
|
| 796 |
+
headers: { "x-experimental": "1" },
|
| 797 |
+
query: { debug: "true" },
|
| 798 |
+
body: { newlyReleasedProviderField: true },
|
| 799 |
+
},
|
| 800 |
+
})
|
| 801 |
+
```
|
| 802 |
+
|
| 803 |
+
Raw overlays are intentional last-resort support for provider features that ship
|
| 804 |
+
before the library has a typed option.
|
| 805 |
+
|
| 806 |
+
## Provider-Native Metadata
|
| 807 |
+
|
| 808 |
+
Normalized message/content/event unions remain closed and exhaustive. Unknown or
|
| 809 |
+
provider-required round-trip data lives in caller-writable `providerMetadata`.
|
| 810 |
+
|
| 811 |
+
```ts
|
| 812 |
+
const assistant = Message.assistant([
|
| 813 |
+
{
|
| 814 |
+
type: "reasoning",
|
| 815 |
+
text: "...",
|
| 816 |
+
providerMetadata: {
|
| 817 |
+
openai: {
|
| 818 |
+
// Opaque provider data needed for replay or continuation.
|
| 819 |
+
},
|
| 820 |
+
},
|
| 821 |
+
},
|
| 822 |
+
])
|
| 823 |
+
```
|
| 824 |
+
|
| 825 |
+
Protocols validate metadata they consume. The field is an escape hatch, not a
|
| 826 |
+
portable semantic guarantee.
|
| 827 |
+
|
| 828 |
+
## Error Model
|
| 829 |
+
|
| 830 |
+
The Effect error channel is a tagged domain union rather than one `LLMError`
|
| 831 |
+
wrapper with nested reasons. Illustrative categories:
|
| 832 |
+
|
| 833 |
+
```ts
|
| 834 |
+
type LLMError =
|
| 835 |
+
| AuthenticationError
|
| 836 |
+
| InvalidRequestError
|
| 837 |
+
| UnsupportedCapabilityError
|
| 838 |
+
| ToolBindingError
|
| 839 |
+
| TransportError
|
| 840 |
+
| ProviderResponseError
|
| 841 |
+
| InvalidProviderOutputError
|
| 842 |
+
| HookError
|
| 843 |
+
```
|
| 844 |
+
|
| 845 |
+
Each error retains relevant provider/model/turn/stage context and its underlying
|
| 846 |
+
cause where available.
|
| 847 |
+
|
| 848 |
+
Expected tool errors keep their own typed error channel. `Tool.make` requires an
|
| 849 |
+
explicit mapping before such errors become model-visible tool results. Expected
|
| 850 |
+
mapped failures let the model recover; defects and interruption fail the run.
|
| 851 |
+
|
| 852 |
+
## Observability
|
| 853 |
+
|
| 854 |
+
The core library emits Effect-native spans and metrics for:
|
| 855 |
+
|
| 856 |
+
- Model runs
|
| 857 |
+
- Provider turns
|
| 858 |
+
- Provider requests
|
| 859 |
+
- Retries
|
| 860 |
+
- Tool executions
|
| 861 |
+
|
| 862 |
+
Default telemetry records metadata only:
|
| 863 |
+
|
| 864 |
+
- Provider and model identity
|
| 865 |
+
- Timing
|
| 866 |
+
- Token/cache usage
|
| 867 |
+
- Estimated cost availability
|
| 868 |
+
- Finish and stop reasons
|
| 869 |
+
- Retry counts
|
| 870 |
+
- Tool names
|
| 871 |
+
|
| 872 |
+
Prompts, model output, tool arguments, and tool results are never recorded by
|
| 873 |
+
default. Explicit hooks or telemetry configuration may opt into content capture.
|
| 874 |
+
|
| 875 |
+
## Promise API
|
| 876 |
+
|
| 877 |
+
Promise wrappers live at a separate subpath so the root remains unambiguously
|
| 878 |
+
Effect-first:
|
| 879 |
+
|
| 880 |
+
```ts
|
| 881 |
+
import { LLM } from "@opencode-ai/ai/promise"
|
| 882 |
+
import { OpenAI } from "@opencode-ai/ai/providers/openai"
|
| 883 |
+
|
| 884 |
+
const result = await LLM.generate({
|
| 885 |
+
model: OpenAI.model("gpt-4.1-mini"),
|
| 886 |
+
prompt: "Explain Effect in one sentence.",
|
| 887 |
+
signal: abortController.signal,
|
| 888 |
+
})
|
| 889 |
+
```
|
| 890 |
+
|
| 891 |
+
Streaming returns an `AsyncIterable<RunEvent>`:
|
| 892 |
+
|
| 893 |
+
```ts
|
| 894 |
+
for await (const event of LLM.stream({ model, prompt, signal })) {
|
| 895 |
+
if (event.type === "turn-event" && event.event.type === "text-delta") {
|
| 896 |
+
process.stdout.write(event.event.text)
|
| 897 |
+
}
|
| 898 |
+
}
|
| 899 |
+
```
|
| 900 |
+
|
| 901 |
+
Top-level Promise functions use a default runtime for built-in services. Custom
|
| 902 |
+
Effect service requirements use a configured client:
|
| 903 |
+
|
| 904 |
+
```ts
|
| 905 |
+
const client = LLM.makeClient({
|
| 906 |
+
layer: Layer.mergeAll(WeatherLive, AuditLive),
|
| 907 |
+
})
|
| 908 |
+
|
| 909 |
+
const result = await client.generate({ model, prompt, tools })
|
| 910 |
+
```
|
| 911 |
+
|
| 912 |
+
The Promise API mirrors Effect semantics. It does not invent different run,
|
| 913 |
+
error, stopping, or cancellation behavior.
|
| 914 |
+
|
| 915 |
+
## Schemas
|
| 916 |
+
|
| 917 |
+
Schemas live in a dedicated namespace/subpath instead of flooding root exports:
|
| 918 |
+
|
| 919 |
+
```ts
|
| 920 |
+
import { LLMSchema } from "@opencode-ai/ai/schema"
|
| 921 |
+
|
| 922 |
+
const request = yield * Schema.decodeUnknown(LLMSchema.Request)(input)
|
| 923 |
+
```
|
| 924 |
+
|
| 925 |
+
Schemas cover only serializable domain values:
|
| 926 |
+
|
| 927 |
+
- Requests and messages
|
| 928 |
+
- Portable tool definitions
|
| 929 |
+
- Turn and run events
|
| 930 |
+
- Serializable result projections
|
| 931 |
+
- Usage and cost estimates
|
| 932 |
+
- Tagged errors where serializable
|
| 933 |
+
- Provider metadata containers
|
| 934 |
+
|
| 935 |
+
Configured models, executable tools, hooks, provider definitions, and protocols
|
| 936 |
+
are process-local behavior and do not receive fake serialization schemas.
|
| 937 |
+
|
| 938 |
+
## Provider Authoring
|
| 939 |
+
|
| 940 |
+
Provider authoring is public but experimental.
|
| 941 |
+
|
| 942 |
+
### Declarative provider definition
|
| 943 |
+
|
| 944 |
+
```ts
|
| 945 |
+
import { Provider, Protocol } from "@opencode-ai/ai/provider"
|
| 946 |
+
|
| 947 |
+
export const ExampleAI = Provider.define({
|
| 948 |
+
id: "example",
|
| 949 |
+
options: ExampleProviderOptions,
|
| 950 |
+
configure: configureExampleDeployment,
|
| 951 |
+
protocols: {
|
| 952 |
+
responses: ExampleResponses,
|
| 953 |
+
},
|
| 954 |
+
models: ({ deployment, catalog }) => ({
|
| 955 |
+
model: (id, defaults) =>
|
| 956 |
+
Provider.model({
|
| 957 |
+
id,
|
| 958 |
+
deployment,
|
| 959 |
+
protocol: ExampleResponses,
|
| 960 |
+
metadata: catalog.model(id),
|
| 961 |
+
defaults,
|
| 962 |
+
}),
|
| 963 |
+
}),
|
| 964 |
+
catalog: generatedExampleCatalog,
|
| 965 |
+
corrections: exampleCatalogCorrections,
|
| 966 |
+
hooks: exampleProviderHooks,
|
| 967 |
+
})
|
| 968 |
+
```
|
| 969 |
+
|
| 970 |
+
The exact builder fields need implementation design, but it must remain one
|
| 971 |
+
declarative immutable object, infer provider option types, and support `.with(...)`
|
| 972 |
+
patching. It must not register globally.
|
| 973 |
+
|
| 974 |
+
Built-ins export their immutable definition for advanced forking:
|
| 975 |
+
|
| 976 |
+
```ts
|
| 977 |
+
import { OpenAI } from "@opencode-ai/ai/providers/openai"
|
| 978 |
+
|
| 979 |
+
const PatchedOpenAI = OpenAI.definition.with({
|
| 980 |
+
protocols: {
|
| 981 |
+
responses: OpenAI.protocols.responses.with({
|
| 982 |
+
// Explicit immutable stage patch.
|
| 983 |
+
body: {
|
| 984 |
+
fromRequest: patchResponsesBody,
|
| 985 |
+
},
|
| 986 |
+
}),
|
| 987 |
+
},
|
| 988 |
+
})
|
| 989 |
+
```
|
| 990 |
+
|
| 991 |
+
### Protocols
|
| 992 |
+
|
| 993 |
+
A protocol exposes all native types and stages:
|
| 994 |
+
|
| 995 |
+
- Provider-native request body and schema
|
| 996 |
+
- Transport frame type
|
| 997 |
+
- Provider-native event and schema
|
| 998 |
+
- Parser state
|
| 999 |
+
- Request lowering
|
| 1000 |
+
- Event stepping
|
| 1001 |
+
- Terminal detection and final flushing
|
| 1002 |
+
|
| 1003 |
+
Every stage is immutably patchable. This is deliberately more open than the AI
|
| 1004 |
+
SDK integrations that motivated this package.
|
| 1005 |
+
|
| 1006 |
+
```ts
|
| 1007 |
+
const PatchedResponses = OpenAIResponses.with({
|
| 1008 |
+
body: {
|
| 1009 |
+
fromRequest: (request) =>
|
| 1010 |
+
OpenAIResponses.body.fromRequest(request).pipe(Effect.map((body) => ({ ...body, custom_field: true }))),
|
| 1011 |
+
},
|
| 1012 |
+
stream: {
|
| 1013 |
+
step: patchResponsesStep,
|
| 1014 |
+
},
|
| 1015 |
+
})
|
| 1016 |
+
```
|
| 1017 |
+
|
| 1018 |
+
Protocol body, frame, native event, and parser-state types are exported. Because
|
| 1019 |
+
provider wire formats change often, these types and patch APIs are explicitly
|
| 1020 |
+
experimental and do not receive the high-level API's compatibility promise.
|
| 1021 |
+
|
| 1022 |
+
## Package Surface
|
| 1023 |
+
|
| 1024 |
+
Illustrative export layout:
|
| 1025 |
+
|
| 1026 |
+
```text
|
| 1027 |
+
@opencode-ai/ai
|
| 1028 |
+
LLM
|
| 1029 |
+
Message
|
| 1030 |
+
Tool
|
| 1031 |
+
StopWhen
|
| 1032 |
+
stable domain types
|
| 1033 |
+
|
| 1034 |
+
@opencode-ai/ai/promise
|
| 1035 |
+
Promise/AsyncIterable LLM facade
|
| 1036 |
+
|
| 1037 |
+
@opencode-ai/ai/schema
|
| 1038 |
+
serializable domain schemas
|
| 1039 |
+
|
| 1040 |
+
@opencode-ai/ai/provider
|
| 1041 |
+
experimental Provider and Protocol authoring APIs
|
| 1042 |
+
|
| 1043 |
+
@opencode-ai/ai/providers/openai
|
| 1044 |
+
@opencode-ai/ai/providers/anthropic
|
| 1045 |
+
@opencode-ai/ai/providers/google
|
| 1046 |
+
...
|
| 1047 |
+
```
|
| 1048 |
+
|
| 1049 |
+
Providers are imported through individual subpaths. The root does not export all
|
| 1050 |
+
providers, and there is no preferred all-providers barrel.
|
| 1051 |
+
|
| 1052 |
+
## Defaults
|
| 1053 |
+
|
| 1054 |
+
| Concern | Default |
|
| 1055 |
+
| ---------------------------- | --------------------------------------------------- |
|
| 1056 |
+
| `LLM.generate` semantics | Complete Model Run |
|
| 1057 |
+
| `LLM.generateTurn` semantics | Exactly one Provider Turn |
|
| 1058 |
+
| Maximum turns | 20 |
|
| 1059 |
+
| Turn-limit outcome | Successful `max-turns` result |
|
| 1060 |
+
| Tool execution | Automatic in runs |
|
| 1061 |
+
| Tool concurrency | Concurrent, bounded, deterministic result order |
|
| 1062 |
+
| Prompt caching | `auto` |
|
| 1063 |
+
| Retries | Conservative, pre-output transient failures only |
|
| 1064 |
+
| Structured output | Capability-selected native or tool strategy |
|
| 1065 |
+
| Capability mismatch | Typed failure before network execution |
|
| 1066 |
+
| Unknown model capability | Conservative protocol baseline |
|
| 1067 |
+
| Telemetry content | Metadata only |
|
| 1068 |
+
| Cost | Estimated aggregate or unavailable |
|
| 1069 |
+
| Cancellation | Interruption/rejection, never successful completion |
|
| 1070 |
+
|
| 1071 |
+
## Clean-break Migration
|
| 1072 |
+
|
| 1073 |
+
The redesign intentionally removes or changes these current concepts:
|
| 1074 |
+
|
| 1075 |
+
| Current | Proposed |
|
| 1076 |
+
| --------------------------------------- | ----------------------------------------------------------- |
|
| 1077 |
+
| `@opencode-ai/llm` | `@opencode-ai/ai` |
|
| 1078 |
+
| Mandatory `LLM.request({ model, ... })` | Inline calls or model-free portable requests |
|
| 1079 |
+
| `LLM.generate` means one turn | `LLM.generate` means complete run |
|
| 1080 |
+
| `LLMClient.generate/stream` | `LLM.generateTurn/streamTurn` for one turn |
|
| 1081 |
+
| `LLMClient.layer` requirement | Standard Effect requirements exposed directly |
|
| 1082 |
+
| Public `Route` mental model | Hidden behind executable `Model` |
|
| 1083 |
+
| `Provider.make` structural helper | Experimental declarative `Provider.define` |
|
| 1084 |
+
| Schema classes as canonical values | Plain immutable values plus schema subpath |
|
| 1085 |
+
| `LLM.updateRequest` | Object spread |
|
| 1086 |
+
| `Tool.toDefinitions` in normal calls | Named executable tool records |
|
| 1087 |
+
| Manual `ToolRuntime.dispatch` loop | Automatic run dispatch; explicit turn API for orchestration |
|
| 1088 |
+
| `providerOptions: { openai: ... }` | Model-typed `provider: ...` |
|
| 1089 |
+
| `generateObject` | Typed `output` option on `generate` |
|
| 1090 |
+
| One event union for provider output | Separate `TurnEvent` and `RunEvent` unions |
|
| 1091 |
+
| `providerExecuted` dispatch check | Distinct hosted-tool constructors |
|
| 1092 |
+
| One wrapped `LLMError` | Tagged domain error union |
|
| 1093 |
+
|
| 1094 |
+
OpenCode should migrate to `generateTurn` / `streamTurn`, preserving its durable
|
| 1095 |
+
prompt admission, persistence, permission, tool settlement, and continuation
|
| 1096 |
+
boundaries. It should not use the automatic run API for Session orchestration.
|
| 1097 |
+
|
| 1098 |
+
## Remaining Implementation-level Questions
|
| 1099 |
+
|
| 1100 |
+
These do not reopen the main design:
|
| 1101 |
+
|
| 1102 |
+
1. Exact `RunEvent` and `TurnEvent` tag names and payloads
|
| 1103 |
+
2. Exact `GenerateResult` shortcut fields for text, reasoning, output, and messages
|
| 1104 |
+
3. Exact Provider definition TypeScript shape needed for strong inference
|
| 1105 |
+
4. Exact protocol `.with(...)` patch syntax and replacement semantics
|
| 1106 |
+
5. Exact Duration input fields and names
|
| 1107 |
+
6. Exact models.dev generation pipeline and correction-file format
|
| 1108 |
+
7. Exact cost representation and decimal arithmetic strategy
|
| 1109 |
+
8. Exact default retry schedule and bounded tool concurrency number
|
| 1110 |
+
9. Whether request-level serializable HTTP overlays belong in the stable schema
|
| 1111 |
+
10. Which tagged errors are serializable versus process-local
|
| 1112 |
+
|
| 1113 |
+
These should be resolved with call-site sketches and implementation spikes rather
|
| 1114 |
+
than by changing the domain boundaries above.
|
packages/llm/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @opencode-ai/llm
|
| 2 |
+
|
| 3 |
+
Schema-first LLM core for opencode. One typed request, response, event, and tool language; provider quirks live in adapters, not in calling code.
|
| 4 |
+
|
| 5 |
+
```ts
|
| 6 |
+
import { Effect } from "effect"
|
| 7 |
+
import { LLM, LLMClient } from "@opencode-ai/llm"
|
| 8 |
+
import { OpenAI } from "@opencode-ai/llm/providers"
|
| 9 |
+
|
| 10 |
+
const model = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
|
| 11 |
+
|
| 12 |
+
const request = LLM.request({
|
| 13 |
+
model,
|
| 14 |
+
system: "You are concise.",
|
| 15 |
+
prompt: "Say hello in one short sentence.",
|
| 16 |
+
generation: { maxTokens: 40 },
|
| 17 |
+
})
|
| 18 |
+
|
| 19 |
+
const program = Effect.gen(function* () {
|
| 20 |
+
const response = yield* LLMClient.generate(request)
|
| 21 |
+
console.log(response.text)
|
| 22 |
+
})
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
| 26 |
+
|
| 27 |
+
## Public API
|
| 28 |
+
|
| 29 |
+
- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
|
| 30 |
+
- **`LLM.generate` / `LLM.stream`** — re-exported from `LLMClient` for one-import use.
|
| 31 |
+
- **`Message.user(...)` / `Message.assistant(...)` / `Message.tool(...)`** — message constructors from the canonical schema model.
|
| 32 |
+
- **`Model.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
|
| 33 |
+
- **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing.
|
| 34 |
+
- **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
|
| 35 |
+
|
| 36 |
+
## Caching
|
| 37 |
+
|
| 38 |
+
Prompt caching is **on by default**. Every `LLMRequest` resolves to `cache: "auto"` unless the caller opts out with `cache: "none"`. Each protocol translates `CacheHint`s to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI and Gemini do implicit caching server-side and don't need inline markers — auto is a no-op there).
|
| 39 |
+
|
| 40 |
+
### Auto placement
|
| 41 |
+
|
| 42 |
+
`"auto"` places three breakpoints — last tool definition, last system part, latest user message. The last-user-message boundary is the load-bearing detail: in a tool-use loop, a single user turn expands into many assistant/tool round-trips, all sharing that prefix. Caching at that boundary lets every intra-turn API call hit.
|
| 43 |
+
|
| 44 |
+
The math justifies the default: Anthropic's 5-minute cache write is 1.25× base, read is 0.1×, so a single reuse within 5 minutes already wins. One-shot completions below the per-model minimum-cacheable-token threshold silently no-op on the wire, so the worst case is harmless.
|
| 45 |
+
|
| 46 |
+
### Opting out
|
| 47 |
+
|
| 48 |
+
```ts
|
| 49 |
+
LLM.request({
|
| 50 |
+
model,
|
| 51 |
+
system,
|
| 52 |
+
prompt: "one-off question",
|
| 53 |
+
cache: "none",
|
| 54 |
+
})
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Granular policy
|
| 58 |
+
|
| 59 |
+
```ts
|
| 60 |
+
cache: {
|
| 61 |
+
tools?: boolean,
|
| 62 |
+
system?: boolean,
|
| 63 |
+
messages?: "latest-user-message" | "latest-assistant" | { tail: number },
|
| 64 |
+
ttlSeconds?: number, // ≥ 3600 → 1h on Anthropic/Bedrock; else 5m
|
| 65 |
+
}
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Manual hints
|
| 69 |
+
|
| 70 |
+
Inline `CacheHint` on any text / system / tool / tool-result part overrides automatic placement. The auto policy preserves manual hints; it only fills gaps.
|
| 71 |
+
|
| 72 |
+
```ts
|
| 73 |
+
LLM.request({
|
| 74 |
+
model,
|
| 75 |
+
system: [
|
| 76 |
+
{ type: "text", text: "stable system prompt", cache: { type: "ephemeral" } },
|
| 77 |
+
],
|
| 78 |
+
...
|
| 79 |
+
})
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
### Provider behavior table
|
| 83 |
+
|
| 84 |
+
| Protocol | `cache: "auto"` |
|
| 85 |
+
| ----------------------- | ------------------------------------------------------------------------- |
|
| 86 |
+
| Anthropic Messages | emits up to 3 `cache_control` markers (4-breakpoint cap enforced) |
|
| 87 |
+
| Bedrock Converse | emits up to 3 `cachePoint` blocks (4-breakpoint cap enforced) |
|
| 88 |
+
| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) |
|
| 89 |
+
| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) |
|
| 90 |
+
|
| 91 |
+
Normalized cache usage is read back into `response.usage.cacheReadInputTokens` and `cacheWriteInputTokens` across every provider.
|
| 92 |
+
|
| 93 |
+
## Providers
|
| 94 |
+
|
| 95 |
+
Provider facades configure endpoint/auth/deployment details first, then expose model selectors that take only a model or deployment id. The selected model carries the executable route value used at runtime.
|
| 96 |
+
|
| 97 |
+
```ts
|
| 98 |
+
import { OpenAI, CloudflareAIGateway } from "@opencode-ai/llm/providers"
|
| 99 |
+
|
| 100 |
+
const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
|
| 101 |
+
const gateway = CloudflareAIGateway.configure({
|
| 102 |
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
|
| 103 |
+
gatewayApiKey: process.env.CLOUDFLARE_API_TOKEN,
|
| 104 |
+
}).model("workers-ai/@cf/meta/llama-3.1-8b-instruct")
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
Included providers: OpenAI, Anthropic, Google (Gemini), Amazon Bedrock, Azure OpenAI, Cloudflare AI Gateway, Cloudflare Workers AI, GitHub Copilot, OpenRouter, xAI, plus generic OpenAI-compatible helpers for DeepSeek, Cerebras, Groq, Fireworks, Together, etc.
|
| 108 |
+
|
| 109 |
+
## Provider options & HTTP overlays
|
| 110 |
+
|
| 111 |
+
Three escape hatches in order of stability:
|
| 112 |
+
|
| 113 |
+
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
|
| 114 |
+
2. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `promptCacheKey`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
|
| 115 |
+
3. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
|
| 116 |
+
|
| 117 |
+
Route/provider defaults are overridden by request-level values for each axis.
|
| 118 |
+
|
| 119 |
+
## Routes
|
| 120 |
+
|
| 121 |
+
Adding a new model or deployment is usually 5-15 lines using `Route.make({ protocol, endpoint, auth, framing, ... })`. The route owns endpoint/auth/framing and the protocol owns body construction plus stream parsing. Transports are reusable IO templates that receive route endpoint/auth at compile time. Capability/catalog metadata lives outside this low-level package; unsupported request shapes fail during protocol lowering. See `AGENTS.md` for the architectural detail.
|
| 122 |
+
|
| 123 |
+
## Effect
|
| 124 |
+
|
| 125 |
+
This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for runtime dispatch and import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
|
| 126 |
+
|
| 127 |
+
## See also
|
| 128 |
+
|
| 129 |
+
- `AGENTS.md` — architecture, route construction, contributor guide
|
| 130 |
+
- `example/tutorial.ts` — runnable end-to-end walkthrough
|
| 131 |
+
- `test/provider/*.test.ts` — fixture-first protocol tests; `*.recorded.test.ts` files cover live cassettes
|
packages/llm/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/package.json",
|
| 3 |
+
"version": "1.18.31",
|
| 4 |
+
"name": "@opencode-ai/llm",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"license": "MIT",
|
| 7 |
+
"private": true,
|
| 8 |
+
"scripts": {
|
| 9 |
+
"setup:recording-env": "bun run script/setup-recording-env.ts",
|
| 10 |
+
"test": "bun test --timeout 30000 --only-failures",
|
| 11 |
+
"typecheck": "tsgo --noEmit"
|
| 12 |
+
},
|
| 13 |
+
"exports": {
|
| 14 |
+
".": "./src/index.ts",
|
| 15 |
+
"./route": "./src/route/index.ts",
|
| 16 |
+
"./provider": "./src/provider.ts",
|
| 17 |
+
"./providers": "./src/providers/index.ts",
|
| 18 |
+
"./providers/amazon-bedrock": "./src/providers/amazon-bedrock.ts",
|
| 19 |
+
"./providers/anthropic": "./src/providers/anthropic.ts",
|
| 20 |
+
"./providers/azure": "./src/providers/azure.ts",
|
| 21 |
+
"./providers/cloudflare": "./src/providers/cloudflare.ts",
|
| 22 |
+
"./providers/github-copilot": "./src/providers/github-copilot.ts",
|
| 23 |
+
"./providers/google": "./src/providers/google.ts",
|
| 24 |
+
"./providers/openai": "./src/providers/openai.ts",
|
| 25 |
+
"./providers/openai-compatible": "./src/providers/openai-compatible.ts",
|
| 26 |
+
"./providers/openai-compatible-profile": "./src/providers/openai-compatible-profile.ts",
|
| 27 |
+
"./providers/openrouter": "./src/providers/openrouter.ts",
|
| 28 |
+
"./providers/xai": "./src/providers/xai.ts",
|
| 29 |
+
"./protocols": "./src/protocols/index.ts",
|
| 30 |
+
"./protocols/anthropic-messages": "./src/protocols/anthropic-messages.ts",
|
| 31 |
+
"./protocols/bedrock-converse": "./src/protocols/bedrock-converse.ts",
|
| 32 |
+
"./protocols/gemini": "./src/protocols/gemini.ts",
|
| 33 |
+
"./protocols/openai-chat": "./src/protocols/openai-chat.ts",
|
| 34 |
+
"./protocols/openai-compatible-chat": "./src/protocols/openai-compatible-chat.ts",
|
| 35 |
+
"./protocols/openai-responses": "./src/protocols/openai-responses.ts"
|
| 36 |
+
},
|
| 37 |
+
"devDependencies": {
|
| 38 |
+
"@clack/prompts": "1.0.0-alpha.1",
|
| 39 |
+
"@effect/platform-node": "catalog:",
|
| 40 |
+
"@opencode-ai/http-recorder": "workspace:*",
|
| 41 |
+
"@tsconfig/bun": "catalog:",
|
| 42 |
+
"@types/bun": "catalog:",
|
| 43 |
+
"@typescript/native-preview": "catalog:"
|
| 44 |
+
},
|
| 45 |
+
"dependencies": {
|
| 46 |
+
"@smithy/eventstream-codec": "4.2.14",
|
| 47 |
+
"@smithy/util-utf8": "4.2.2",
|
| 48 |
+
"@opencode-ai/schema": "workspace:*",
|
| 49 |
+
"aws4fetch": "1.0.20",
|
| 50 |
+
"effect": "catalog:"
|
| 51 |
+
}
|
| 52 |
+
}
|
packages/llm/sst-env.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This file is auto-generated by SST. Do not edit. */
|
| 2 |
+
/* tslint:disable */
|
| 3 |
+
/* eslint-disable */
|
| 4 |
+
/* deno-fmt-ignore-file */
|
| 5 |
+
/* biome-ignore-all lint: auto-generated */
|
| 6 |
+
|
| 7 |
+
/// <reference path="../../sst-env.d.ts" />
|
| 8 |
+
|
| 9 |
+
import "sst"
|
| 10 |
+
export {}
|
packages/llm/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json.schemastore.org/tsconfig",
|
| 3 |
+
"extends": "@tsconfig/bun/tsconfig.json",
|
| 4 |
+
"compilerOptions": {
|
| 5 |
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
| 6 |
+
"noUncheckedIndexedAccess": false,
|
| 7 |
+
"plugins": [
|
| 8 |
+
{
|
| 9 |
+
"name": "@effect/language-service",
|
| 10 |
+
"transform": "@effect/language-service/transform",
|
| 11 |
+
"namespaceImportPackages": ["effect", "@effect/*"]
|
| 12 |
+
}
|
| 13 |
+
]
|
| 14 |
+
}
|
| 15 |
+
}
|