mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44f4211357 |
@@ -325,7 +325,6 @@ jobs:
|
|||||||
run: bun run build
|
run: bun run build
|
||||||
working-directory: packages/desktop
|
working-directory: packages/desktop
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ on:
|
|||||||
- "bun.lock"
|
- "bun.lock"
|
||||||
- "packages/storybook/**"
|
- "packages/storybook/**"
|
||||||
- "packages/ui/**"
|
- "packages/ui/**"
|
||||||
- "packages/session-ui/**"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [dev]
|
branches: [dev]
|
||||||
paths:
|
paths:
|
||||||
@@ -18,7 +17,6 @@ on:
|
|||||||
- "bun.lock"
|
- "bun.lock"
|
||||||
- "packages/storybook/**"
|
- "packages/storybook/**"
|
||||||
- "packages/ui/**"
|
- "packages/ui/**"
|
||||||
- "packages/session-ui/**"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -57,6 +57,13 @@ The bounded projection of a Core-executed tool result persisted in Session histo
|
|||||||
**Managed Tool Output File**:
|
**Managed Tool Output File**:
|
||||||
A temporary file created under OpenCode's shared tool-output directory to retain complete output that was too large for Session history.
|
A temporary file created under OpenCode's shared tool-output directory to retain complete output that was too large for Session history.
|
||||||
|
|
||||||
|
**Model Request Options**:
|
||||||
|
Provider-semantic model settings selected from the Catalog and active Session variant before the LLM protocol adapter encodes them for a provider request.
|
||||||
|
_Avoid_: Request body, wire options
|
||||||
|
|
||||||
|
**Generation Controls**:
|
||||||
|
Provider-neutral sampling and output controls, partitioned from provider semantics and compatibility wire fields when model metadata enters the Catalog.
|
||||||
|
|
||||||
**PTY Environment**:
|
**PTY Environment**:
|
||||||
The host-supplied environment overlay applied by the server when creating a PTY, observed for the request Location and resolved PTY working directory.
|
The host-supplied environment overlay applied by the server when creating a PTY, observed for the request Location and resolved PTY working directory.
|
||||||
|
|
||||||
@@ -107,6 +114,8 @@ The host-supplied environment overlay applied by the server when creating a PTY,
|
|||||||
- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
|
- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
|
||||||
- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
|
- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
|
||||||
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
|
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
|
||||||
|
- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
|
||||||
|
- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
|
||||||
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
|
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
|
||||||
- A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
|
- A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
|
||||||
- A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.
|
- A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.
|
||||||
|
|||||||
@@ -29,12 +29,11 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@pierre/trees": "1.0.0-beta.4",
|
"@pierre/trees": "1.0.0-beta.4",
|
||||||
"@sentry/solid": "catalog:",
|
"@sentry/solid": "catalog:",
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"bin": {
|
"bin": {
|
||||||
"lildax": "./bin/lildax.cjs",
|
"lildax": "./bin/lildax.cjs",
|
||||||
},
|
},
|
||||||
@@ -112,7 +111,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -148,7 +147,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -175,7 +174,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.82",
|
"@ai-sdk/anthropic": "3.0.82",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -197,7 +196,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -221,7 +220,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/support": {
|
"packages/console/support": {
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
@@ -241,7 +240,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -334,7 +333,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -388,7 +387,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-drizzle-sqlite": {
|
"packages/effect-drizzle-sqlite": {
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -402,7 +401,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-sqlite-node": {
|
"packages/effect-sqlite-node": {
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -414,11 +413,10 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/standard-validator": "catalog:",
|
"@hono/standard-validator": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
@@ -446,7 +444,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -462,7 +460,7 @@
|
|||||||
},
|
},
|
||||||
"packages/http-recorder": {
|
"packages/http-recorder": {
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.83",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||||
@@ -481,7 +479,7 @@
|
|||||||
},
|
},
|
||||||
"packages/llm": {
|
"packages/llm": {
|
||||||
"name": "@opencode-ai/llm",
|
"name": "@opencode-ai/llm",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@smithy/eventstream-codec": "4.2.14",
|
"@smithy/eventstream-codec": "4.2.14",
|
||||||
@@ -500,7 +498,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -539,7 +537,6 @@
|
|||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
"@opencode-ai/llm": "workspace:*",
|
"@opencode-ai/llm": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/server": "workspace:*",
|
"@opencode-ai/server": "workspace:*",
|
||||||
@@ -629,7 +626,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -679,7 +676,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -694,7 +691,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@opencode-ai/server",
|
"name": "@opencode-ai/server",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
@@ -706,53 +703,9 @@
|
|||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/session-ui": {
|
|
||||||
"name": "@opencode-ai/session-ui",
|
|
||||||
"version": "1.17.10",
|
|
||||||
"dependencies": {
|
|
||||||
"@kobalte/core": "catalog:",
|
|
||||||
"@opencode-ai/core": "workspace:*",
|
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
|
||||||
"@pierre/diffs": "catalog:",
|
|
||||||
"@shikijs/stream": "catalog:",
|
|
||||||
"@shikijs/transformers": "3.9.2",
|
|
||||||
"@solid-primitives/bounds": "0.1.3",
|
|
||||||
"@solid-primitives/event-listener": "2.4.5",
|
|
||||||
"@solid-primitives/media": "2.3.3",
|
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
|
||||||
"@solidjs/meta": "catalog:",
|
|
||||||
"@solidjs/router": "catalog:",
|
|
||||||
"diff": "catalog:",
|
|
||||||
"dompurify": "3.3.1",
|
|
||||||
"fuzzysort": "catalog:",
|
|
||||||
"katex": "0.16.27",
|
|
||||||
"luxon": "catalog:",
|
|
||||||
"marked": "catalog:",
|
|
||||||
"marked-katex-extension": "5.1.6",
|
|
||||||
"marked-shiki": "catalog:",
|
|
||||||
"morphdom": "2.7.8",
|
|
||||||
"motion": "12.34.5",
|
|
||||||
"remeda": "catalog:",
|
|
||||||
"remend": "catalog:",
|
|
||||||
"shiki": "catalog:",
|
|
||||||
"solid-js": "catalog:",
|
|
||||||
"solid-list": "catalog:",
|
|
||||||
"strip-ansi": "7.1.2",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tsconfig/node22": "catalog:",
|
|
||||||
"@types/bun": "catalog:",
|
|
||||||
"@types/katex": "0.16.7",
|
|
||||||
"@types/luxon": "catalog:",
|
|
||||||
"@typescript/native-preview": "catalog:",
|
|
||||||
"typescript": "catalog:",
|
|
||||||
"vite": "catalog:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -765,7 +718,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/app": {
|
"packages/stats/app": {
|
||||||
"name": "@opencode-ai/stats-app",
|
"name": "@opencode-ai/stats-app",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@opencode-ai/stats-core": "workspace:*",
|
"@opencode-ai/stats-core": "workspace:*",
|
||||||
@@ -798,7 +751,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/core": {
|
"packages/stats/core": {
|
||||||
"name": "@opencode-ai/stats-core",
|
"name": "@opencode-ai/stats-core",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-athena": "3.933.0",
|
"@aws-sdk/client-athena": "3.933.0",
|
||||||
"@planetscale/database": "1.19.0",
|
"@planetscale/database": "1.19.0",
|
||||||
@@ -817,7 +770,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/server": {
|
"packages/stats/server": {
|
||||||
"name": "@opencode-ai/stats-server",
|
"name": "@opencode-ai/stats-server",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-firehose": "3.933.0",
|
"@aws-sdk/client-firehose": "3.933.0",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
@@ -836,7 +789,6 @@
|
|||||||
"packages/storybook": {
|
"packages/storybook": {
|
||||||
"name": "@opencode-ai/storybook",
|
"name": "@opencode-ai/storybook",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@storybook/addon-a11y": "^10.2.13",
|
"@storybook/addon-a11y": "^10.2.13",
|
||||||
@@ -858,7 +810,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@opencode-ai/tui",
|
"name": "@opencode-ai/tui",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
@@ -885,9 +837,11 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
|
"@opencode-ai/core": "workspace:*",
|
||||||
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@shikijs/stream": "catalog:",
|
"@shikijs/stream": "catalog:",
|
||||||
"@shikijs/transformers": "3.9.2",
|
"@shikijs/transformers": "3.9.2",
|
||||||
@@ -932,7 +886,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -1871,8 +1825,6 @@
|
|||||||
|
|
||||||
"@opencode-ai/server": ["@opencode-ai/server@workspace:packages/server"],
|
"@opencode-ai/server": ["@opencode-ai/server@workspace:packages/server"],
|
||||||
|
|
||||||
"@opencode-ai/session-ui": ["@opencode-ai/session-ui@workspace:packages/session-ui"],
|
|
||||||
|
|
||||||
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
|
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
|
||||||
|
|
||||||
"@opencode-ai/stats-app": ["@opencode-ai/stats-app@workspace:packages/stats/app"],
|
"@opencode-ai/stats-app": ["@opencode-ai/stats-app@workspace:packages/stats/app"],
|
||||||
@@ -5891,8 +5843,6 @@
|
|||||||
|
|
||||||
"@opencode-ai/llm/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
"@opencode-ai/llm/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||||
|
|
||||||
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
|
||||||
|
|
||||||
"@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
"@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||||
|
|
||||||
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
|
||||||
|
|||||||
+1
-2
@@ -42,12 +42,11 @@ const inferenceEventTable = new aws.s3tables.Table(
|
|||||||
{ name: "request", type: "string", required: false },
|
{ name: "request", type: "string", required: false },
|
||||||
{ name: "client", type: "string", required: false },
|
{ name: "client", type: "string", required: false },
|
||||||
{ name: "user_agent", type: "string", required: false },
|
{ name: "user_agent", type: "string", required: false },
|
||||||
{ name: "model", type: "string", required: false },
|
|
||||||
{ name: "model_tier", type: "string", required: false },
|
|
||||||
{ name: "model_variant", type: "string", required: false },
|
{ name: "model_variant", type: "string", required: false },
|
||||||
{ name: "source", type: "string", required: false },
|
{ name: "source", type: "string", required: false },
|
||||||
{ name: "provider", type: "string", required: false },
|
{ name: "provider", type: "string", required: false },
|
||||||
{ name: "provider_model", type: "string", required: false },
|
{ name: "provider_model", type: "string", required: false },
|
||||||
|
{ name: "model", type: "string", required: false },
|
||||||
{ name: "llm_error_code", type: "int", required: false },
|
{ name: "llm_error_code", type: "int", required: false },
|
||||||
{ name: "llm_error_message", type: "string", required: false },
|
{ name: "llm_error_message", type: "string", required: false },
|
||||||
{ name: "error_response", type: "string", required: false },
|
{ name: "error_response", type: "string", required: false },
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-gV5PVd9+MB5tM3pPH+S6YhGPRPXqHv5Ayw+8gngNlqY=",
|
"x86_64-linux": "sha256-JJnJVqP2+NiiRVoTKXjGD09lPRyfz8YMfx0m3jBaUzU=",
|
||||||
"aarch64-linux": "sha256-1pxoseGyRwAawiPblo8FeaaAAdl0mkKRs6BS8qxxoh0=",
|
"aarch64-linux": "sha256-tY3/IA+iZbOQQGxrcHMvQ1xqByskZyVI+4LZkU0wVjs=",
|
||||||
"aarch64-darwin": "sha256-81D/v2Ka7mtFHp4iM9eB9kjFcumSTvR07ABb1ahRLS0=",
|
"aarch64-darwin": "sha256-9qMRrQKKOgQuOcAgAq8oZacVFD7G4nbAfZawmCRuJdY=",
|
||||||
"x86_64-darwin": "sha256-nolxEbc/1ItbmuHPUjaSu3DHwBV/0/Si2xuM6wpiq0k="
|
"x86_64-darwin": "sha256-dR2VGxDLoAPEmk8SsEF4dhlPBcb9ubztAuVfwxuAD4M="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@pierre/trees": "1.0.0-beta.4",
|
"@pierre/trees": "1.0.0-beta.4",
|
||||||
"@sentry/solid": "catalog:",
|
"@sentry/solid": "catalog:",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { I18nProvider } from "@opencode-ai/ui/context"
|
|||||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||||
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
||||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||||
import { File } from "@opencode-ai/session-ui/file"
|
import { File } from "@opencode-ai/ui/file"
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { Splash } from "@opencode-ai/ui/logo"
|
import { Splash } from "@opencode-ai/ui/logo"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { same } from "@/utils/same"
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Accordion } from "@opencode-ai/ui/accordion"
|
import { Accordion } from "@opencode-ai/ui/accordion"
|
||||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||||
import { File } from "@opencode-ai/session-ui/file"
|
import { File } from "@opencode-ai/ui/file"
|
||||||
import { Markdown } from "@opencode-ai/session-ui/markdown"
|
import { Markdown } from "@opencode-ai/ui/markdown"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { QueryClient } from "@tanstack/solid-query"
|
import { QueryClient } from "@tanstack/solid-query"
|
||||||
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client"
|
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client"
|
||||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import type { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap"
|
import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap"
|
||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { ServerScope } from "@/utils/server-scope"
|
import { ServerScope } from "@/utils/server-scope"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
|||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||||
import { loadMcpQuery } from "../server-sync"
|
import { loadMcpQuery } from "../server-sync"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { beforeAll, describe, expect, mock, test } from "bun:test"
|
import { beforeAll, describe, expect, mock, test } from "bun:test"
|
||||||
import { createRoot, getOwner, type Owner } from "solid-js"
|
import { createRoot, getOwner, type Owner } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import type { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import type { State } from "./types"
|
import type { State } from "./types"
|
||||||
import type { QueryOptionsApi } from "../server-sync"
|
import type { QueryOptionsApi } from "../server-sync"
|
||||||
import { ServerScope } from "@/utils/server-scope"
|
import { ServerScope } from "@/utils/server-scope"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction"
|
|||||||
import { useQuery } from "@tanstack/solid-query"
|
import { useQuery } from "@tanstack/solid-query"
|
||||||
import { QueryOptionsApi } from "../server-sync"
|
import { QueryOptionsApi } from "../server-sync"
|
||||||
import { directoryKey, type DirectoryKey } from "./utils"
|
import { directoryKey, type DirectoryKey } from "./utils"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import type { ServerScope } from "@/utils/server-scope"
|
import type { ServerScope } from "@/utils/server-scope"
|
||||||
|
|
||||||
export function createChildStoreManager(input: {
|
export function createChildStoreManager(input: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {
|
|||||||
Todo,
|
Todo,
|
||||||
VcsInfo,
|
VcsInfo,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import type { Accessor } from "solid-js"
|
import type { Accessor } from "solid-js"
|
||||||
import type { SetStoreFunction, Store } from "solid-js/store"
|
import type { SetStoreFunction, Store } from "solid-js/store"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Agent, Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client"
|
import type { Agent, Project, ProviderListResponse } from "@opencode-ai/sdk/v2/client"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
import { NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
export { pathKey as directoryKey, type PathKey as DirectoryKey } from "@/utils/path-key"
|
export { pathKey as directoryKey, type PathKey as DirectoryKey } from "@/utils/path-key"
|
||||||
|
|
||||||
export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ import { createRefreshQueue } from "./global-sync/queue"
|
|||||||
import { directoryKey } from "./global-sync/utils"
|
import { directoryKey } from "./global-sync/utils"
|
||||||
import { PathKey } from "@/utils/path-key"
|
import { PathKey } from "@/utils/path-key"
|
||||||
import { createDirSyncContext } from "./directory-sync"
|
import { createDirSyncContext } from "./directory-sync"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext, NormalizedProviderListResponse } from "@opencode-ai/ui/context"
|
||||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
|
||||||
import { createRefCountMap } from "@/utils/refcount"
|
import { createRefCountMap } from "@/utils/refcount"
|
||||||
import { useGlobal } from "./global"
|
import { useGlobal } from "./global"
|
||||||
import { ServerConnection, useServer } from "./server"
|
import { ServerConnection, useServer } from "./server"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@import "@opencode-ai/ui/styles/tailwind";
|
@import "@opencode-ai/ui/styles/tailwind";
|
||||||
@import "@opencode-ai/session-ui/styles";
|
|
||||||
@import "@opencode-ai/ui/v2/styles/tailwind.css";
|
@import "@opencode-ai/ui/v2/styles/tailwind.css";
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DataProvider } from "@opencode-ai/session-ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import { ServerHealthIndicator } from "@/components/server/server-row"
|
|||||||
import { type ServerHealth } from "@/utils/server-health"
|
import { type ServerHealth } from "@/utils/server-health"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
import { useMarked } from "@opencode-ai/ui/context/marked"
|
import { useMarked } from "@opencode-ai/ui/context/marked"
|
||||||
import { preloadMarkdown } from "@opencode-ai/session-ui/markdown-cache"
|
import { preloadMarkdown } from "@opencode-ai/ui/markdown-cache"
|
||||||
|
|
||||||
const HOME_SESSION_LIMIT = 64
|
const HOME_SESSION_LIMIT = 64
|
||||||
const HOME_ROW_LAYOUT =
|
const HOME_ROW_LAYOUT =
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getProjectAvatarVariant } from "@/context/layout"
|
|||||||
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
||||||
import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state"
|
import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state"
|
||||||
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
|
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
|
||||||
import { SessionProgressIndicatorV2 } from "@opencode-ai/session-ui/v2/session-progress-indicator-v2"
|
import { SessionProgressIndicatorV2 } from "@opencode-ai/ui/v2/session-progress-indicator-v2"
|
||||||
import { Show } from "solid-js"
|
import { Show } from "solid-js"
|
||||||
|
|
||||||
export function SessionTabAvatar(props: {
|
export function SessionTabAvatar(props: {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
|||||||
import { Select } from "@opencode-ai/ui/select"
|
import { Select } from "@opencode-ai/ui/select"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||||
import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge"
|
import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { For, Show } from "solid-js"
|
import { For, Show } from "solid-js"
|
||||||
import type { PermissionRequest } from "@opencode-ai/sdk/v2"
|
import type { PermissionRequest } from "@opencode-ai/sdk/v2"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useMutation } from "@tanstack/solid-query"
|
import { useMutation } from "@tanstack/solid-query"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { createEffect, createMemo, createSignal, Match, on, onCleanup, Switch }
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import type { FileSearchHandle } from "@opencode-ai/session-ui/file"
|
import type { FileSearchHandle } from "@opencode-ai/ui/file"
|
||||||
import { useFileComponent } from "@opencode-ai/ui/context/file"
|
import { useFileComponent } from "@opencode-ai/ui/context/file"
|
||||||
import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge"
|
import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
||||||
import { createLineCommentController } from "@opencode-ai/session-ui/line-comment-annotations"
|
import { createLineCommentController } from "@opencode-ai/ui/line-comment-annotations"
|
||||||
import { sampledChecksum } from "@opencode-ai/core/util/encode"
|
import { sampledChecksum } from "@opencode-ai/core/util/encode"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { createEffect, onCleanup, type JSX } from "solid-js"
|
import { createEffect, onCleanup, type JSX } from "solid-js"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
||||||
import { SessionReview } from "@opencode-ai/session-ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import type {
|
import type {
|
||||||
SessionReviewCommentActions,
|
SessionReviewCommentActions,
|
||||||
SessionReviewCommentDelete,
|
SessionReviewCommentDelete,
|
||||||
SessionReviewCommentUpdate,
|
SessionReviewCommentUpdate,
|
||||||
} from "@opencode-ai/session-ui/session-review"
|
} from "@opencode-ai/ui/session-review"
|
||||||
import type { SelectedLineRange } from "@/context/file"
|
import type { SelectedLineRange } from "@/context/file"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
Part as MessagePart,
|
Part as MessagePart,
|
||||||
partDefaultOpen,
|
partDefaultOpen,
|
||||||
type UserActions,
|
type UserActions,
|
||||||
} from "@opencode-ai/session-ui/message-part"
|
} from "@opencode-ai/ui/message-part"
|
||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
@@ -35,7 +35,7 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
|||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { SessionRetry } from "@opencode-ai/session-ui/session-retry"
|
import { SessionRetry } from "@opencode-ai/ui/session-retry"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
@@ -51,7 +51,7 @@ import type {
|
|||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { Popover as KobaltePopover } from "@kobalte/core/popover"
|
import { Popover as KobaltePopover } from "@kobalte/core/popover"
|
||||||
import { normalize } from "@opencode-ai/session-ui/session-diff"
|
import { normalize } from "@opencode-ai/ui/session-diff"
|
||||||
import { useFileComponent } from "@opencode-ai/ui/context/file"
|
import { useFileComponent } from "@opencode-ai/ui/context/file"
|
||||||
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
||||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
||||||
import { AssistantMessage, Part, SessionStatus, SnapshotFileDiff, UserMessage } from "@opencode-ai/sdk/v2"
|
import { AssistantMessage, Part, SessionStatus, SnapshotFileDiff, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { groupParts, PartGroup, renderable } from "@opencode-ai/session-ui/message-part"
|
import { groupParts, PartGroup, renderable } from "@opencode-ai/ui/message-part"
|
||||||
import { Data, Equal } from "effect"
|
import { Data, Equal } from "effect"
|
||||||
|
|
||||||
export type SummaryDiff = SnapshotFileDiff & { file: string }
|
export type SummaryDiff = SnapshotFileDiff & { file: string }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge"
|
import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
||||||
import { useFile, selectionFromLines, type FileSelection, type SelectedLineRange } from "@/context/file"
|
import { useFile, selectionFromLines, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ export async function handler(
|
|||||||
providerInfo.modifyHeaders(headers, providerInfo.apiKey, stickyId)
|
providerInfo.modifyHeaders(headers, providerInfo.apiKey, stickyId)
|
||||||
Object.entries(providerInfo.headerModifier ?? {}).forEach(([k, v]) => {
|
Object.entries(providerInfo.headerModifier ?? {}).forEach(([k, v]) => {
|
||||||
if (v === "$ip") return headers.set(k, ip)
|
if (v === "$ip") return headers.set(k, ip)
|
||||||
if (v === "$caller") return headers.set(k, `caller:${ip}`)
|
|
||||||
if (v === "$session") return headers.set(k, sessionId)
|
if (v === "$session") return headers.set(k, sessionId)
|
||||||
if (v === "$model") return headers.set(k, model)
|
if (v === "$model") return headers.set(k, model)
|
||||||
if (v === "$request") return headers.set(k, requestId)
|
if (v === "$request") return headers.set(k, requestId)
|
||||||
@@ -355,7 +354,7 @@ export async function handler(
|
|||||||
responseLength += value.length
|
responseLength += value.length
|
||||||
buffer += decoder.decode(value, { stream: true })
|
buffer += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
const parts = buffer.split(/\r\n\r\n|\n\n|\r\r/)
|
const parts = buffer.split(providerInfo.streamSeparator)
|
||||||
buffer = parts.pop() ?? ""
|
buffer = parts.pop() ?? ""
|
||||||
|
|
||||||
for (let part of parts) {
|
for (let part of parts) {
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) =>
|
|||||||
return encoder.encode(messages.join(""))
|
return encoder.encode(messages.join(""))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
streamSeparator: "\n\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const googleHelper: ProviderHelper = ({ providerModel }) => ({
|
|||||||
return body
|
return body
|
||||||
},
|
},
|
||||||
createBinaryStreamDecoder: () => undefined,
|
createBinaryStreamDecoder: () => undefined,
|
||||||
|
streamSeparator: "\r\n\r\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage }) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createBinaryStreamDecoder: () => undefined,
|
createBinaryStreamDecoder: () => undefined,
|
||||||
|
streamSeparator: "\n\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const openaiHelper: ProviderHelper = ({ workspaceID }) => ({
|
|||||||
},
|
},
|
||||||
modifyBody: (body: Record<string, any>) => body,
|
modifyBody: (body: Record<string, any>) => body,
|
||||||
createBinaryStreamDecoder: () => undefined,
|
createBinaryStreamDecoder: () => undefined,
|
||||||
|
streamSeparator: "\n\n",
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
let usage: Usage
|
let usage: Usage
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export type ProviderHelper = (input: {
|
|||||||
modifyHeaders: (headers: Headers, apiKey: string, stickyId: string) => void
|
modifyHeaders: (headers: Headers, apiKey: string, stickyId: string) => void
|
||||||
modifyBody: (body: Record<string, any>) => Record<string, any>
|
modifyBody: (body: Record<string, any>) => Record<string, any>
|
||||||
createBinaryStreamDecoder: () => ((chunk: Uint8Array) => Uint8Array | undefined) | undefined
|
createBinaryStreamDecoder: () => ((chunk: Uint8Array) => Uint8Array | undefined) | undefined
|
||||||
|
streamSeparator: string
|
||||||
createUsageParser: () => {
|
createUsageParser: () => {
|
||||||
parse: (chunk: string) => void
|
parse: (chunk: string) => void
|
||||||
retrieve: () => any
|
retrieve: () => any
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -118,12 +118,11 @@ function toLakeEvent(time: string, data: Record<string, unknown>) {
|
|||||||
request: string(data, "request"),
|
request: string(data, "request"),
|
||||||
client: string(data, "client"),
|
client: string(data, "client"),
|
||||||
user_agent: string(data, "user_agent"),
|
user_agent: string(data, "user_agent"),
|
||||||
model: string(data, "model"),
|
|
||||||
model_tier: string(data, "model.tier"),
|
|
||||||
model_variant: string(data, "model.variant"),
|
model_variant: string(data, "model.variant"),
|
||||||
source: string(data, "source"),
|
source: string(data, "source"),
|
||||||
provider: string(data, "provider"),
|
provider: string(data, "provider"),
|
||||||
provider_model: string(data, "provider.model"),
|
provider_model: string(data, "provider.model"),
|
||||||
|
model: string(data, "model"),
|
||||||
llm_error_code: integer(data, "llm.error.code"),
|
llm_error_code: integer(data, "llm.error.code"),
|
||||||
llm_error_message: string(data, "llm.error.message"),
|
llm_error_message: string(data, "llm.error.message"),
|
||||||
error_response: string(data, "error.response"),
|
error_response: string(data, "error.response"),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export * as Catalog from "./catalog"
|
export * as Catalog from "./catalog"
|
||||||
|
|
||||||
import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect"
|
import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect"
|
||||||
import { Catalog } from "@opencode-ai/schema/catalog"
|
|
||||||
import { ModelV2 } from "./model"
|
import { ModelV2 } from "./model"
|
||||||
|
import { ModelRequest } from "./model-request"
|
||||||
import { ProviderV2 } from "./provider"
|
import { ProviderV2 } from "./provider"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { Policy } from "./policy"
|
import { Policy } from "./policy"
|
||||||
@@ -18,7 +18,9 @@ export type DefaultModel = { providerID: ProviderV2.ID; modelID: ModelV2.ID }
|
|||||||
|
|
||||||
export const PolicyActions = Schema.Literals(["provider.use"])
|
export const PolicyActions = Schema.Literals(["provider.use"])
|
||||||
|
|
||||||
export const Event = Catalog.Event
|
export const Event = {
|
||||||
|
Updated: EventV2.define({ type: "catalog.updated", schema: {} }),
|
||||||
|
}
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
providers: Map<ProviderV2.ID, ProviderRecord>
|
providers: Map<ProviderV2.ID, ProviderRecord>
|
||||||
@@ -84,8 +86,7 @@ export const layer = Layer.effect(
|
|||||||
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
|
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
|
||||||
: model.api
|
: model.api
|
||||||
const request = {
|
const request = {
|
||||||
headers: { ...provider.request.headers, ...model.request.headers },
|
...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),
|
||||||
body: { ...provider.request.body, ...model.request.body },
|
|
||||||
variant: model.request.variant,
|
variant: model.request.variant,
|
||||||
}
|
}
|
||||||
return ModelV2.Info.make({
|
return ModelV2.Info.make({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { define } from "../../plugin/internal"
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Config } from "../../config"
|
import { Config } from "../../config"
|
||||||
import { ModelV2 } from "../../model"
|
import { ModelV2 } from "../../model"
|
||||||
|
import { ModelRequest } from "../../model-request"
|
||||||
import { ProviderV2 } from "../../provider"
|
import { ProviderV2 } from "../../provider"
|
||||||
|
|
||||||
export const Plugin = define({
|
export const Plugin = define({
|
||||||
@@ -58,11 +59,15 @@ export const Plugin = define({
|
|||||||
Object.assign(provider.request.body, item.request.body)
|
Object.assign(provider.request.body, item.request.body)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const providerApi = catalog.provider.get(providerID)?.provider.api
|
||||||
|
const providerPackage = providerApi?.type === "aisdk" ? providerApi.package : undefined
|
||||||
|
|
||||||
for (const [id, config] of Object.entries(item.models ?? {})) {
|
for (const [id, config] of Object.entries(item.models ?? {})) {
|
||||||
catalog.model.update(providerID, id, (model) => {
|
catalog.model.update(providerID, id, (model) => {
|
||||||
if (config.family !== undefined) model.family = config.family
|
if (config.family !== undefined) model.family = config.family
|
||||||
if (config.name !== undefined) model.name = config.name
|
if (config.name !== undefined) model.name = config.name
|
||||||
if (config.api !== undefined) model.api = { ...model.api, ...config.api }
|
if (config.api !== undefined) model.api = { ...model.api, ...config.api }
|
||||||
|
const packageName = model.api.type === "aisdk" ? model.api.package : providerPackage
|
||||||
if (config.capabilities !== undefined) {
|
if (config.capabilities !== undefined) {
|
||||||
model.capabilities = {
|
model.capabilities = {
|
||||||
tools: config.capabilities.tools,
|
tools: config.capabilities.tools,
|
||||||
@@ -71,8 +76,10 @@ export const Plugin = define({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.request !== undefined) {
|
if (config.request !== undefined) {
|
||||||
Object.assign(model.request.headers, config.request.headers)
|
ModelRequest.assign(model.request, {
|
||||||
Object.assign(model.request.body, config.request.body)
|
headers: config.request.headers,
|
||||||
|
...ModelRequest.normalizeAiSdkOptions(packageName, config.request.body ?? {}),
|
||||||
|
})
|
||||||
if (config.request.variant !== undefined) model.request.variant = config.request.variant
|
if (config.request.variant !== undefined) model.request.variant = config.request.variant
|
||||||
}
|
}
|
||||||
if (config.variants !== undefined) {
|
if (config.variants !== undefined) {
|
||||||
@@ -83,11 +90,15 @@ export const Plugin = define({
|
|||||||
id: variant.id,
|
id: variant.id,
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
|
generation: {},
|
||||||
|
options: {},
|
||||||
}
|
}
|
||||||
model.variants.push(existing)
|
model.variants.push(existing)
|
||||||
}
|
}
|
||||||
Object.assign(existing.headers, variant.headers)
|
ModelRequest.assign(existing, {
|
||||||
Object.assign(existing.body, variant.body)
|
headers: variant.headers,
|
||||||
|
...ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.cost !== undefined) {
|
if (config.cost !== undefined) {
|
||||||
|
|||||||
+94
-23
@@ -1,19 +1,46 @@
|
|||||||
export * as EventV2 from "./event"
|
export * as EventV2 from "./event"
|
||||||
|
|
||||||
import { Cause, Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
|
import { Cause, Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
|
||||||
import { Event } from "@opencode-ai/schema/event"
|
|
||||||
import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
|
|
||||||
import { and, asc, eq, gt } from "drizzle-orm"
|
import { and, asc, eq, gt } from "drizzle-orm"
|
||||||
import { Database } from "./database/database"
|
import { Database } from "./database/database"
|
||||||
import { EventSequenceTable, EventTable } from "./event/sql"
|
import { EventSequenceTable, EventTable } from "./event/sql"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
|
import { withStatics } from "./schema"
|
||||||
|
import { Identifier } from "./util/identifier"
|
||||||
import { LayerNode } from "./effect/layer-node"
|
import { LayerNode } from "./effect/layer-node"
|
||||||
import { isDeepStrictEqual } from "node:util"
|
import { isDeepStrictEqual } from "node:util"
|
||||||
import { Durable } from "@opencode-ai/schema/durable-event-manifest"
|
|
||||||
|
|
||||||
export const ID = Event.ID
|
export const ID = Schema.String.check(Schema.isStartsWith("evt_")).pipe(
|
||||||
export type ID = import("@opencode-ai/schema/event").ID
|
Schema.brand("Event.ID"),
|
||||||
export type { Data, Definition, Payload } from "@opencode-ai/schema/event"
|
withStatics((schema) => ({
|
||||||
|
create: () => schema.make("evt_" + Identifier.ascending()),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
export type ID = typeof ID.Type
|
||||||
|
|
||||||
|
export type Definition<Type extends string = string, DataSchema extends Schema.Top = Schema.Top> = {
|
||||||
|
readonly type: Type
|
||||||
|
readonly durable?: {
|
||||||
|
readonly version: number
|
||||||
|
readonly aggregate: string
|
||||||
|
}
|
||||||
|
readonly data: DataSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Data<D extends Definition> = Schema.Schema.Type<D["data"]>
|
||||||
|
|
||||||
|
export type Payload<D extends Definition = Definition> = {
|
||||||
|
readonly id: ID
|
||||||
|
readonly type: D["type"]
|
||||||
|
readonly data: Data<D>
|
||||||
|
readonly durable?: {
|
||||||
|
readonly aggregateID: string
|
||||||
|
readonly seq: number
|
||||||
|
readonly version: number
|
||||||
|
}
|
||||||
|
readonly location?: Location.Ref
|
||||||
|
readonly metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export type Subscriber<D extends Definition = Definition> = (event: Payload<D>) => Effect.Effect<void>
|
export type Subscriber<D extends Definition = Definition> = (event: Payload<D>) => Effect.Effect<void>
|
||||||
export type Unsubscribe = Effect.Effect<void>
|
export type Unsubscribe = Effect.Effect<void>
|
||||||
@@ -47,8 +74,52 @@ export class InvalidDurableEventError extends Schema.TaggedErrorClass<InvalidDur
|
|||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
export const define = Event.define
|
export function versionedType(type: string, version: number) {
|
||||||
export const versionedType = Event.versionedType
|
return `${type}.${version}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registry = new Map<string, Definition>()
|
||||||
|
const durableRegistry = new Map<string, Definition>()
|
||||||
|
|
||||||
|
export function define<const Type extends string, Fields extends Schema.Struct.Fields>(input: {
|
||||||
|
readonly type: Type
|
||||||
|
readonly durable?: {
|
||||||
|
readonly version: number
|
||||||
|
readonly aggregate: string
|
||||||
|
}
|
||||||
|
readonly schema: Fields
|
||||||
|
}): Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> & Definition<Type, Schema.Struct<Fields>> {
|
||||||
|
const Data = Schema.Struct(input.schema)
|
||||||
|
const Payload = Schema.Struct({
|
||||||
|
id: ID,
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||||
|
type: Schema.Literal(input.type),
|
||||||
|
durable: Schema.optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Number, version: Schema.Number })),
|
||||||
|
location: Schema.optional(Location.Ref),
|
||||||
|
data: Data,
|
||||||
|
}).annotate({ identifier: input.type })
|
||||||
|
|
||||||
|
const definition = Object.assign(Payload, {
|
||||||
|
type: input.type,
|
||||||
|
...(input.durable === undefined ? {} : { durable: input.durable }),
|
||||||
|
data: Data,
|
||||||
|
})
|
||||||
|
const existing = registry.get(input.type)
|
||||||
|
if (
|
||||||
|
input.durable === undefined ||
|
||||||
|
existing?.durable === undefined ||
|
||||||
|
input.durable.version >= existing.durable.version
|
||||||
|
) {
|
||||||
|
registry.set(input.type, definition)
|
||||||
|
}
|
||||||
|
if (input.durable) durableRegistry.set(versionedType(input.type, input.durable.version), definition)
|
||||||
|
return definition as Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> &
|
||||||
|
Definition<Type, Schema.Struct<Fields>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function definitions() {
|
||||||
|
return registry.values().toArray()
|
||||||
|
}
|
||||||
|
|
||||||
export interface PublishOptions {
|
export interface PublishOptions {
|
||||||
readonly id?: ID
|
readonly id?: ID
|
||||||
@@ -98,7 +169,6 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
typed: new Map<string, PubSub.PubSub<Payload>>(),
|
typed: new Map<string, PubSub.PubSub<Payload>>(),
|
||||||
}
|
}
|
||||||
const projectors = new Map<string, Subscriber[]>()
|
const projectors = new Map<string, Subscriber[]>()
|
||||||
// TODO: Bind durable projectors to exact type+version before supporting incompatible historical payloads.
|
|
||||||
const listeners = new Array<Subscriber>()
|
const listeners = new Array<Subscriber>()
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
|
|
||||||
@@ -124,7 +194,6 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
function commitDurableEvent(
|
function commitDurableEvent(
|
||||||
definition: Definition,
|
|
||||||
event: Payload,
|
event: Payload,
|
||||||
input?: {
|
input?: {
|
||||||
readonly seq: number
|
readonly seq: number
|
||||||
@@ -135,6 +204,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
commit?: (seq: number) => Effect.Effect<void>,
|
commit?: (seq: number) => Effect.Effect<void>,
|
||||||
) {
|
) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
|
const definition = registry.get(event.type)
|
||||||
const durable = definition?.durable
|
const durable = definition?.durable
|
||||||
if (durable) {
|
if (durable) {
|
||||||
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
|
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
|
||||||
@@ -168,10 +238,9 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
.get()
|
.get()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
const latest = row?.seq ?? -1
|
const latest = row?.seq ?? -1
|
||||||
const encoded = Schema.encodeUnknownSync(definition.data)(event.data) as Record<
|
const encoded = Schema.encodeUnknownSync(
|
||||||
string,
|
definition.data as Schema.Codec<unknown, unknown, never, never>,
|
||||||
unknown
|
)(event.data) as Record<string, unknown>
|
||||||
>
|
|
||||||
if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) {
|
if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) {
|
||||||
yield* Effect.die(
|
yield* Effect.die(
|
||||||
new InvalidDurableEventError({
|
new InvalidDurableEventError({
|
||||||
@@ -287,8 +356,9 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
|
function publishEvent<D extends Definition>(event: Payload<D>, commit?: PublishOptions["commit"]) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
|
const definition = registry.get(event.type)
|
||||||
if (!definition?.durable && commit)
|
if (!definition?.durable && commit)
|
||||||
return yield* Effect.die(
|
return yield* Effect.die(
|
||||||
new InvalidDurableEventError({
|
new InvalidDurableEventError({
|
||||||
@@ -297,7 +367,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if (definition?.durable) {
|
if (definition?.durable) {
|
||||||
const committed = yield* commitDurableEvent(definition, event as Payload, undefined, commit)
|
const committed = yield* commitDurableEvent(event as Payload, undefined, commit)
|
||||||
if (committed) {
|
if (committed) {
|
||||||
event = {
|
event = {
|
||||||
...event,
|
...event,
|
||||||
@@ -346,7 +416,6 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
? { directory: serviceLocation.directory, workspaceID: serviceLocation.workspaceID }
|
? { directory: serviceLocation.directory, workspaceID: serviceLocation.workspaceID }
|
||||||
: undefined)
|
: undefined)
|
||||||
return yield* publishEvent(
|
return yield* publishEvent(
|
||||||
definition,
|
|
||||||
{
|
{
|
||||||
id: options?.id ?? ID.create(),
|
id: options?.id ?? ID.create(),
|
||||||
...(options?.metadata ? { metadata: options.metadata } : {}),
|
...(options?.metadata ? { metadata: options.metadata } : {}),
|
||||||
@@ -364,7 +433,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
|
options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
|
||||||
) {
|
) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const definition = Durable.get(event.type)
|
const definition = durableRegistry.get(event.type)
|
||||||
if (!definition?.durable) {
|
if (!definition?.durable) {
|
||||||
yield* Effect.die(
|
yield* Effect.die(
|
||||||
new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` }),
|
new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` }),
|
||||||
@@ -373,9 +442,11 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
const payload = {
|
const payload = {
|
||||||
id: event.id,
|
id: event.id,
|
||||||
type: definition.type,
|
type: definition.type,
|
||||||
data: Schema.decodeUnknownSync(definition.data)(event.data),
|
data: Schema.decodeUnknownSync(definition.data as Schema.Codec<unknown, unknown, never, never>)(
|
||||||
|
event.data,
|
||||||
|
),
|
||||||
} as Payload
|
} as Payload
|
||||||
const committed = yield* commitDurableEvent(definition, payload, {
|
const committed = yield* commitDurableEvent(payload, {
|
||||||
seq: event.seq,
|
seq: event.seq,
|
||||||
aggregateID: event.aggregateID,
|
aggregateID: event.aggregateID,
|
||||||
ownerID: options?.ownerID,
|
ownerID: options?.ownerID,
|
||||||
@@ -459,8 +530,8 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
|
|
||||||
const streamAll = (): Stream.Stream<Payload> => Stream.fromPubSub(pubsub.all)
|
const streamAll = (): Stream.Stream<Payload> => Stream.fromPubSub(pubsub.all)
|
||||||
|
|
||||||
const decodeSerializedEvent = (event: SerializedEvent) => {
|
const decodeSerializedEvent = (event: SerializedEvent): Payload => {
|
||||||
const definition = Durable.get(event.type)
|
const definition = durableRegistry.get(event.type)
|
||||||
if (!definition?.durable) {
|
if (!definition?.durable) {
|
||||||
throw new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` })
|
throw new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` })
|
||||||
}
|
}
|
||||||
@@ -468,7 +539,7 @@ export const layerWith = (options?: LayerOptions) =>
|
|||||||
id: event.id,
|
id: event.id,
|
||||||
type: definition.type,
|
type: definition.type,
|
||||||
durable: { aggregateID: event.aggregateID, seq: event.seq, version: definition.durable.version },
|
durable: { aggregateID: event.aggregateID, seq: event.seq, version: definition.durable.version },
|
||||||
data: Schema.decodeUnknownSync(definition.data)(event.data),
|
data: Schema.decodeUnknownSync(definition.data as Schema.Codec<unknown, unknown, never, never>)(event.data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ export * as FileSystem from "./filesystem"
|
|||||||
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
|
import { EventV2 } from "./event"
|
||||||
import { FSUtil } from "./fs-util"
|
import { FSUtil } from "./fs-util"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { PositiveInt, RelativePath } from "./schema"
|
import { PositiveInt, RelativePath } from "./schema"
|
||||||
import { FileSystemSearch } from "./filesystem/search"
|
import { FileSystemSearch } from "./filesystem/search"
|
||||||
import { Entry, FileSystem, Match } from "@opencode-ai/schema/filesystem"
|
import { Entry, Match } from "@opencode-ai/schema/filesystem"
|
||||||
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
|
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
|
||||||
|
|
||||||
export const ReadInput = Schema.Struct({
|
export const ReadInput = Schema.Struct({
|
||||||
@@ -47,7 +48,14 @@ export class GrepInput extends Schema.Class<GrepInput>("FileSystem.GrepInput")({
|
|||||||
limit: PositiveInt.pipe(Schema.optional),
|
limit: PositiveInt.pipe(Schema.optional),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export const Event = FileSystem.Event
|
export const Event = {
|
||||||
|
Edited: EventV2.define({
|
||||||
|
type: "file.edited",
|
||||||
|
schema: {
|
||||||
|
file: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
|
readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ export * as Watcher from "./watcher"
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||||
import type ParcelWatcher from "@parcel/watcher"
|
import type ParcelWatcher from "@parcel/watcher"
|
||||||
import { Cause, Context, Effect, Layer } from "effect"
|
import { Cause, Context, Effect, Layer, Schema } from "effect"
|
||||||
import { FileSystemWatcher } from "@opencode-ai/schema/filesystem-watcher"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Config } from "../config"
|
import { Config } from "../config"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
@@ -20,7 +19,15 @@ declare const OPENCODE_LIBC: string | undefined
|
|||||||
|
|
||||||
const SUBSCRIBE_TIMEOUT_MS = 10_000
|
const SUBSCRIBE_TIMEOUT_MS = 10_000
|
||||||
|
|
||||||
export const Event = FileSystemWatcher.Event
|
export const Event = {
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "file.watcher.updated",
|
||||||
|
schema: {
|
||||||
|
file: Schema.String,
|
||||||
|
event: Schema.Literals(["add", "change", "unlink"]),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
|
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -136,7 +136,16 @@ export class AuthorizationError extends Schema.TaggedErrorClass<AuthorizationErr
|
|||||||
|
|
||||||
export type Error = CodeRequiredError | AuthorizationError
|
export type Error = CodeRequiredError | AuthorizationError
|
||||||
|
|
||||||
export const Event = Integration.Event
|
export const Event = {
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "integration.updated",
|
||||||
|
schema: {},
|
||||||
|
}),
|
||||||
|
ConnectionUpdated: EventV2.define({
|
||||||
|
type: "integration.connection.updated",
|
||||||
|
schema: { integrationID: ID },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export const Ref = Integration.Ref
|
export const Ref = Integration.Ref
|
||||||
export type Ref = Integration.Ref
|
export type Ref = Integration.Ref
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
export * as ModelRequest from "./model-request"
|
||||||
|
|
||||||
|
import { ModelRequest } from "@opencode-ai/schema/model-request"
|
||||||
|
|
||||||
|
export const Generation = ModelRequest.Generation
|
||||||
|
export type Generation = ModelRequest.Generation
|
||||||
|
|
||||||
|
export const Request = ModelRequest.Request
|
||||||
|
export type Request = ModelRequest.Request
|
||||||
|
|
||||||
|
interface MutableRequest {
|
||||||
|
headers: Record<string, string>
|
||||||
|
body: Record<string, unknown>
|
||||||
|
generation?: Record<string, unknown>
|
||||||
|
options?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
const generationKeys = new Map<string, keyof Generation>([
|
||||||
|
["maxOutputTokens", "maxTokens"],
|
||||||
|
["maxTokens", "maxTokens"],
|
||||||
|
["temperature", "temperature"],
|
||||||
|
["topP", "topP"],
|
||||||
|
["topK", "topK"],
|
||||||
|
["frequencyPenalty", "frequencyPenalty"],
|
||||||
|
["presencePenalty", "presencePenalty"],
|
||||||
|
["seed", "seed"],
|
||||||
|
["stopSequences", "stop"],
|
||||||
|
["stop", "stop"],
|
||||||
|
])
|
||||||
|
|
||||||
|
interface Profile {
|
||||||
|
readonly namespace: string
|
||||||
|
readonly semantics: ReadonlyMap<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
const profiles = new Map<string, Profile>([
|
||||||
|
[
|
||||||
|
"@ai-sdk/openai",
|
||||||
|
{
|
||||||
|
namespace: "openai",
|
||||||
|
semantics: new Map([
|
||||||
|
["store", "store"],
|
||||||
|
["promptCacheKey", "promptCacheKey"],
|
||||||
|
["reasoningEffort", "reasoningEffort"],
|
||||||
|
["reasoningSummary", "reasoningSummary"],
|
||||||
|
["include", "include"],
|
||||||
|
["textVerbosity", "textVerbosity"],
|
||||||
|
["serviceTier", "serviceTier"],
|
||||||
|
["service_tier", "serviceTier"],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@ai-sdk/openai-compatible",
|
||||||
|
{
|
||||||
|
namespace: "openai",
|
||||||
|
semantics: new Map([
|
||||||
|
["store", "store"],
|
||||||
|
["promptCacheKey", "promptCacheKey"],
|
||||||
|
["reasoningEffort", "reasoningEffort"],
|
||||||
|
["reasoning_effort", "reasoningEffort"],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
["@ai-sdk/anthropic", { namespace: "anthropic", semantics: new Map([["thinking", "thinking"]]) }],
|
||||||
|
])
|
||||||
|
|
||||||
|
export const namespace = (packageName: string) => profiles.get(packageName)?.namespace
|
||||||
|
|
||||||
|
export const merge = (base: Request, override: Partial<Request>) => ({
|
||||||
|
headers: { ...base.headers, ...override.headers },
|
||||||
|
body: { ...base.body, ...override.body },
|
||||||
|
generation: { ...base.generation, ...override.generation },
|
||||||
|
options: { ...base.options, ...override.options },
|
||||||
|
})
|
||||||
|
|
||||||
|
export const assign = (target: MutableRequest, override: Partial<Request>) => {
|
||||||
|
Object.assign(target.headers, override.headers)
|
||||||
|
Object.assign(target.body, override.body)
|
||||||
|
Object.assign((target.generation ??= {}), override.generation)
|
||||||
|
Object.assign((target.options ??= {}), override.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Partitions AI-SDK-shaped request options before they enter the Catalog. */
|
||||||
|
export function normalizeAiSdkOptions(packageName: string | undefined, input: Readonly<Record<string, unknown>>) {
|
||||||
|
const generation: Record<string, number | ReadonlyArray<string>> = {}
|
||||||
|
const options: Record<string, unknown> = {}
|
||||||
|
const body: Record<string, unknown> = {}
|
||||||
|
const semantics = profiles.get(packageName ?? "")?.semantics
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(input)) {
|
||||||
|
const generationKey = generationKeys.get(key)
|
||||||
|
if (generationKey === "stop" && Array.isArray(value) && value.every((item) => typeof item === "string"))
|
||||||
|
generation[generationKey] = value
|
||||||
|
else if (generationKey !== undefined && generationKey !== "stop" && typeof value === "number")
|
||||||
|
generation[generationKey] = value
|
||||||
|
else if (semantics?.has(key)) options[semantics.get(key)!] = value
|
||||||
|
else body[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return { generation, options, body }
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
|
import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
|
||||||
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { ModelsDev } from "@opencode-ai/schema/models-dev"
|
|
||||||
import { Global } from "./global"
|
import { Global } from "./global"
|
||||||
import { Flag } from "./flag/flag"
|
import { Flag } from "./flag/flag"
|
||||||
import { Flock } from "./util/flock"
|
import { Flock } from "./util/flock"
|
||||||
@@ -109,7 +108,12 @@ export const Provider = Schema.Struct({
|
|||||||
|
|
||||||
export type Provider = Schema.Schema.Type<typeof Provider>
|
export type Provider = Schema.Schema.Type<typeof Provider>
|
||||||
|
|
||||||
export const Event = ModelsDev.Event
|
export const Event = {
|
||||||
|
Refreshed: EventV2.define({
|
||||||
|
type: "models-dev.refreshed",
|
||||||
|
schema: {},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
declare const OPENCODE_MODELS_DEV: Record<string, Provider> | undefined
|
declare const OPENCODE_MODELS_DEV: Record<string, Provider> | undefined
|
||||||
|
|
||||||
|
|||||||
@@ -7,31 +7,45 @@ import { Location } from "./location"
|
|||||||
import { AgentV2 } from "./agent"
|
import { AgentV2 } from "./agent"
|
||||||
import { SessionV2 } from "./session"
|
import { SessionV2 } from "./session"
|
||||||
import { SessionStore } from "./session/store"
|
import { SessionStore } from "./session/store"
|
||||||
|
import { withStatics } from "./schema"
|
||||||
|
import { Identifier } from "./util/identifier"
|
||||||
import { Wildcard } from "./util/wildcard"
|
import { Wildcard } from "./util/wildcard"
|
||||||
import { PermissionSaved } from "./permission/saved"
|
import { PermissionSaved } from "./permission/saved"
|
||||||
|
|
||||||
export { Effect, Rule, Ruleset } from "@opencode-ai/schema/permission"
|
export { Effect, Rule, Ruleset } from "@opencode-ai/schema/permission"
|
||||||
const missingAgentPermissions: Permission.Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
|
const missingAgentPermissions: Permission.Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
|
||||||
|
|
||||||
export const ID = Permission.ID
|
export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
|
||||||
|
Schema.brand("PermissionV2.ID"),
|
||||||
|
withStatics((schema) => ({ create: (id?: string) => schema.make(id ?? "per_" + Identifier.ascending()) })),
|
||||||
|
)
|
||||||
export type ID = typeof ID.Type
|
export type ID = typeof ID.Type
|
||||||
|
|
||||||
export const Source = Permission.Source
|
export const Source = Schema.Union([
|
||||||
|
Schema.Struct({
|
||||||
|
type: Schema.Literal("tool"),
|
||||||
|
messageID: Schema.String,
|
||||||
|
callID: Schema.String,
|
||||||
|
}),
|
||||||
|
]).annotate({ identifier: "PermissionV2.Source" })
|
||||||
export type Source = typeof Source.Type
|
export type Source = typeof Source.Type
|
||||||
|
|
||||||
const RequestFields = {
|
const RequestFields = {
|
||||||
sessionID: Permission.Request.fields.sessionID,
|
sessionID: SessionV2.ID,
|
||||||
action: Permission.Request.fields.action,
|
action: Schema.String,
|
||||||
resources: Permission.Request.fields.resources,
|
resources: Schema.Array(Schema.String),
|
||||||
save: Permission.Request.fields.save,
|
save: Schema.Array(Schema.String).pipe(Schema.optional),
|
||||||
metadata: Permission.Request.fields.metadata,
|
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||||
source: Permission.Request.fields.source,
|
source: Source.pipe(Schema.optional),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Request = Permission.Request
|
export const Request = Schema.Struct({
|
||||||
|
id: ID,
|
||||||
|
...RequestFields,
|
||||||
|
}).annotate({ identifier: "PermissionV2.Request" })
|
||||||
export type Request = typeof Request.Type
|
export type Request = typeof Request.Type
|
||||||
|
|
||||||
export const Reply = Permission.Reply
|
export const Reply = Schema.Literals(["once", "always", "reject"]).annotate({ identifier: "PermissionV2.Reply" })
|
||||||
export type Reply = typeof Reply.Type
|
export type Reply = typeof Reply.Type
|
||||||
|
|
||||||
export const AssertInput = Schema.Struct({
|
export const AssertInput = Schema.Struct({
|
||||||
@@ -54,7 +68,17 @@ export const AskResult = Schema.Struct({
|
|||||||
}).annotate({ identifier: "PermissionV2.AskResult" })
|
}).annotate({ identifier: "PermissionV2.AskResult" })
|
||||||
export type AskResult = typeof AskResult.Type
|
export type AskResult = typeof AskResult.Type
|
||||||
|
|
||||||
export const Event = Permission.Event
|
export const Event = {
|
||||||
|
Asked: EventV2.define({ type: "permission.v2.asked", schema: Request.fields }),
|
||||||
|
Replied: EventV2.define({
|
||||||
|
type: "permission.v2.replied",
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionV2.ID,
|
||||||
|
requestID: ID,
|
||||||
|
reply: Reply,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionV2.RejectedError", {}) {}
|
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionV2.RejectedError", {}) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
export * as PluginV2 from "./plugin"
|
export * as PluginV2 from "./plugin"
|
||||||
|
|
||||||
import { Context, Deferred, Effect, Exit, Layer, Scope } from "effect"
|
import { Context, Deferred, Effect, Exit, Layer, Schema, Scope } from "effect"
|
||||||
import type { Plugin } from "@opencode-ai/plugin/v2/effect"
|
import type { Plugin } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { PluginEvent, PluginID } from "@opencode-ai/schema/plugin"
|
|
||||||
import { AgentV2 } from "./agent"
|
import { AgentV2 } from "./agent"
|
||||||
import { AISDK } from "./aisdk"
|
import { AISDK } from "./aisdk"
|
||||||
import { Catalog } from "./catalog"
|
import { Catalog } from "./catalog"
|
||||||
@@ -15,10 +14,17 @@ import { Reference } from "./reference"
|
|||||||
import { SkillV2 } from "./skill"
|
import { SkillV2 } from "./skill"
|
||||||
import { State } from "./state"
|
import { State } from "./state"
|
||||||
|
|
||||||
export const ID = PluginID
|
export const ID = Schema.String.pipe(Schema.brand("Plugin.ID"))
|
||||||
export type ID = typeof ID.Type
|
export type ID = typeof ID.Type
|
||||||
|
|
||||||
export const Event = PluginEvent
|
export const Event = {
|
||||||
|
Added: EventV2.define({
|
||||||
|
type: "plugin.added",
|
||||||
|
schema: {
|
||||||
|
id: ID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly add: (id: ID, effect: Plugin["effect"]) => Effect.Effect<void>
|
readonly add: (id: ID, effect: Plugin["effect"]) => Effect.Effect<void>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { CommandPlugin } from "./command"
|
|||||||
import { ModelsDevPlugin } from "./models-dev"
|
import { ModelsDevPlugin } from "./models-dev"
|
||||||
import { ProviderPlugins } from "./provider"
|
import { ProviderPlugins } from "./provider"
|
||||||
import { SkillPlugin } from "./skill"
|
import { SkillPlugin } from "./skill"
|
||||||
import { VariantPlugin } from "./variant"
|
|
||||||
|
|
||||||
export type Requirements =
|
export type Requirements =
|
||||||
| AgentV2.Service
|
| AgentV2.Service
|
||||||
@@ -108,13 +107,12 @@ export const locationLayer = Layer.effectDiscard(
|
|||||||
yield* add(CommandPlugin.Plugin)
|
yield* add(CommandPlugin.Plugin)
|
||||||
yield* add(SkillPlugin.Plugin)
|
yield* add(SkillPlugin.Plugin)
|
||||||
yield* add(ModelsDevPlugin)
|
yield* add(ModelsDevPlugin)
|
||||||
|
yield* add(ConfigProviderPlugin.Plugin)
|
||||||
yield* add(ConfigAgentPlugin.Plugin)
|
yield* add(ConfigAgentPlugin.Plugin)
|
||||||
yield* add(ConfigCommandPlugin.Plugin)
|
yield* add(ConfigCommandPlugin.Plugin)
|
||||||
yield* add(ConfigSkillPlugin.Plugin)
|
yield* add(ConfigSkillPlugin.Plugin)
|
||||||
for (const item of ProviderPlugins) yield* add(item)
|
for (const item of ProviderPlugins) yield* add(item)
|
||||||
yield* add(ConfigExternalPlugin.Plugin)
|
yield* add(ConfigExternalPlugin.Plugin)
|
||||||
yield* add(ConfigProviderPlugin.Plugin)
|
|
||||||
yield* add(VariantPlugin.Plugin)
|
|
||||||
}).pipe(Effect.withSpan("PluginInternal.boot"), Effect.forkScoped({ startImmediately: true }))
|
}).pipe(Effect.withSpan("PluginInternal.boot"), Effect.forkScoped({ startImmediately: true }))
|
||||||
}),
|
}),
|
||||||
).pipe(
|
).pipe(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { define } from "./internal"
|
|||||||
import { Effect, Stream } from "effect"
|
import { Effect, Stream } from "effect"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
import { ModelV2 } from "../model"
|
import { ModelV2 } from "../model"
|
||||||
|
import { ModelRequest } from "../model-request"
|
||||||
import { ModelsDev } from "../models-dev"
|
import { ModelsDev } from "../models-dev"
|
||||||
import { ProviderV2 } from "../provider"
|
import { ProviderV2 } from "../provider"
|
||||||
|
|
||||||
@@ -37,12 +38,15 @@ function cost(input: ModelsDev.Model["cost"]) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function variants(model: ModelsDev.Model) {
|
function variants(model: ModelsDev.Model, packageName?: string) {
|
||||||
return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({
|
return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => {
|
||||||
|
const request = ModelRequest.normalizeAiSdkOptions(packageName, item.provider?.body ?? {})
|
||||||
|
return {
|
||||||
id: ModelV2.VariantID.make(id),
|
id: ModelV2.VariantID.make(id),
|
||||||
headers: { ...(item.provider?.headers ?? {}) },
|
headers: { ...(item.provider?.headers ?? {}) },
|
||||||
body: { ...(item.provider?.body ?? {}) },
|
...request,
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelsDevPlugin = define({
|
export const ModelsDevPlugin = define({
|
||||||
@@ -111,7 +115,7 @@ export const ModelsDevPlugin = define({
|
|||||||
input: [...(model.modalities?.input ?? [])],
|
input: [...(model.modalities?.input ?? [])],
|
||||||
output: [...(model.modalities?.output ?? [])],
|
output: [...(model.modalities?.output ?? [])],
|
||||||
}
|
}
|
||||||
draft.variants = variants(model)
|
draft.variants = variants(model, model.provider?.npm ?? item.npm)
|
||||||
draft.time.released = released(model.release_date)
|
draft.time.released = released(model.release_date)
|
||||||
draft.cost = cost(model.cost)
|
draft.cost = cost(model.cost)
|
||||||
draft.status = model.status ?? "active"
|
draft.status = model.status ?? "active"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import { EventV2 } from "../../event"
|
|||||||
import { Credential } from "../../credential"
|
import { Credential } from "../../credential"
|
||||||
import { Integration } from "../../integration"
|
import { Integration } from "../../integration"
|
||||||
import { ModelV2 } from "../../model"
|
import { ModelV2 } from "../../model"
|
||||||
|
import { ModelRequest } from "../../model-request"
|
||||||
import { ProviderV2 } from "../../provider"
|
import { ProviderV2 } from "../../provider"
|
||||||
import { ConfigProviderV1 } from "../../v1/config/provider"
|
import { ConfigProviderV1 } from "../../v1/config/provider"
|
||||||
import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
|
|
||||||
import { ConfigV1 } from "../../v1/config/config"
|
import { ConfigV1 } from "../../v1/config/config"
|
||||||
|
|
||||||
const defaultServer = "https://console.opencode.ai"
|
const defaultServer = "https://console.opencode.ai"
|
||||||
@@ -142,14 +142,15 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||||||
if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input]
|
if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input]
|
||||||
if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output]
|
if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output]
|
||||||
const packageName = config.provider?.npm ?? item.npm
|
const packageName = config.provider?.npm ?? item.npm
|
||||||
const lowerer = ConfigProviderOptionsV1.get(packageName)
|
ModelRequest.assign(model.request, {
|
||||||
Object.assign(model.request.headers, config.headers)
|
headers: config.headers,
|
||||||
Object.assign(model.request.body, lowerer.request(withoutCredentials(config.options)))
|
...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(config.options)),
|
||||||
|
})
|
||||||
if (config.variants !== undefined) {
|
if (config.variants !== undefined) {
|
||||||
model.variants = Object.entries(config.variants).map(([id, options]) => ({
|
model.variants = Object.entries(config.variants).map(([id, options]) => ({
|
||||||
id: ModelV2.VariantID.make(id),
|
id: ModelV2.VariantID.make(id),
|
||||||
headers: { ...(options.headers ?? {}) },
|
headers: { ...(options.headers ?? {}) },
|
||||||
body: lowerer.request(withoutCredentials(options)),
|
...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(options)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if (config.release_date !== undefined) {
|
if (config.release_date !== undefined) {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
export * as VariantPlugin from "./variant"
|
|
||||||
|
|
||||||
import type { ModelV2Info } from "@opencode-ai/sdk/v2/types"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { define } from "./internal"
|
|
||||||
|
|
||||||
export const Plugin = define({
|
|
||||||
id: "variant",
|
|
||||||
effect: Effect.fn(function* (ctx) {
|
|
||||||
yield* ctx.catalog.transform((catalog) => {
|
|
||||||
for (const record of catalog.provider.list()) {
|
|
||||||
for (const model of record.models.values()) {
|
|
||||||
catalog.model.update(model.providerID, model.id, (draft) => {
|
|
||||||
const generated = generate(draft)
|
|
||||||
if (generated.length === 0) return
|
|
||||||
|
|
||||||
const explicit = new Map(draft.variants.map((variant) => [variant.id, variant]))
|
|
||||||
const generatedIDs = new Set(generated.map((variant) => variant.id))
|
|
||||||
draft.variants = [
|
|
||||||
...generated.map((variant) => explicit.get(variant.id) ?? variant),
|
|
||||||
...draft.variants.filter((variant) => !generatedIDs.has(variant.id)),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
export function generate(model: ModelV2Info): ModelV2Info["variants"] {
|
|
||||||
if (model.api.type !== "aisdk" || model.api.package !== "@ai-sdk/openai-compatible") return []
|
|
||||||
const ids = `${model.id} ${model.api.id}`.toLowerCase()
|
|
||||||
if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return []
|
|
||||||
return ["high", "max"].map((id) => ({
|
|
||||||
id,
|
|
||||||
headers: {},
|
|
||||||
body: { reasoning_effort: id },
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ import { Slug } from "../util/slug"
|
|||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
import { Database } from "../database/database"
|
import { Database } from "../database/database"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { ProjectDirectoriesEvent } from "@opencode-ai/schema/project-directories"
|
|
||||||
|
|
||||||
export const StrategyID = Schema.Trim.pipe(Schema.check(Schema.isNonEmpty()), Schema.brand("ProjectCopy.StrategyID"))
|
export const StrategyID = Schema.Trim.pipe(Schema.check(Schema.isNonEmpty()), Schema.brand("ProjectCopy.StrategyID"))
|
||||||
export type StrategyID = typeof StrategyID.Type
|
export type StrategyID = typeof StrategyID.Type
|
||||||
@@ -107,7 +106,12 @@ export interface Strategy {
|
|||||||
readonly list: (directory: AbsolutePath) => Effect.Effect<ListEntry[], Git.WorktreeError | DirectoryUnavailableError>
|
readonly list: (directory: AbsolutePath) => Effect.Effect<ListEntry[], Git.WorktreeError | DirectoryUnavailableError>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Event = ProjectDirectoriesEvent
|
export const Event = {
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "project.directories.updated",
|
||||||
|
schema: { projectID: Project.ID },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly register: (strategy: Strategy) => Effect.Effect<void, DuplicateStrategyError>
|
readonly register: (strategy: Strategy) => Effect.Effect<void, DuplicateStrategyError>
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ export * as Pty from "./pty"
|
|||||||
|
|
||||||
import type { Disp, Proc } from "#pty"
|
import type { Disp, Proc } from "#pty"
|
||||||
import { Context, Effect, Layer, Schema, Types } from "effect"
|
import { Context, Effect, Layer, Schema, Types } from "effect"
|
||||||
import { PtyEvent, PtyInfo } from "@opencode-ai/schema/pty"
|
|
||||||
import { Config } from "./config"
|
import { Config } from "./config"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { PositiveInt } from "./schema"
|
import { NonNegativeInt, PositiveInt } from "./schema"
|
||||||
import { PtyID } from "./pty/schema"
|
import { PtyID } from "./pty/schema"
|
||||||
import { Shell } from "./shell"
|
import { Shell } from "./shell"
|
||||||
import { lazy } from "./util/lazy"
|
import { lazy } from "./util/lazy"
|
||||||
@@ -36,7 +35,18 @@ type Active = {
|
|||||||
listeners: Disp[]
|
listeners: Disp[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Info = PtyInfo
|
export const Info = Schema.Struct({
|
||||||
|
id: PtyID,
|
||||||
|
title: Schema.String,
|
||||||
|
command: Schema.String,
|
||||||
|
args: Schema.Array(Schema.String),
|
||||||
|
cwd: Schema.String,
|
||||||
|
status: Schema.Literals(["running", "exited"]),
|
||||||
|
// Windows ConPTY assigns the child pid asynchronously, so 0 is valid at spawn time.
|
||||||
|
pid: NonNegativeInt,
|
||||||
|
// Present once status is "exited".
|
||||||
|
exitCode: Schema.optional(NonNegativeInt),
|
||||||
|
}).annotate({ identifier: "Pty" })
|
||||||
|
|
||||||
export type Info = Types.DeepMutable<typeof Info.Type>
|
export type Info = Types.DeepMutable<typeof Info.Type>
|
||||||
|
|
||||||
@@ -90,7 +100,12 @@ export class ExitedError extends Schema.TaggedErrorClass<ExitedError>()("Pty.Exi
|
|||||||
ptyID: PtyID,
|
ptyID: PtyID,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export const Event = PtyEvent
|
export const Event = {
|
||||||
|
Created: EventV2.define({ type: "pty.created", schema: { info: Info } }),
|
||||||
|
Updated: EventV2.define({ type: "pty.updated", schema: { info: Info } }),
|
||||||
|
Exited: EventV2.define({ type: "pty.exited", schema: { id: PtyID, exitCode: NonNegativeInt } }),
|
||||||
|
Deleted: EventV2.define({ type: "pty.deleted", schema: { id: PtyID } }),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly list: () => Effect.Effect<Info[]>
|
readonly list: () => Effect.Effect<Info[]>
|
||||||
|
|||||||
@@ -1 +1,13 @@
|
|||||||
export { ID as PtyID } from "@opencode-ai/schema/pty"
|
import { Schema } from "effect"
|
||||||
|
import { Identifier } from "../id/id"
|
||||||
|
import { withStatics } from "../schema"
|
||||||
|
|
||||||
|
const ptyIdSchema = Schema.String.check(Schema.isStartsWith("pty")).pipe(Schema.brand("PtyID"))
|
||||||
|
|
||||||
|
export type PtyID = typeof ptyIdSchema.Type
|
||||||
|
|
||||||
|
export const PtyID = ptyIdSchema.pipe(
|
||||||
|
withStatics((schema: typeof ptyIdSchema) => ({
|
||||||
|
ascending: (id?: string) => schema.make(Identifier.ascending("pty", id)),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export * as PublicEventManifest from "./public-event-manifest"
|
|
||||||
|
|
||||||
export { ServerDefinitions as Definitions } from "@opencode-ai/schema/event-manifest"
|
|
||||||
@@ -1,35 +1,83 @@
|
|||||||
export * as QuestionV2 from "./question"
|
export * as QuestionV2 from "./question"
|
||||||
|
|
||||||
import { Context, Deferred, Effect, Layer, Schema } from "effect"
|
import { Context, Deferred, Effect, Layer, Schema } from "effect"
|
||||||
import { Question } from "@opencode-ai/schema/question"
|
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
|
import { Identifier } from "./id/id"
|
||||||
|
import { withStatics } from "./schema"
|
||||||
import { SessionSchema } from "./session/schema"
|
import { SessionSchema } from "./session/schema"
|
||||||
|
|
||||||
export const ID = Question.ID
|
export const ID = Schema.String.check(Schema.isStartsWith("que")).pipe(
|
||||||
|
Schema.brand("QuestionV2.ID"),
|
||||||
|
withStatics((schema) => ({ ascending: (id?: string) => schema.make(Identifier.ascending("question", id)) })),
|
||||||
|
)
|
||||||
export type ID = typeof ID.Type
|
export type ID = typeof ID.Type
|
||||||
|
|
||||||
export const Option = Question.Option
|
export const Option = Schema.Struct({
|
||||||
|
label: Schema.String.annotate({ description: "Display text (1-5 words, concise)" }),
|
||||||
|
description: Schema.String.annotate({ description: "Explanation of choice" }),
|
||||||
|
}).annotate({ identifier: "QuestionV2.Option" })
|
||||||
export type Option = typeof Option.Type
|
export type Option = typeof Option.Type
|
||||||
|
|
||||||
export const Info = Question.Info
|
const base = {
|
||||||
|
question: Schema.String.annotate({ description: "Complete question" }),
|
||||||
|
header: Schema.String.annotate({ description: "Very short label (max 30 chars)" }),
|
||||||
|
options: Schema.Array(Option).annotate({ description: "Available choices" }),
|
||||||
|
multiple: Schema.Boolean.pipe(Schema.optional).annotate({ description: "Allow selecting multiple choices" }),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Info = Schema.Struct({
|
||||||
|
...base,
|
||||||
|
custom: Schema.Boolean.pipe(Schema.optional).annotate({
|
||||||
|
description: "Allow typing a custom answer (default: true)",
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "QuestionV2.Info" })
|
||||||
export type Info = typeof Info.Type
|
export type Info = typeof Info.Type
|
||||||
|
|
||||||
export const Prompt = Question.Prompt
|
export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionV2.Prompt" })
|
||||||
export type Prompt = typeof Prompt.Type
|
export type Prompt = typeof Prompt.Type
|
||||||
|
|
||||||
export const Tool = Question.Tool
|
export const Tool = Schema.Struct({
|
||||||
|
messageID: Schema.String,
|
||||||
|
callID: Schema.String,
|
||||||
|
}).annotate({ identifier: "QuestionV2.Tool" })
|
||||||
export type Tool = typeof Tool.Type
|
export type Tool = typeof Tool.Type
|
||||||
|
|
||||||
export const Request = Question.Request
|
export const Request = Schema.Struct({
|
||||||
|
id: ID,
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
questions: Schema.Array(Info).annotate({ description: "Questions to ask" }),
|
||||||
|
tool: Tool.pipe(Schema.optional),
|
||||||
|
}).annotate({ identifier: "QuestionV2.Request" })
|
||||||
export type Request = typeof Request.Type
|
export type Request = typeof Request.Type
|
||||||
|
|
||||||
export const Answer = Question.Answer
|
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionV2.Answer" })
|
||||||
export type Answer = typeof Answer.Type
|
export type Answer = typeof Answer.Type
|
||||||
|
|
||||||
export const Reply = Question.Reply
|
export const Reply = Schema.Struct({
|
||||||
|
answers: Schema.Array(Answer).annotate({
|
||||||
|
description: "User answers in order of questions (each answer is an array of selected labels)",
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "QuestionV2.Reply" })
|
||||||
export type Reply = typeof Reply.Type
|
export type Reply = typeof Reply.Type
|
||||||
|
|
||||||
export const Event = Question.Event
|
export const Event = {
|
||||||
|
Asked: EventV2.define({ type: "question.v2.asked", schema: Request.fields }),
|
||||||
|
Replied: EventV2.define({
|
||||||
|
type: "question.v2.replied",
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
requestID: ID,
|
||||||
|
answers: Schema.Array(Answer),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Rejected: EventV2.define({
|
||||||
|
type: "question.v2.rejected",
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
requestID: ID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("QuestionV2.RejectedError", {}) {
|
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("QuestionV2.RejectedError", {}) {
|
||||||
override get message() {
|
override get message() {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export type GitSource = Reference.GitSource
|
|||||||
export const Source = Reference.Source
|
export const Source = Reference.Source
|
||||||
export type Source = Reference.Source
|
export type Source = Reference.Source
|
||||||
|
|
||||||
export const Event = Reference.Event
|
export const Event = {
|
||||||
|
Updated: EventV2.define({ type: "reference.updated", schema: {} }),
|
||||||
|
}
|
||||||
|
|
||||||
export class Info extends Schema.Class<Info>("Reference.Info")({
|
export class Info extends Schema.Class<Info>("Reference.Info")({
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
|
|||||||
@@ -1,2 +1,468 @@
|
|||||||
export * from "@opencode-ai/schema/session-event"
|
import { Schema } from "effect"
|
||||||
export * as SessionEvent from "@opencode-ai/schema/session-event"
|
import { ProviderMetadata, ToolContent } from "@opencode-ai/schema/llm"
|
||||||
|
import { Delivery } from "@opencode-ai/schema/session-delivery"
|
||||||
|
import { EventV2 } from "../event"
|
||||||
|
import { ModelV2 } from "../model"
|
||||||
|
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "../schema"
|
||||||
|
import { FileAttachment, Prompt } from "./prompt"
|
||||||
|
import { SessionSchema } from "./schema"
|
||||||
|
import { Location } from "../location"
|
||||||
|
import { SessionMessageID } from "./message-id"
|
||||||
|
import { SessionMessage } from "./message"
|
||||||
|
|
||||||
|
export { FileAttachment }
|
||||||
|
|
||||||
|
export const Source = Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: NonNegativeInt,
|
||||||
|
text: Schema.String,
|
||||||
|
}).annotate({
|
||||||
|
identifier: "session.next.event.source",
|
||||||
|
})
|
||||||
|
export type Source = typeof Source.Type
|
||||||
|
|
||||||
|
const Base = {
|
||||||
|
timestamp: DateTimeUtcFromMillis,
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
}
|
||||||
|
const PromptFields = {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
prompt: Prompt,
|
||||||
|
delivery: Delivery,
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
durable: {
|
||||||
|
aggregate: "sessionID",
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
const stepSettlementOptions = {
|
||||||
|
durable: {
|
||||||
|
aggregate: "sessionID",
|
||||||
|
version: 2,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const UnknownError = SessionMessage.UnknownError
|
||||||
|
export type UnknownError = SessionMessage.UnknownError
|
||||||
|
|
||||||
|
export const AgentSwitched = EventV2.define({
|
||||||
|
type: "session.next.agent.switched",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
agent: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type AgentSwitched = typeof AgentSwitched.Type
|
||||||
|
|
||||||
|
export const ModelSwitched = EventV2.define({
|
||||||
|
type: "session.next.model.switched",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
model: ModelV2.Ref,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type ModelSwitched = typeof ModelSwitched.Type
|
||||||
|
|
||||||
|
export const Moved = EventV2.define({
|
||||||
|
type: "session.next.moved",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
location: Location.Ref,
|
||||||
|
subdirectory: RelativePath.pipe(Schema.optional),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Moved = typeof Moved.Type
|
||||||
|
|
||||||
|
export const Prompted = EventV2.define({
|
||||||
|
type: "session.next.prompted",
|
||||||
|
...options,
|
||||||
|
schema: PromptFields,
|
||||||
|
})
|
||||||
|
export type Prompted = typeof Prompted.Type
|
||||||
|
|
||||||
|
export const PromptAdmitted = EventV2.define({
|
||||||
|
type: "session.next.prompt.admitted",
|
||||||
|
...options,
|
||||||
|
schema: PromptFields,
|
||||||
|
})
|
||||||
|
export type PromptAdmitted = typeof PromptAdmitted.Type
|
||||||
|
|
||||||
|
export const ContextUpdated = EventV2.define({
|
||||||
|
type: "session.next.context.updated",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
text: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type ContextUpdated = typeof ContextUpdated.Type
|
||||||
|
|
||||||
|
export const Synthetic = EventV2.define({
|
||||||
|
type: "session.next.synthetic",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
text: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Synthetic = typeof Synthetic.Type
|
||||||
|
|
||||||
|
export namespace Shell {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.shell.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
callID: Schema.String,
|
||||||
|
command: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.shell.ended",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
callID: Schema.String,
|
||||||
|
output: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Step {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.step.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
agent: Schema.String,
|
||||||
|
model: ModelV2.Ref,
|
||||||
|
snapshot: Schema.String.pipe(Schema.optional),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.step.ended",
|
||||||
|
...stepSettlementOptions,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
finish: Schema.String,
|
||||||
|
cost: Schema.Finite,
|
||||||
|
tokens: Schema.Struct({
|
||||||
|
input: Schema.Finite,
|
||||||
|
output: Schema.Finite,
|
||||||
|
reasoning: Schema.Finite,
|
||||||
|
cache: Schema.Struct({
|
||||||
|
read: Schema.Finite,
|
||||||
|
write: Schema.Finite,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
snapshot: Schema.String.pipe(Schema.optional),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
|
||||||
|
export const Failed = EventV2.define({
|
||||||
|
type: "session.next.step.failed",
|
||||||
|
...stepSettlementOptions,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
error: UnknownError,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Failed = typeof Failed.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Text {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.text.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
textID: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
// Stream fragments are live-only; Text.Ended is the replayable full-value boundary.
|
||||||
|
export const Delta = EventV2.define({
|
||||||
|
type: "session.next.text.delta",
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
textID: Schema.String,
|
||||||
|
delta: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.text.ended",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
textID: Schema.String,
|
||||||
|
text: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Reasoning {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.reasoning.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
reasoningID: Schema.String,
|
||||||
|
providerMetadata: ProviderMetadata.pipe(Schema.optional),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
// Stream fragments are live-only; Reasoning.Ended is the replayable full-value boundary.
|
||||||
|
export const Delta = EventV2.define({
|
||||||
|
type: "session.next.reasoning.delta",
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
reasoningID: Schema.String,
|
||||||
|
delta: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.reasoning.ended",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
reasoningID: Schema.String,
|
||||||
|
text: Schema.String,
|
||||||
|
providerMetadata: ProviderMetadata.pipe(Schema.optional),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Tool {
|
||||||
|
const ToolBase = {
|
||||||
|
...Base,
|
||||||
|
assistantMessageID: SessionMessageID.ID,
|
||||||
|
callID: Schema.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Input {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.tool.input.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
name: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
// Stream fragments are live-only; Input.Ended is the replayable raw-input boundary.
|
||||||
|
export const Delta = EventV2.define({
|
||||||
|
type: "session.next.tool.input.delta",
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
delta: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.tool.input.ended",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
text: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Called = EventV2.define({
|
||||||
|
type: "session.next.tool.called",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
tool: Schema.String,
|
||||||
|
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
provider: Schema.Struct({
|
||||||
|
executed: Schema.Boolean,
|
||||||
|
metadata: ProviderMetadata.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Called = typeof Called.Type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replayable bounded running-tool state. Tools should checkpoint semantic
|
||||||
|
* transitions or at a bounded cadence, not persist every stdout/stderr chunk.
|
||||||
|
*/
|
||||||
|
export const Progress = EventV2.define({
|
||||||
|
type: "session.next.tool.progress",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
structured: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
content: Schema.Array(ToolContent),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Progress = typeof Progress.Type
|
||||||
|
|
||||||
|
export const Success = EventV2.define({
|
||||||
|
type: "session.next.tool.success",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
structured: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
content: Schema.Array(ToolContent),
|
||||||
|
outputPaths: Schema.Array(Schema.String).pipe(Schema.optional),
|
||||||
|
result: Schema.Unknown.pipe(Schema.optional),
|
||||||
|
provider: Schema.Struct({
|
||||||
|
executed: Schema.Boolean,
|
||||||
|
metadata: ProviderMetadata.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Success = typeof Success.Type
|
||||||
|
|
||||||
|
export const Failed = EventV2.define({
|
||||||
|
type: "session.next.tool.failed",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...ToolBase,
|
||||||
|
error: UnknownError,
|
||||||
|
result: Schema.Unknown.pipe(Schema.optional),
|
||||||
|
provider: Schema.Struct({
|
||||||
|
executed: Schema.Boolean,
|
||||||
|
metadata: ProviderMetadata.pipe(Schema.optional),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Failed = typeof Failed.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RetryError = Schema.Struct({
|
||||||
|
message: Schema.String,
|
||||||
|
statusCode: Schema.Finite.pipe(Schema.optional),
|
||||||
|
isRetryable: Schema.Boolean,
|
||||||
|
responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||||
|
responseBody: Schema.String.pipe(Schema.optional),
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||||
|
}).annotate({
|
||||||
|
identifier: "session.next.retry_error",
|
||||||
|
})
|
||||||
|
export type RetryError = typeof RetryError.Type
|
||||||
|
|
||||||
|
export const Retried = EventV2.define({
|
||||||
|
type: "session.next.retried",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
attempt: Schema.Finite,
|
||||||
|
error: RetryError,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Retried = typeof Retried.Type
|
||||||
|
|
||||||
|
export namespace Compaction {
|
||||||
|
export const Started = EventV2.define({
|
||||||
|
type: "session.next.compaction.started",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Started = typeof Started.Type
|
||||||
|
|
||||||
|
export const Delta = EventV2.define({
|
||||||
|
type: "session.next.compaction.delta",
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
text: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Delta = typeof Delta.Type
|
||||||
|
|
||||||
|
export const Ended = EventV2.define({
|
||||||
|
type: "session.next.compaction.ended",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
...Base,
|
||||||
|
messageID: SessionMessageID.ID,
|
||||||
|
reason: Started.data.fields.reason,
|
||||||
|
text: Schema.String,
|
||||||
|
recent: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export type Ended = typeof Ended.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
const DurableDefinitions = [
|
||||||
|
AgentSwitched,
|
||||||
|
ModelSwitched,
|
||||||
|
Moved,
|
||||||
|
Prompted,
|
||||||
|
PromptAdmitted,
|
||||||
|
ContextUpdated,
|
||||||
|
Synthetic,
|
||||||
|
Shell.Started,
|
||||||
|
Shell.Ended,
|
||||||
|
Step.Started,
|
||||||
|
Step.Ended,
|
||||||
|
Step.Failed,
|
||||||
|
Text.Started,
|
||||||
|
Text.Ended,
|
||||||
|
Tool.Input.Started,
|
||||||
|
Tool.Input.Ended,
|
||||||
|
Tool.Called,
|
||||||
|
Tool.Progress,
|
||||||
|
Tool.Success,
|
||||||
|
Tool.Failed,
|
||||||
|
Reasoning.Started,
|
||||||
|
Reasoning.Ended,
|
||||||
|
Retried,
|
||||||
|
Compaction.Started,
|
||||||
|
Compaction.Ended,
|
||||||
|
] as const
|
||||||
|
const EphemeralDefinitions = [Text.Delta, Tool.Input.Delta, Reasoning.Delta, Compaction.Delta] as const
|
||||||
|
|
||||||
|
export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
|
||||||
|
export type DurableEvent = typeof Durable.Type
|
||||||
|
|
||||||
|
export const All = Schema.Union([...DurableDefinitions, ...EphemeralDefinitions], { mode: "oneOf" }).pipe(
|
||||||
|
Schema.toTaggedUnion("type"),
|
||||||
|
)
|
||||||
|
export type Event = typeof All.Type
|
||||||
|
export type Type = Event["type"]
|
||||||
|
|
||||||
|
export * as SessionEvent from "./event"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Catalog } from "../../catalog"
|
|||||||
import { Credential } from "../../credential"
|
import { Credential } from "../../credential"
|
||||||
import { Integration } from "../../integration"
|
import { Integration } from "../../integration"
|
||||||
import { ModelV2 } from "../../model"
|
import { ModelV2 } from "../../model"
|
||||||
|
import { ModelRequest } from "../../model-request"
|
||||||
import { ProviderV2 } from "../../provider"
|
import { ProviderV2 } from "../../provider"
|
||||||
import { SessionSchema } from "../schema"
|
import { SessionSchema } from "../schema"
|
||||||
|
|
||||||
@@ -87,6 +88,8 @@ const apiKey = (model: ModelV2.Info, credential?: Credential.Value) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
|
const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
|
||||||
|
const options = model.request.options ?? {}
|
||||||
|
const namespace = model.api.type === "aisdk" ? ModelRequest.namespace(model.api.package) : undefined
|
||||||
const body = model.request.body
|
const body = model.request.body
|
||||||
const httpBody = Object.hasOwn(body, "apiKey")
|
const httpBody = Object.hasOwn(body, "apiKey")
|
||||||
? Object.fromEntries(Object.entries(body).filter(([key]) => key !== "apiKey"))
|
? Object.fromEntries(Object.entries(body).filter(([key]) => key !== "apiKey"))
|
||||||
@@ -95,6 +98,8 @@ const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
|
|||||||
provider: model.providerID,
|
provider: model.providerID,
|
||||||
endpoint: model.api.url === undefined ? undefined : { baseURL: model.api.url },
|
endpoint: model.api.url === undefined ? undefined : { baseURL: model.api.url },
|
||||||
headers: model.request.headers,
|
headers: model.request.headers,
|
||||||
|
generation: model.request.generation,
|
||||||
|
providerOptions: namespace && Object.keys(options).length > 0 ? { [namespace]: options } : undefined,
|
||||||
http: { body: httpBody },
|
http: { body: httpBody },
|
||||||
limits: { context: model.limit.context, output: model.limit.output },
|
limits: { context: model.limit.context, output: model.limit.output },
|
||||||
})
|
})
|
||||||
@@ -117,8 +122,7 @@ const withVariant = (
|
|||||||
return Effect.succeed(
|
return Effect.succeed(
|
||||||
variant
|
variant
|
||||||
? produce(model, (draft) => {
|
? produce(model, (draft) => {
|
||||||
Object.assign(draft.request.headers, variant.headers)
|
ModelRequest.assign(draft.request, variant)
|
||||||
Object.assign(draft.request.body, variant.body)
|
|
||||||
})
|
})
|
||||||
: model,
|
: model,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
export * as SessionTodo from "./todo"
|
export * as SessionTodo from "./todo"
|
||||||
|
|
||||||
import { asc, eq } from "drizzle-orm"
|
import { asc, eq } from "drizzle-orm"
|
||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { SessionTodo, SessionTodoInfo } from "@opencode-ai/schema/session-todo"
|
|
||||||
import { Database } from "../database/database"
|
import { Database } from "../database/database"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
import { SessionSchema } from "./schema"
|
import { SessionSchema } from "./schema"
|
||||||
import { TodoTable } from "./sql"
|
import { TodoTable } from "./sql"
|
||||||
|
|
||||||
export const Info = SessionTodoInfo
|
export const Info = Schema.Struct({
|
||||||
|
content: Schema.String.annotate({ description: "Brief description of the task" }),
|
||||||
|
status: Schema.String.annotate({
|
||||||
|
description: "Current status of the task: pending, in_progress, completed, cancelled",
|
||||||
|
}),
|
||||||
|
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }),
|
||||||
|
}).annotate({ identifier: "SessionTodo.Info" })
|
||||||
export type Info = typeof Info.Type
|
export type Info = typeof Info.Type
|
||||||
|
|
||||||
export const Event = SessionTodo.Event
|
export const Event = {
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "todo.updated",
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
todos: Schema.Array(Info),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly update: (input: {
|
readonly update: (input: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ConfigMCPV1 } from "./mcp"
|
|||||||
import { ConfigPermissionV1 } from "./permission"
|
import { ConfigPermissionV1 } from "./permission"
|
||||||
import { ConfigProviderV1 } from "./provider"
|
import { ConfigProviderV1 } from "./provider"
|
||||||
import { ConfigProviderOptionsV1 } from "./provider-options"
|
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||||
|
import { ModelRequest } from "../../model-request"
|
||||||
|
|
||||||
const keys = new Set([
|
const keys = new Set([
|
||||||
"logLevel",
|
"logLevel",
|
||||||
@@ -191,7 +192,11 @@ function migrateProvider(info: ConfigProviderV1.Info) {
|
|||||||
function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
|
function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
|
||||||
const packageID = info.provider?.npm ?? packageName
|
const packageID = info.provider?.npm ?? packageName
|
||||||
const lowerer = ConfigProviderOptionsV1.get(packageID)
|
const lowerer = ConfigProviderOptionsV1.get(packageID)
|
||||||
const request = info.options && lowerer.request(info.options)
|
const ingest = (options: Readonly<Record<string, unknown>>) => {
|
||||||
|
const request = ModelRequest.normalizeAiSdkOptions(packageID, options)
|
||||||
|
return { ...lowerer.request(request.body), ...request.generation, ...request.options }
|
||||||
|
}
|
||||||
|
const request = info.options && ingest(info.options)
|
||||||
const costs = info.cost && [
|
const costs = info.cost && [
|
||||||
{
|
{
|
||||||
input: info.cost.input,
|
input: info.cost.input,
|
||||||
@@ -236,7 +241,7 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
|
|||||||
info.variants &&
|
info.variants &&
|
||||||
Object.entries(info.variants).map(([id, options]) => ({
|
Object.entries(info.variants).map(([id, options]) => ({
|
||||||
id,
|
id,
|
||||||
body: lowerer.request(options),
|
body: ingest(options),
|
||||||
})),
|
})),
|
||||||
cost: costs,
|
cost: costs,
|
||||||
disabled: info.status === "deprecated" ? true : undefined,
|
disabled: info.status === "deprecated" ? true : undefined,
|
||||||
|
|||||||
@@ -1,8 +1,71 @@
|
|||||||
export * as PermissionV1 from "./permission"
|
export * as PermissionV1 from "./permission"
|
||||||
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
export * from "@opencode-ai/schema/permission-v1"
|
import { ProjectV2 } from "../project"
|
||||||
import { ID } from "@opencode-ai/schema/permission-v1"
|
import { withStatics } from "../schema"
|
||||||
|
import { SessionSchema } from "../session/schema"
|
||||||
|
import { Identifier } from "../util/identifier"
|
||||||
|
|
||||||
|
export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
|
||||||
|
Schema.brand("PermissionID"),
|
||||||
|
withStatics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "per_" + Identifier.ascending()) })),
|
||||||
|
)
|
||||||
|
export type ID = typeof ID.Type
|
||||||
|
|
||||||
|
export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" })
|
||||||
|
export type Action = typeof Action.Type
|
||||||
|
|
||||||
|
export const Rule = Schema.Struct({
|
||||||
|
permission: Schema.String,
|
||||||
|
pattern: Schema.String,
|
||||||
|
action: Action,
|
||||||
|
}).annotate({ identifier: "PermissionRule" })
|
||||||
|
export type Rule = typeof Rule.Type
|
||||||
|
|
||||||
|
export const Ruleset = Schema.Array(Rule).annotate({ identifier: "PermissionRuleset" })
|
||||||
|
export type Ruleset = typeof Ruleset.Type
|
||||||
|
|
||||||
|
export const Request = Schema.Struct({
|
||||||
|
id: ID,
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
permission: Schema.String,
|
||||||
|
patterns: Schema.Array(Schema.String),
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.Unknown),
|
||||||
|
always: Schema.Array(Schema.String),
|
||||||
|
tool: Schema.Struct({
|
||||||
|
messageID: Schema.String,
|
||||||
|
callID: Schema.String,
|
||||||
|
}).pipe(Schema.optional),
|
||||||
|
}).annotate({ identifier: "PermissionRequest" })
|
||||||
|
export type Request = typeof Request.Type
|
||||||
|
|
||||||
|
export const Reply = Schema.Literals(["once", "always", "reject"])
|
||||||
|
export type Reply = typeof Reply.Type
|
||||||
|
|
||||||
|
export const ReplyBody = Schema.Struct({
|
||||||
|
reply: Reply,
|
||||||
|
message: Schema.String.pipe(Schema.optional),
|
||||||
|
}).annotate({ identifier: "PermissionReplyBody" })
|
||||||
|
export type ReplyBody = typeof ReplyBody.Type
|
||||||
|
|
||||||
|
export const Approval = Schema.Struct({
|
||||||
|
projectID: ProjectV2.ID,
|
||||||
|
patterns: Schema.Array(Schema.String),
|
||||||
|
}).annotate({ identifier: "PermissionApproval" })
|
||||||
|
export type Approval = typeof Approval.Type
|
||||||
|
|
||||||
|
export const AskInput = Schema.Struct({
|
||||||
|
...Request.fields,
|
||||||
|
id: ID.pipe(Schema.optional),
|
||||||
|
ruleset: Ruleset,
|
||||||
|
}).annotate({ identifier: "PermissionAskInput" })
|
||||||
|
export type AskInput = typeof AskInput.Type
|
||||||
|
|
||||||
|
export const ReplyInput = Schema.Struct({
|
||||||
|
requestID: ID,
|
||||||
|
...ReplyBody.fields,
|
||||||
|
}).annotate({ identifier: "PermissionReplyInput" })
|
||||||
|
export type ReplyInput = typeof ReplyInput.Type
|
||||||
|
|
||||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionRejectedError", {}) {
|
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionRejectedError", {}) {
|
||||||
override get message() {
|
override get message() {
|
||||||
|
|||||||
+607
-43
@@ -1,52 +1,39 @@
|
|||||||
export * as SessionV1 from "./session"
|
export * as SessionV1 from "./session"
|
||||||
|
|
||||||
import { Schema } from "effect"
|
import { Effect, Schema, Types } from "effect"
|
||||||
|
import { EventV2 } from "../event"
|
||||||
|
import { PermissionV1 } from "./permission"
|
||||||
|
import { ProjectV2 } from "../project"
|
||||||
|
import { ProviderV2 } from "../provider"
|
||||||
|
import { ModelV2 } from "../model"
|
||||||
|
import { optionalOmitUndefined, withStatics } from "../schema"
|
||||||
|
import { Identifier } from "../util/identifier"
|
||||||
import { NonNegativeInt } from "../schema"
|
import { NonNegativeInt } from "../schema"
|
||||||
import { NamedError } from "../util/error"
|
import { NamedError } from "../util/error"
|
||||||
|
import { SessionSchema } from "../session/schema"
|
||||||
|
import { WorkspaceV2 } from "../workspace"
|
||||||
|
|
||||||
export {
|
const Timestamp = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))
|
||||||
AgentPart,
|
|
||||||
AgentPartInput,
|
export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe(
|
||||||
Assistant,
|
Schema.brand("MessageID"),
|
||||||
CompactionPart,
|
withStatics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "msg_" + Identifier.ascending()) })),
|
||||||
Event,
|
)
|
||||||
FilePart,
|
export type MessageID = typeof MessageID.Type
|
||||||
FilePartInput,
|
|
||||||
FilePartSource,
|
export const PartID = Schema.String.check(Schema.isStartsWith("prt")).pipe(
|
||||||
FileSource,
|
Schema.brand("PartID"),
|
||||||
Format,
|
withStatics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "prt_" + Identifier.ascending()) })),
|
||||||
Info,
|
)
|
||||||
MessageID,
|
export type PartID = typeof PartID.Type
|
||||||
OutputFormatJsonSchema,
|
|
||||||
OutputFormatText,
|
|
||||||
Part,
|
|
||||||
PartID,
|
|
||||||
PatchPart,
|
|
||||||
Range,
|
|
||||||
ReasoningPart,
|
|
||||||
ResourceSource,
|
|
||||||
RetryPart,
|
|
||||||
SessionInfo,
|
|
||||||
SnapshotPart,
|
|
||||||
StepFinishPart,
|
|
||||||
StepStartPart,
|
|
||||||
SubtaskPart,
|
|
||||||
SubtaskPartInput,
|
|
||||||
SymbolSource,
|
|
||||||
TextPart,
|
|
||||||
TextPartInput,
|
|
||||||
ToolPart,
|
|
||||||
ToolState,
|
|
||||||
ToolStateCompleted,
|
|
||||||
ToolStateError,
|
|
||||||
ToolStatePending,
|
|
||||||
ToolStateRunning,
|
|
||||||
User,
|
|
||||||
WithParts,
|
|
||||||
} from "@opencode-ai/schema/session-v1"
|
|
||||||
|
|
||||||
export const OutputLengthError = NamedError.create("MessageOutputLengthError", {})
|
export const OutputLengthError = NamedError.create("MessageOutputLengthError", {})
|
||||||
export const AuthError = NamedError.create("ProviderAuthError", { providerID: Schema.String, message: Schema.String })
|
|
||||||
|
export const AuthError = NamedError.create("ProviderAuthError", {
|
||||||
|
providerID: Schema.String,
|
||||||
|
message: Schema.String,
|
||||||
|
})
|
||||||
|
|
||||||
export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String })
|
export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String })
|
||||||
export const StructuredOutputError = NamedError.create("StructuredOutputError", {
|
export const StructuredOutputError = NamedError.create("StructuredOutputError", {
|
||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
@@ -65,4 +52,581 @@ export const ContextOverflowError = NamedError.create("ContextOverflowError", {
|
|||||||
message: Schema.String,
|
message: Schema.String,
|
||||||
responseBody: Schema.optional(Schema.String),
|
responseBody: Schema.optional(Schema.String),
|
||||||
})
|
})
|
||||||
export const ContentFilterError = NamedError.create("ContentFilterError", { message: Schema.String })
|
export const ContentFilterError = NamedError.create("ContentFilterError", {
|
||||||
|
message: Schema.String,
|
||||||
|
})
|
||||||
|
|
||||||
|
export class OutputFormatText extends Schema.Class<OutputFormatText>("OutputFormatText")({
|
||||||
|
type: Schema.Literal("text"),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class OutputFormatJsonSchema extends Schema.Class<OutputFormatJsonSchema>("OutputFormatJsonSchema")({
|
||||||
|
type: Schema.Literal("json_schema"),
|
||||||
|
schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }),
|
||||||
|
retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({
|
||||||
|
discriminator: "type",
|
||||||
|
identifier: "OutputFormat",
|
||||||
|
})
|
||||||
|
export type OutputFormat = Schema.Schema.Type<typeof Format>
|
||||||
|
|
||||||
|
const partBase = {
|
||||||
|
id: PartID,
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
messageID: MessageID,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SnapshotPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("snapshot"),
|
||||||
|
snapshot: Schema.String,
|
||||||
|
}).annotate({ identifier: "SnapshotPart" })
|
||||||
|
export type SnapshotPart = Types.DeepMutable<Schema.Schema.Type<typeof SnapshotPart>>
|
||||||
|
|
||||||
|
export const PatchPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("patch"),
|
||||||
|
hash: Schema.String,
|
||||||
|
files: Schema.Array(Schema.String),
|
||||||
|
}).annotate({ identifier: "PatchPart" })
|
||||||
|
export type PatchPart = Types.DeepMutable<Schema.Schema.Type<typeof PatchPart>>
|
||||||
|
|
||||||
|
export const TextPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("text"),
|
||||||
|
text: Schema.String,
|
||||||
|
synthetic: Schema.optional(Schema.Boolean),
|
||||||
|
ignored: Schema.optional(Schema.Boolean),
|
||||||
|
time: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: Schema.optional(NonNegativeInt),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
}).annotate({ identifier: "TextPart" })
|
||||||
|
export type TextPart = Types.DeepMutable<Schema.Schema.Type<typeof TextPart>>
|
||||||
|
|
||||||
|
export const ReasoningPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("reasoning"),
|
||||||
|
text: Schema.String,
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
time: Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: Schema.optional(NonNegativeInt),
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "ReasoningPart" })
|
||||||
|
export type ReasoningPart = Types.DeepMutable<Schema.Schema.Type<typeof ReasoningPart>>
|
||||||
|
|
||||||
|
const filePartSourceBase = {
|
||||||
|
text: Schema.Struct({
|
||||||
|
value: Schema.String,
|
||||||
|
start: Schema.Finite,
|
||||||
|
end: Schema.Finite,
|
||||||
|
}).annotate({ identifier: "FilePartSourceText" }),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Range = Schema.Struct({
|
||||||
|
start: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }),
|
||||||
|
end: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }),
|
||||||
|
}).annotate({ identifier: "Range" })
|
||||||
|
export type Range = typeof Range.Type
|
||||||
|
|
||||||
|
export const FileSource = Schema.Struct({
|
||||||
|
...filePartSourceBase,
|
||||||
|
type: Schema.Literal("file"),
|
||||||
|
path: Schema.String,
|
||||||
|
}).annotate({ identifier: "FileSource" })
|
||||||
|
|
||||||
|
export const SymbolSource = Schema.Struct({
|
||||||
|
...filePartSourceBase,
|
||||||
|
type: Schema.Literal("symbol"),
|
||||||
|
path: Schema.String,
|
||||||
|
range: Range,
|
||||||
|
name: Schema.String,
|
||||||
|
kind: NonNegativeInt,
|
||||||
|
}).annotate({ identifier: "SymbolSource" })
|
||||||
|
|
||||||
|
export const ResourceSource = Schema.Struct({
|
||||||
|
...filePartSourceBase,
|
||||||
|
type: Schema.Literal("resource"),
|
||||||
|
clientName: Schema.String,
|
||||||
|
uri: Schema.String,
|
||||||
|
}).annotate({ identifier: "ResourceSource" })
|
||||||
|
|
||||||
|
export const FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({
|
||||||
|
discriminator: "type",
|
||||||
|
identifier: "FilePartSource",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const FilePart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("file"),
|
||||||
|
mime: Schema.String,
|
||||||
|
filename: Schema.optional(Schema.String),
|
||||||
|
url: Schema.String,
|
||||||
|
source: Schema.optional(FilePartSource),
|
||||||
|
}).annotate({ identifier: "FilePart" })
|
||||||
|
export type FilePart = Types.DeepMutable<Schema.Schema.Type<typeof FilePart>>
|
||||||
|
|
||||||
|
export const AgentPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("agent"),
|
||||||
|
name: Schema.String,
|
||||||
|
source: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
value: Schema.String,
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: NonNegativeInt,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}).annotate({ identifier: "AgentPart" })
|
||||||
|
export type AgentPart = Types.DeepMutable<Schema.Schema.Type<typeof AgentPart>>
|
||||||
|
|
||||||
|
export const CompactionPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("compaction"),
|
||||||
|
auto: Schema.Boolean,
|
||||||
|
overflow: Schema.optional(Schema.Boolean),
|
||||||
|
tail_start_id: Schema.optional(MessageID),
|
||||||
|
}).annotate({ identifier: "CompactionPart" })
|
||||||
|
export type CompactionPart = Types.DeepMutable<Schema.Schema.Type<typeof CompactionPart>>
|
||||||
|
|
||||||
|
export const SubtaskPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("subtask"),
|
||||||
|
prompt: Schema.String,
|
||||||
|
description: Schema.String,
|
||||||
|
agent: Schema.String,
|
||||||
|
model: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
providerID: ProviderV2.ID,
|
||||||
|
modelID: ModelV2.ID,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
command: Schema.optional(Schema.String),
|
||||||
|
}).annotate({ identifier: "SubtaskPart" })
|
||||||
|
export type SubtaskPart = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPart>>
|
||||||
|
|
||||||
|
export const RetryPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("retry"),
|
||||||
|
attempt: NonNegativeInt,
|
||||||
|
error: APIError.EffectSchema,
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: NonNegativeInt,
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "RetryPart" })
|
||||||
|
export type RetryPart = Omit<Types.DeepMutable<Schema.Schema.Type<typeof RetryPart>>, "error"> & {
|
||||||
|
error: APIError
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepStartPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("step-start"),
|
||||||
|
snapshot: Schema.optional(Schema.String),
|
||||||
|
}).annotate({ identifier: "StepStartPart" })
|
||||||
|
export type StepStartPart = Types.DeepMutable<Schema.Schema.Type<typeof StepStartPart>>
|
||||||
|
|
||||||
|
export const StepFinishPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("step-finish"),
|
||||||
|
reason: Schema.String,
|
||||||
|
snapshot: Schema.optional(Schema.String),
|
||||||
|
cost: Schema.Finite,
|
||||||
|
tokens: Schema.Struct({
|
||||||
|
total: Schema.optional(Schema.Finite),
|
||||||
|
input: Schema.Finite,
|
||||||
|
output: Schema.Finite,
|
||||||
|
reasoning: Schema.Finite,
|
||||||
|
cache: Schema.Struct({
|
||||||
|
read: Schema.Finite,
|
||||||
|
write: Schema.Finite,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "StepFinishPart" })
|
||||||
|
export type StepFinishPart = Types.DeepMutable<Schema.Schema.Type<typeof StepFinishPart>>
|
||||||
|
|
||||||
|
export const ToolStatePending = Schema.Struct({
|
||||||
|
status: Schema.Literal("pending"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
raw: Schema.String,
|
||||||
|
}).annotate({ identifier: "ToolStatePending" })
|
||||||
|
export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
|
||||||
|
|
||||||
|
export const ToolStateRunning = Schema.Struct({
|
||||||
|
status: Schema.Literal("running"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
title: Schema.optional(Schema.String),
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
time: Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "ToolStateRunning" })
|
||||||
|
export type ToolStateRunning = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateRunning>>
|
||||||
|
|
||||||
|
export const ToolStateCompleted = Schema.Struct({
|
||||||
|
status: Schema.Literal("completed"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
output: Schema.String,
|
||||||
|
title: Schema.String,
|
||||||
|
metadata: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
time: Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: NonNegativeInt,
|
||||||
|
compacted: Schema.optional(NonNegativeInt),
|
||||||
|
}),
|
||||||
|
attachments: Schema.optional(Schema.Array(FilePart)),
|
||||||
|
}).annotate({ identifier: "ToolStateCompleted" })
|
||||||
|
export type ToolStateCompleted = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateCompleted>>
|
||||||
|
|
||||||
|
export const ToolStateError = Schema.Struct({
|
||||||
|
status: Schema.Literal("error"),
|
||||||
|
input: Schema.Record(Schema.String, Schema.Any),
|
||||||
|
error: Schema.String,
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
time: Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: NonNegativeInt,
|
||||||
|
}),
|
||||||
|
}).annotate({ identifier: "ToolStateError" })
|
||||||
|
export type ToolStateError = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateError>>
|
||||||
|
|
||||||
|
export const ToolState = Schema.Union([
|
||||||
|
ToolStatePending,
|
||||||
|
ToolStateRunning,
|
||||||
|
ToolStateCompleted,
|
||||||
|
ToolStateError,
|
||||||
|
]).annotate({
|
||||||
|
discriminator: "status",
|
||||||
|
identifier: "ToolState",
|
||||||
|
})
|
||||||
|
export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
|
||||||
|
|
||||||
|
export const ToolPart = Schema.Struct({
|
||||||
|
...partBase,
|
||||||
|
type: Schema.Literal("tool"),
|
||||||
|
callID: Schema.String,
|
||||||
|
tool: Schema.String,
|
||||||
|
state: ToolState,
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
}).annotate({ identifier: "ToolPart" })
|
||||||
|
export type ToolPart = Omit<Types.DeepMutable<Schema.Schema.Type<typeof ToolPart>>, "state"> & {
|
||||||
|
state: ToolState
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageBase = {
|
||||||
|
id: MessageID,
|
||||||
|
sessionID: partBase.sessionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileDiff = Schema.Struct({
|
||||||
|
file: Schema.optional(Schema.String),
|
||||||
|
patch: Schema.optional(Schema.String),
|
||||||
|
additions: Schema.Finite,
|
||||||
|
deletions: Schema.Finite,
|
||||||
|
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
|
||||||
|
}).annotate({ identifier: "SnapshotFileDiff" })
|
||||||
|
|
||||||
|
export const User = Schema.Struct({
|
||||||
|
...messageBase,
|
||||||
|
role: Schema.Literal("user"),
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: Timestamp,
|
||||||
|
}),
|
||||||
|
format: Schema.optional(Format),
|
||||||
|
summary: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
title: Schema.optional(Schema.String),
|
||||||
|
body: Schema.optional(Schema.String),
|
||||||
|
diffs: Schema.Array(FileDiff),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
agent: Schema.String,
|
||||||
|
model: Schema.Struct({
|
||||||
|
providerID: ProviderV2.ID,
|
||||||
|
modelID: ModelV2.ID,
|
||||||
|
variant: Schema.optional(Schema.String),
|
||||||
|
}),
|
||||||
|
system: Schema.optional(Schema.String),
|
||||||
|
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||||
|
}).annotate({ identifier: "UserMessage" })
|
||||||
|
export type User = Types.DeepMutable<Schema.Schema.Type<typeof User>>
|
||||||
|
|
||||||
|
export const Part = Schema.Union([
|
||||||
|
TextPart,
|
||||||
|
SubtaskPart,
|
||||||
|
ReasoningPart,
|
||||||
|
FilePart,
|
||||||
|
ToolPart,
|
||||||
|
StepStartPart,
|
||||||
|
StepFinishPart,
|
||||||
|
SnapshotPart,
|
||||||
|
PatchPart,
|
||||||
|
AgentPart,
|
||||||
|
RetryPart,
|
||||||
|
CompactionPart,
|
||||||
|
]).annotate({ discriminator: "type", identifier: "Part" })
|
||||||
|
export type Part =
|
||||||
|
| TextPart
|
||||||
|
| SubtaskPart
|
||||||
|
| ReasoningPart
|
||||||
|
| FilePart
|
||||||
|
| ToolPart
|
||||||
|
| StepStartPart
|
||||||
|
| StepFinishPart
|
||||||
|
| SnapshotPart
|
||||||
|
| PatchPart
|
||||||
|
| AgentPart
|
||||||
|
| RetryPart
|
||||||
|
| CompactionPart
|
||||||
|
|
||||||
|
const AssistantErrorSchema = Schema.Union([
|
||||||
|
AuthError.EffectSchema,
|
||||||
|
NamedError.Unknown.EffectSchema,
|
||||||
|
OutputLengthError.EffectSchema,
|
||||||
|
AbortedError.EffectSchema,
|
||||||
|
StructuredOutputError.EffectSchema,
|
||||||
|
ContextOverflowError.EffectSchema,
|
||||||
|
ContentFilterError.EffectSchema,
|
||||||
|
APIError.EffectSchema,
|
||||||
|
]).annotate({ discriminator: "name" })
|
||||||
|
type AssistantError = Schema.Schema.Type<typeof AssistantErrorSchema>
|
||||||
|
|
||||||
|
export const TextPartInput = Schema.Struct({
|
||||||
|
id: Schema.optional(PartID),
|
||||||
|
type: Schema.Literal("text"),
|
||||||
|
text: Schema.String,
|
||||||
|
synthetic: Schema.optional(Schema.Boolean),
|
||||||
|
ignored: Schema.optional(Schema.Boolean),
|
||||||
|
time: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: Schema.optional(NonNegativeInt),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
}).annotate({ identifier: "TextPartInput" })
|
||||||
|
export type TextPartInput = Types.DeepMutable<Schema.Schema.Type<typeof TextPartInput>>
|
||||||
|
|
||||||
|
export const FilePartInput = Schema.Struct({
|
||||||
|
id: Schema.optional(PartID),
|
||||||
|
type: Schema.Literal("file"),
|
||||||
|
mime: Schema.String,
|
||||||
|
filename: Schema.optional(Schema.String),
|
||||||
|
url: Schema.String,
|
||||||
|
source: Schema.optional(FilePartSource),
|
||||||
|
}).annotate({ identifier: "FilePartInput" })
|
||||||
|
export type FilePartInput = Types.DeepMutable<Schema.Schema.Type<typeof FilePartInput>>
|
||||||
|
|
||||||
|
export const AgentPartInput = Schema.Struct({
|
||||||
|
id: Schema.optional(PartID),
|
||||||
|
type: Schema.Literal("agent"),
|
||||||
|
name: Schema.String,
|
||||||
|
source: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
value: Schema.String,
|
||||||
|
start: NonNegativeInt,
|
||||||
|
end: NonNegativeInt,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}).annotate({ identifier: "AgentPartInput" })
|
||||||
|
export type AgentPartInput = Types.DeepMutable<Schema.Schema.Type<typeof AgentPartInput>>
|
||||||
|
|
||||||
|
export const SubtaskPartInput = Schema.Struct({
|
||||||
|
id: Schema.optional(PartID),
|
||||||
|
type: Schema.Literal("subtask"),
|
||||||
|
prompt: Schema.String,
|
||||||
|
description: Schema.String,
|
||||||
|
agent: Schema.String,
|
||||||
|
model: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
providerID: ProviderV2.ID,
|
||||||
|
modelID: ModelV2.ID,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
command: Schema.optional(Schema.String),
|
||||||
|
}).annotate({ identifier: "SubtaskPartInput" })
|
||||||
|
export type SubtaskPartInput = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPartInput>>
|
||||||
|
|
||||||
|
export const Assistant = Schema.Struct({
|
||||||
|
...messageBase,
|
||||||
|
role: Schema.Literal("assistant"),
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: NonNegativeInt,
|
||||||
|
completed: Schema.optional(NonNegativeInt),
|
||||||
|
}),
|
||||||
|
error: Schema.optional(AssistantErrorSchema),
|
||||||
|
parentID: MessageID,
|
||||||
|
modelID: ModelV2.ID,
|
||||||
|
providerID: ProviderV2.ID,
|
||||||
|
mode: Schema.String,
|
||||||
|
agent: Schema.String,
|
||||||
|
path: Schema.Struct({
|
||||||
|
cwd: Schema.String,
|
||||||
|
root: Schema.String,
|
||||||
|
}),
|
||||||
|
summary: Schema.optional(Schema.Boolean),
|
||||||
|
cost: Schema.Finite,
|
||||||
|
tokens: Schema.Struct({
|
||||||
|
total: Schema.optional(Schema.Finite),
|
||||||
|
input: Schema.Finite,
|
||||||
|
output: Schema.Finite,
|
||||||
|
reasoning: Schema.Finite,
|
||||||
|
cache: Schema.Struct({
|
||||||
|
read: Schema.Finite,
|
||||||
|
write: Schema.Finite,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
structured: Schema.optional(Schema.Any),
|
||||||
|
variant: Schema.optional(Schema.String),
|
||||||
|
finish: Schema.optional(Schema.String),
|
||||||
|
}).annotate({ identifier: "AssistantMessage" })
|
||||||
|
export type Assistant = Omit<Types.DeepMutable<Schema.Schema.Type<typeof Assistant>>, "error"> & {
|
||||||
|
error?: AssistantError
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" })
|
||||||
|
export type Info = User | Assistant
|
||||||
|
|
||||||
|
export const WithParts = Schema.Struct({
|
||||||
|
info: Info,
|
||||||
|
parts: Schema.Array(Part),
|
||||||
|
})
|
||||||
|
export type WithParts = {
|
||||||
|
info: Info
|
||||||
|
parts: Part[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
durable: {
|
||||||
|
aggregate: "sessionID",
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const SessionSummary = Schema.Struct({
|
||||||
|
additions: Schema.Finite,
|
||||||
|
deletions: Schema.Finite,
|
||||||
|
files: Schema.Finite,
|
||||||
|
diffs: optionalOmitUndefined(Schema.Array(FileDiff)),
|
||||||
|
})
|
||||||
|
|
||||||
|
const SessionTokens = Schema.Struct({
|
||||||
|
input: Schema.Finite,
|
||||||
|
output: Schema.Finite,
|
||||||
|
reasoning: Schema.Finite,
|
||||||
|
cache: Schema.Struct({
|
||||||
|
read: Schema.Finite,
|
||||||
|
write: Schema.Finite,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const SessionShare = Schema.Struct({
|
||||||
|
url: Schema.String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const SessionRevert = Schema.Struct({
|
||||||
|
messageID: MessageID,
|
||||||
|
partID: optionalOmitUndefined(PartID),
|
||||||
|
snapshot: optionalOmitUndefined(Schema.String),
|
||||||
|
diff: optionalOmitUndefined(Schema.String),
|
||||||
|
})
|
||||||
|
|
||||||
|
const SessionModel = Schema.Struct({
|
||||||
|
id: ModelV2.ID,
|
||||||
|
providerID: ProviderV2.ID,
|
||||||
|
variant: optionalOmitUndefined(Schema.String),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const SessionInfo = Schema.Struct({
|
||||||
|
id: SessionSchema.ID,
|
||||||
|
slug: Schema.String,
|
||||||
|
projectID: ProjectV2.ID,
|
||||||
|
workspaceID: optionalOmitUndefined(WorkspaceV2.ID),
|
||||||
|
directory: Schema.String,
|
||||||
|
path: optionalOmitUndefined(Schema.String),
|
||||||
|
parentID: optionalOmitUndefined(SessionSchema.ID),
|
||||||
|
summary: optionalOmitUndefined(SessionSummary),
|
||||||
|
cost: optionalOmitUndefined(Schema.Finite),
|
||||||
|
tokens: optionalOmitUndefined(SessionTokens),
|
||||||
|
share: optionalOmitUndefined(SessionShare),
|
||||||
|
title: Schema.String,
|
||||||
|
agent: optionalOmitUndefined(Schema.String),
|
||||||
|
model: optionalOmitUndefined(SessionModel),
|
||||||
|
version: Schema.String,
|
||||||
|
metadata: optionalOmitUndefined(Schema.Record(Schema.String, Schema.Any)),
|
||||||
|
time: Schema.Struct({
|
||||||
|
created: NonNegativeInt,
|
||||||
|
updated: NonNegativeInt,
|
||||||
|
compacting: optionalOmitUndefined(NonNegativeInt),
|
||||||
|
archived: optionalOmitUndefined(Schema.Finite),
|
||||||
|
}),
|
||||||
|
permission: optionalOmitUndefined(PermissionV1.Ruleset),
|
||||||
|
revert: optionalOmitUndefined(SessionRevert),
|
||||||
|
}).annotate({ identifier: "Session" })
|
||||||
|
export type SessionInfo = typeof SessionInfo.Type
|
||||||
|
|
||||||
|
export const Event = {
|
||||||
|
Created: EventV2.define({
|
||||||
|
type: "session.created",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
info: SessionInfo,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "session.updated",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
info: SessionInfo,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Deleted: EventV2.define({
|
||||||
|
type: "session.deleted",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
info: SessionInfo,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
MessageUpdated: EventV2.define({
|
||||||
|
type: "message.updated",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
info: Info,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
MessageRemoved: EventV2.define({
|
||||||
|
type: "message.removed",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
messageID: MessageID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
PartUpdated: EventV2.define({
|
||||||
|
type: "message.part.updated",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
part: Part,
|
||||||
|
time: Schema.Finite,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
PartRemoved: EventV2.define({
|
||||||
|
type: "message.part.removed",
|
||||||
|
...options,
|
||||||
|
schema: {
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
messageID: MessageID,
|
||||||
|
partID: PartID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|||||||
@@ -233,13 +233,16 @@ describe("CatalogV2", () => {
|
|||||||
model.request.headers.shared = "model"
|
model.request.headers.shared = "model"
|
||||||
model.request.body.model = true
|
model.request.body.model = true
|
||||||
model.request.body.request = true
|
model.request.body.request = true
|
||||||
model.request.body.shared = "model"
|
const options = (model.request.options ??= {})
|
||||||
|
options.shared = "model"
|
||||||
|
options.model = true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const model = required(yield* catalog.model.get(providerID, modelID))
|
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||||
expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
||||||
expect(model.request.body).toEqual({ provider: true, model: true, request: true, shared: "model" })
|
expect(model.request.body).toEqual({ provider: true, model: true, request: true })
|
||||||
|
expect(model.request.options).toEqual({ shared: "model", model: true })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -599,9 +599,9 @@ describe("Config", () => {
|
|||||||
models: {
|
models: {
|
||||||
model: {
|
model: {
|
||||||
request: {
|
request: {
|
||||||
body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
|
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
|
||||||
},
|
},
|
||||||
variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }],
|
variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function request(headers: Record<string, string>, variant?: string) {
|
|||||||
const decode = Schema.decodeUnknownSync(Config.Info)
|
const decode = Schema.decodeUnknownSync(Config.Info)
|
||||||
|
|
||||||
describe("ConfigProviderPlugin.Plugin", () => {
|
describe("ConfigProviderPlugin.Plugin", () => {
|
||||||
it.effect("keeps configured model variant bodies unchanged", () =>
|
it.effect("partitions existing model variant bodies without changing config shape", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.opencode
|
const providerID = ProviderV2.ID.opencode
|
||||||
@@ -96,7 +96,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
expect(model.variants).toMatchObject([
|
expect(model.variants).toMatchObject([
|
||||||
{
|
{
|
||||||
id: "high",
|
id: "high",
|
||||||
body: {
|
body: {},
|
||||||
|
options: {
|
||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
include: ["reasoning.encrypted_content"],
|
include: ["reasoning.encrypted_content"],
|
||||||
@@ -106,7 +107,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("keeps layered model variant bodies unchanged", () =>
|
it.effect("uses the effective provider package across layered config", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
const providerID = ProviderV2.ID.opencode
|
const providerID = ProviderV2.ID.opencode
|
||||||
@@ -146,7 +147,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
|||||||
const model = required(yield* catalog.model.get(providerID, modelID))
|
const model = required(yield* catalog.model.get(providerID, modelID))
|
||||||
expect(model.variants[0]).toMatchObject({
|
expect(model.variants[0]).toMatchObject({
|
||||||
id: "high",
|
id: "high",
|
||||||
body: { reasoningEffort: "high" },
|
body: {},
|
||||||
|
options: { reasoningEffort: "high" },
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
+138
-126
@@ -1,14 +1,10 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Stream } from "effect"
|
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Stream } from "effect"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { Event } from "@opencode-ai/schema/event"
|
|
||||||
import { Session } from "@opencode-ai/schema/session"
|
|
||||||
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
|
||||||
import { SessionV1 } from "@opencode-ai/schema/session-v1"
|
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { EventSequenceTable, EventTable } from "@opencode-ai/core/event/sql"
|
import { EventSequenceTable, EventTable } from "@opencode-ai/core/event/sql"
|
||||||
import { Location } from "@opencode-ai/core/location"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath, DateTimeUtcFromMillis } from "@opencode-ai/core/schema"
|
||||||
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { location } from "./fixture/location"
|
import { location } from "./fixture/location"
|
||||||
@@ -20,6 +16,10 @@ const locationLayer = Layer.succeed(
|
|||||||
location({ directory: AbsolutePath.make("project"), workspaceID: WorkspaceV2.ID.make("wrk_test") }),
|
location({ directory: AbsolutePath.make("project"), workspaceID: WorkspaceV2.ID.make("wrk_test") }),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
const eventLayer = Layer.mergeAll(EventV2.defaultLayer, Database.defaultLayer)
|
||||||
|
const it = testEffect(eventLayer.pipe(Layer.provideMerge(locationLayer)))
|
||||||
|
const itWithoutLocation = testEffect(eventLayer)
|
||||||
|
|
||||||
const Message = EventV2.define({
|
const Message = EventV2.define({
|
||||||
type: "test.message",
|
type: "test.message",
|
||||||
schema: {
|
schema: {
|
||||||
@@ -70,16 +70,18 @@ const VersionedMessage = EventV2.define({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const DurableMessage = SessionV1.Event.MessageRemoved
|
const SyncTimestamp = EventV2.define({
|
||||||
const durableData = (sessionID: Session.ID, text: string) => ({
|
type: "test.timestamp",
|
||||||
sessionID,
|
durable: {
|
||||||
messageID: SessionV1.MessageID.ascending(`msg_${text}`),
|
version: 1,
|
||||||
|
aggregate: "id",
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
id: Schema.String,
|
||||||
|
timestamp: DateTimeUtcFromMillis,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const eventLayer = Layer.mergeAll(EventV2.layerWith().pipe(Layer.provide(Database.defaultLayer)), Database.defaultLayer)
|
|
||||||
const it = testEffect(eventLayer.pipe(Layer.provideMerge(locationLayer)))
|
|
||||||
const itWithoutLocation = testEffect(eventLayer)
|
|
||||||
|
|
||||||
describe("EventV2", () => {
|
describe("EventV2", () => {
|
||||||
it.effect("publishes events with the current location", () =>
|
it.effect("publishes events with the current location", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -120,21 +122,26 @@ describe("EventV2", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("selects the latest durable definition independent of declaration order", () =>
|
it.effect("stores definitions in the exported registry", () =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
expect(EventV2.registry.get(Message.type)).toBe(Message)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("keeps the latest sync definition in the registry", () =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
const latest = EventV2.define({
|
const latest = EventV2.define({
|
||||||
type: "test.out-of-order",
|
type: "test.out-of-order",
|
||||||
durable: { version: 2, aggregate: "id" },
|
durable: { version: 2, aggregate: "id" },
|
||||||
schema: { id: Schema.String },
|
schema: { id: Schema.String },
|
||||||
})
|
})
|
||||||
const historical = EventV2.define({
|
EventV2.define({
|
||||||
type: "test.out-of-order",
|
type: "test.out-of-order",
|
||||||
durable: { version: 1, aggregate: "id" },
|
durable: { version: 1, aggregate: "id" },
|
||||||
schema: { id: Schema.String },
|
schema: { id: Schema.String },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(Event.latest([latest, historical]).get("test.out-of-order")).toBe(latest)
|
expect(EventV2.registry.get("test.out-of-order")).toBe(latest)
|
||||||
expect(Event.latest([historical, latest]).get("test.out-of-order")).toBe(latest)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -356,19 +363,19 @@ describe("EventV2", () => {
|
|||||||
it.effect("replays durable aggregate events after a sequence and tails new events", () =>
|
it.effect("replays durable aggregate events after a sequence and tails new events", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "zero"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "zero" })
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "one"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "one" })
|
||||||
const fiber = yield* events
|
const fiber = yield* events
|
||||||
.durable({ aggregateID, after: 0 })
|
.durable({ aggregateID, after: 0 })
|
||||||
.pipe(Stream.take(2), Stream.runCollect, Effect.forkScoped)
|
.pipe(Stream.take(2), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "two"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "two" })
|
||||||
|
|
||||||
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual([
|
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual([
|
||||||
[1, durableData(aggregateID, "one")],
|
[1, { id: aggregateID, text: "one" }],
|
||||||
[2, durableData(aggregateID, "two")],
|
[2, { id: aggregateID, text: "two" }],
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -376,15 +383,20 @@ describe("EventV2", () => {
|
|||||||
it.effect("catches durable aggregate events published during replay handoff", () =>
|
it.effect("catches durable aggregate events published during replay handoff", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "zero"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "zero" })
|
||||||
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(2), Stream.runCollect, Effect.forkScoped)
|
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(2), Stream.runCollect, Effect.forkScoped)
|
||||||
|
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "one"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "one" })
|
||||||
|
|
||||||
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual([
|
expect(
|
||||||
[0, durableData(aggregateID, "zero")],
|
Array.from(yield* Fiber.join(fiber)).map((event) => [
|
||||||
[1, durableData(aggregateID, "one")],
|
event.durable?.seq,
|
||||||
|
(event.data as { text: string }).text,
|
||||||
|
]),
|
||||||
|
).toEqual([
|
||||||
|
[0, "zero"],
|
||||||
|
[1, "one"],
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -403,16 +415,16 @@ describe("EventV2", () => {
|
|||||||
|
|
||||||
yield* Effect.gen(function* () {
|
yield* Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Deferred.await(readStarted)
|
yield* Deferred.await(readStarted)
|
||||||
|
|
||||||
pause = false
|
pause = false
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "during handoff"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "during handoff" })
|
||||||
yield* Deferred.succeed(continueRead, undefined)
|
yield* Deferred.succeed(continueRead, undefined)
|
||||||
|
|
||||||
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual([
|
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual([
|
||||||
[0, durableData(aggregateID, "during handoff")],
|
[0, { id: aggregateID, text: "during handoff" }],
|
||||||
])
|
])
|
||||||
}).pipe(Effect.provide(Layer.mergeAll(Database.defaultLayer, eventLayer)))
|
}).pipe(Effect.provide(Layer.mergeAll(Database.defaultLayer, eventLayer)))
|
||||||
}),
|
}),
|
||||||
@@ -421,7 +433,7 @@ describe("EventV2", () => {
|
|||||||
it.effect("coalesces durable aggregate wakes while draining every committed event", () =>
|
it.effect("coalesces durable aggregate wakes while draining every committed event", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const count = 64
|
const count = 64
|
||||||
const fiber = yield* events
|
const fiber = yield* events
|
||||||
.durable({ aggregateID })
|
.durable({ aggregateID })
|
||||||
@@ -429,11 +441,11 @@ describe("EventV2", () => {
|
|||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
for (let index = 0; index < count; index++) {
|
for (let index = 0; index < count; index++) {
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, String(index)))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: String(index) })
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual(
|
expect(Array.from(yield* Fiber.join(fiber)).map((event) => [event.durable?.seq, event.data])).toEqual(
|
||||||
Array.from({ length: count }, (_, index) => [index, durableData(aggregateID, String(index))]),
|
Array.from({ length: count }, (_, index) => [index, { id: aggregateID, text: String(index) }]),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -441,14 +453,14 @@ describe("EventV2", () => {
|
|||||||
it.effect("omits live-only events from durable aggregate streams", () =>
|
it.effect("omits live-only events from durable aggregate streams", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
const fiber = yield* events.durable({ aggregateID }).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
yield* events.publish(Message, { text: "live only" })
|
yield* events.publish(Message, { text: "live only" })
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "durable"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "durable" })
|
||||||
|
|
||||||
expect(Array.from(yield* Fiber.join(fiber)).map((event) => event.type)).toEqual([DurableMessage.type])
|
expect(Array.from(yield* Fiber.join(fiber)).map((event) => event.type)).toEqual([SyncMessage.type])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -475,23 +487,23 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
yield* events.project(DurableMessage, (event) =>
|
yield* events.project(SyncMessage, (event) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
received.push(event)
|
received.push(event)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
|
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "hello"),
|
data: { id: aggregateID, text: "hello" },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(received[0]?.type).toBe(DurableMessage.type)
|
expect(received[0]?.type).toBe(SyncMessage.type)
|
||||||
expect(received[0]?.data).toEqual(durableData(aggregateID, "hello"))
|
expect(received[0]?.data).toEqual({ id: aggregateID, text: "hello" })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -499,14 +511,14 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
|
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "replayed"),
|
data: { id: aggregateID, text: "replayed" },
|
||||||
})
|
})
|
||||||
const rows = yield* db
|
const rows = yield* db
|
||||||
.select()
|
.select()
|
||||||
@@ -526,11 +538,11 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const envelopeAggregateID = Session.ID.create()
|
const envelopeAggregateID = EventV2.ID.create()
|
||||||
const payloadAggregateID = Session.ID.create()
|
const payloadAggregateID = EventV2.ID.create()
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
yield* events.publish(DurableMessage, durableData(payloadAggregateID, "seed"))
|
yield* events.publish(SyncMessage, { id: payloadAggregateID, text: "seed" })
|
||||||
yield* events.project(DurableMessage, (event) =>
|
yield* events.project(SyncMessage, (event) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
received.push(event)
|
received.push(event)
|
||||||
}),
|
}),
|
||||||
@@ -539,10 +551,10 @@ describe("EventV2", () => {
|
|||||||
const exit = yield* events
|
const exit = yield* events
|
||||||
.replay({
|
.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID: envelopeAggregateID,
|
aggregateID: envelopeAggregateID,
|
||||||
data: durableData(payloadAggregateID, "replayed"),
|
data: { id: payloadAggregateID, text: "replayed" },
|
||||||
})
|
})
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
const rows = yield* db
|
const rows = yield* db
|
||||||
@@ -568,22 +580,22 @@ describe("EventV2", () => {
|
|||||||
it.effect("replay defects on sequence mismatch", () =>
|
it.effect("replay defects on sequence mismatch", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
|
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "first"),
|
data: { id: aggregateID, text: "first" },
|
||||||
})
|
})
|
||||||
const exit = yield* events
|
const exit = yield* events
|
||||||
.replay({
|
.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 5,
|
seq: 5,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "bad"),
|
data: { id: aggregateID, text: "bad" },
|
||||||
})
|
})
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
|
|
||||||
@@ -594,9 +606,9 @@ describe("EventV2", () => {
|
|||||||
it.effect("replay decodes synchronized transformed values before projection", () =>
|
it.effect("replay decodes synchronized transformed values before projection", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const received = new Array<typeof SessionEvent.ContextUpdated.Type>()
|
const received = new Array<typeof SyncTimestamp.Type>()
|
||||||
yield* events.project(SessionEvent.ContextUpdated, (event) =>
|
yield* events.project(SyncTimestamp, (event) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
received.push(event)
|
received.push(event)
|
||||||
}),
|
}),
|
||||||
@@ -604,10 +616,10 @@ describe("EventV2", () => {
|
|||||||
|
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(SessionEvent.ContextUpdated.type, 1),
|
type: EventV2.versionedType(SyncTimestamp.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: { sessionID: aggregateID, messageID: "msg_context", timestamp: 0, text: "context" },
|
data: { id: aggregateID, timestamp: 0 },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(received[0]?.data.timestamp).toEqual(DateTime.makeUnsafe(0))
|
expect(received[0]?.data.timestamp).toEqual(DateTime.makeUnsafe(0))
|
||||||
@@ -634,21 +646,21 @@ describe("EventV2", () => {
|
|||||||
it.effect("replayAll validates contiguous aggregate events", () =>
|
it.effect("replayAll validates contiguous aggregate events", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const source = yield* events.replayAll([
|
const source = yield* events.replayAll([
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "one"),
|
data: { id: aggregateID, text: "one" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "two"),
|
data: { id: aggregateID, text: "two" },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -660,38 +672,38 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
|
|
||||||
const one = yield* events.replayAll([
|
const one = yield* events.replayAll([
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "one"),
|
data: { id: aggregateID, text: "one" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "two"),
|
data: { id: aggregateID, text: "two" },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const two = yield* events.replayAll([
|
const two = yield* events.replayAll([
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 2,
|
seq: 2,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "three"),
|
data: { id: aggregateID, text: "three" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 3,
|
seq: 3,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "four"),
|
data: { id: aggregateID, text: "four" },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const rows = yield* db
|
const rows = yield* db
|
||||||
@@ -711,10 +723,10 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "seed"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "seed" })
|
||||||
yield* events.claim(aggregateID, "owner-a")
|
yield* events.claim(aggregateID, "owner-a")
|
||||||
yield* events.project(DurableMessage, (event) =>
|
yield* events.project(SyncMessage, (event) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
received.push(event)
|
received.push(event)
|
||||||
}),
|
}),
|
||||||
@@ -723,10 +735,10 @@ describe("EventV2", () => {
|
|||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "ignored"),
|
data: { id: aggregateID, text: "ignored" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-b" },
|
{ ownerID: "owner-b" },
|
||||||
)
|
)
|
||||||
@@ -738,14 +750,14 @@ describe("EventV2", () => {
|
|||||||
it.effect("strict owner fences exact replay", () =>
|
it.effect("strict owner fences exact replay", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const id = EventV2.ID.create()
|
const id = EventV2.ID.create()
|
||||||
const replayed = {
|
const replayed = {
|
||||||
id,
|
id,
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "owned"),
|
data: { id: aggregateID, text: "owned" },
|
||||||
}
|
}
|
||||||
yield* events.replay(replayed, { ownerID: "owner-a" })
|
yield* events.replay(replayed, { ownerID: "owner-a" })
|
||||||
|
|
||||||
@@ -759,11 +771,11 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const published = yield* events.publish(DurableMessage, durableData(aggregateID, "owned"))
|
const published = yield* events.publish(SyncMessage, { id: aggregateID, text: "owned" })
|
||||||
const replayed = {
|
const replayed = {
|
||||||
id: published.id,
|
id: published.id,
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: published.durable!.seq,
|
seq: published.durable!.seq,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: published.data,
|
data: published.data,
|
||||||
@@ -780,7 +792,7 @@ describe("EventV2", () => {
|
|||||||
expect(row?.ownerID).toBe("owner-a")
|
expect(row?.ownerID).toBe("owner-a")
|
||||||
const exit = yield* events
|
const exit = yield* events
|
||||||
.replay(
|
.replay(
|
||||||
{ ...replayed, id: EventV2.ID.create(), seq: 1, data: durableData(aggregateID, "conflict") },
|
{ ...replayed, id: EventV2.ID.create(), seq: 1, data: { id: aggregateID, text: "conflict" } },
|
||||||
{ ownerID: "owner-b", strictOwner: true },
|
{ ownerID: "owner-b", strictOwner: true },
|
||||||
)
|
)
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
@@ -792,15 +804,15 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
|
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "owned"),
|
data: { id: aggregateID, text: "owned" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-1" },
|
{ ownerID: "owner-1" },
|
||||||
)
|
)
|
||||||
@@ -819,26 +831,26 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "local"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "local" })
|
||||||
|
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "claimed"),
|
data: { id: aggregateID, text: "claimed" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-1" },
|
{ ownerID: "owner-1" },
|
||||||
)
|
)
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 2,
|
seq: 2,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "fenced"),
|
data: { id: aggregateID, text: "fenced" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-2" },
|
{ ownerID: "owner-2" },
|
||||||
)
|
)
|
||||||
@@ -863,14 +875,14 @@ describe("EventV2", () => {
|
|||||||
it.effect("strict replay rejects an owner conflict instead of silently skipping it", () =>
|
it.effect("strict replay rejects an owner conflict instead of silently skipping it", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "claimed"),
|
data: { id: aggregateID, text: "claimed" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-1" },
|
{ ownerID: "owner-1" },
|
||||||
)
|
)
|
||||||
@@ -879,10 +891,10 @@ describe("EventV2", () => {
|
|||||||
.replay(
|
.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "conflict"),
|
data: { id: aggregateID, text: "conflict" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-2", strictOwner: true },
|
{ ownerID: "owner-2", strictOwner: true },
|
||||||
)
|
)
|
||||||
@@ -896,14 +908,14 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
||||||
const replayed = {
|
const replayed = {
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "replayed"),
|
data: { id: aggregateID, text: "replayed" },
|
||||||
}
|
}
|
||||||
|
|
||||||
yield* events.replay(replayed, { publish: true })
|
yield* events.replay(replayed, { publish: true })
|
||||||
@@ -917,19 +929,19 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const replayed = {
|
const replayed = {
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "original"),
|
data: { id: aggregateID, text: "original" },
|
||||||
}
|
}
|
||||||
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
||||||
yield* events.replay(replayed, { publish: true })
|
yield* events.replay(replayed, { publish: true })
|
||||||
|
|
||||||
const exit = yield* events
|
const exit = yield* events
|
||||||
.replay({ ...replayed, data: durableData(aggregateID, "divergent") }, { publish: true })
|
.replay({ ...replayed, data: { id: aggregateID, text: "divergent" } }, { publish: true })
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
|
|
||||||
expect(String(exit)).toContain("Replay diverged")
|
expect(String(exit)).toContain("Replay diverged")
|
||||||
@@ -940,23 +952,23 @@ describe("EventV2", () => {
|
|||||||
it.effect("rejects an event ID reused at another aggregate position", () =>
|
it.effect("rejects an event ID reused at another aggregate position", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const id = EventV2.ID.create()
|
const id = EventV2.ID.create()
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id,
|
id,
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "first"),
|
data: { id: aggregateID, text: "first" },
|
||||||
})
|
})
|
||||||
|
|
||||||
const exit = yield* events
|
const exit = yield* events
|
||||||
.replay({
|
.replay({
|
||||||
id,
|
id,
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "second"),
|
data: { id: aggregateID, text: "second" },
|
||||||
})
|
})
|
||||||
.pipe(Effect.exit)
|
.pipe(Effect.exit)
|
||||||
|
|
||||||
@@ -968,27 +980,27 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
yield* events.listen((event) => Effect.sync(() => received.push(event)))
|
||||||
|
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "first"),
|
data: { id: aggregateID, text: "first" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-1" },
|
{ ownerID: "owner-1" },
|
||||||
)
|
)
|
||||||
yield* events.replay(
|
yield* events.replay(
|
||||||
{
|
{
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 1,
|
seq: 1,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "ignored"),
|
data: { id: aggregateID, text: "ignored" },
|
||||||
},
|
},
|
||||||
{ ownerID: "owner-2", publish: true },
|
{ ownerID: "owner-2", publish: true },
|
||||||
)
|
)
|
||||||
@@ -1035,10 +1047,10 @@ describe("EventV2", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const received = new Array<EventV2.Payload>()
|
const received = new Array<EventV2.Payload>()
|
||||||
const aggregateID = Session.ID.create()
|
const aggregateID = EventV2.ID.create()
|
||||||
yield* events.publish(DurableMessage, durableData(aggregateID, "seed"))
|
yield* events.publish(SyncMessage, { id: aggregateID, text: "seed" })
|
||||||
yield* events.remove(aggregateID)
|
yield* events.remove(aggregateID)
|
||||||
yield* events.project(DurableMessage, (event) =>
|
yield* events.project(SyncMessage, (event) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
received.push(event)
|
received.push(event)
|
||||||
}),
|
}),
|
||||||
@@ -1046,13 +1058,13 @@ describe("EventV2", () => {
|
|||||||
|
|
||||||
yield* events.replay({
|
yield* events.replay({
|
||||||
id: EventV2.ID.create(),
|
id: EventV2.ID.create(),
|
||||||
type: EventV2.versionedType(DurableMessage.type, 1),
|
type: EventV2.versionedType(SyncMessage.type, 1),
|
||||||
seq: 0,
|
seq: 0,
|
||||||
aggregateID,
|
aggregateID,
|
||||||
data: durableData(aggregateID, "replayed"),
|
data: { id: aggregateID, text: "replayed" },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(received[0]?.data).toEqual(durableData(aggregateID, "replayed"))
|
expect(received[0]?.data).toEqual({ id: aggregateID, text: "replayed" })
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { SessionV1 as Wire } from "@opencode-ai/schema/session-v1"
|
|
||||||
import { SessionV1 } from "../src/v1/session"
|
|
||||||
|
|
||||||
describe("legacy event schema compatibility", () => {
|
|
||||||
test("Core references canonical SessionV1 definitions", () => {
|
|
||||||
expect(SessionV1.Event.Created).toBe(Wire.Event.Created)
|
|
||||||
expect(SessionV1.Event.PartUpdated).toBe(Wire.Event.PartUpdated)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Core retains NamedError constructor identity", () => {
|
|
||||||
const error = new SessionV1.APIError({ message: "failed", isRetryable: false })
|
|
||||||
expect(error).toBeInstanceOf(SessionV1.APIError)
|
|
||||||
expect(error.toObject()).toEqual({ name: "APIError", data: { message: "failed", isRetryable: false } })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { ModelRequest } from "@opencode-ai/core/model-request"
|
||||||
|
|
||||||
|
describe("ModelRequest", () => {
|
||||||
|
test("partitions AI SDK model and models.dev mode options", () => {
|
||||||
|
expect(
|
||||||
|
ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", {
|
||||||
|
maxOutputTokens: 4096,
|
||||||
|
temperature: 0.2,
|
||||||
|
reasoningEffort: "high",
|
||||||
|
serviceTier: "priority",
|
||||||
|
custom_extension: { enabled: true },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
generation: { maxTokens: 4096, temperature: 0.2 },
|
||||||
|
options: { reasoningEffort: "high", serviceTier: "priority" },
|
||||||
|
body: { custom_extension: { enabled: true } },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("keeps unknown-provider options as compatibility fields", () => {
|
||||||
|
expect(ModelRequest.normalizeAiSdkOptions(undefined, { temperature: 0.2, reasoningEffort: "high" })).toEqual({
|
||||||
|
generation: { temperature: 0.2 },
|
||||||
|
options: {},
|
||||||
|
body: { reasoningEffort: "high" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not consult inherited package-name properties", () => {
|
||||||
|
expect(ModelRequest.normalizeAiSdkOptions("__proto__", { reasoningEffort: "high" })).toEqual({
|
||||||
|
generation: {},
|
||||||
|
options: {},
|
||||||
|
body: { reasoningEffort: "high" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("normalizes models.dev wire aliases owned by native protocols", () => {
|
||||||
|
expect(ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", { service_tier: "priority" })).toEqual({
|
||||||
|
generation: {},
|
||||||
|
options: { serviceTier: "priority" },
|
||||||
|
body: {},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -290,11 +290,21 @@ function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
|
|||||||
...value.request,
|
...value.request,
|
||||||
headers: { ...value.request.headers },
|
headers: { ...value.request.headers },
|
||||||
body: { ...value.request.body },
|
body: { ...value.request.body },
|
||||||
|
generation: value.request.generation && {
|
||||||
|
...value.request.generation,
|
||||||
|
stop: value.request.generation.stop && [...value.request.generation.stop],
|
||||||
|
},
|
||||||
|
options: value.request.options && { ...value.request.options },
|
||||||
},
|
},
|
||||||
variants: value.variants.map((variant) => ({
|
variants: value.variants.map((variant) => ({
|
||||||
...variant,
|
...variant,
|
||||||
headers: { ...variant.headers },
|
headers: { ...variant.headers },
|
||||||
body: { ...variant.body },
|
body: { ...variant.body },
|
||||||
|
generation: variant.generation && {
|
||||||
|
...variant.generation,
|
||||||
|
stop: variant.generation.stop && [...variant.generation.stop],
|
||||||
|
},
|
||||||
|
options: variant.options && { ...variant.options },
|
||||||
})),
|
})),
|
||||||
time: { ...value.time },
|
time: { ...value.time },
|
||||||
cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),
|
cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),
|
||||||
|
|||||||
@@ -150,12 +150,15 @@ describe("OpencodePlugin", () => {
|
|||||||
cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }],
|
cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }],
|
||||||
limit: { context: 1000, output: 100 },
|
limit: { context: 1000, output: 100 },
|
||||||
})
|
})
|
||||||
expect(model.request.body).toEqual({ custom: "value", temperature: 0.5 })
|
expect(model.request).toMatchObject({ body: { custom: "value" }, generation: { temperature: 0.5 } })
|
||||||
|
expect(model.request.body).toEqual({ custom: "value" })
|
||||||
expect(model.variants).toEqual([
|
expect(model.variants).toEqual([
|
||||||
{
|
{
|
||||||
id: ModelV2.VariantID.make("high"),
|
id: ModelV2.VariantID.make("high"),
|
||||||
headers: {},
|
headers: {},
|
||||||
body: { temperature: 0.2 },
|
body: {},
|
||||||
|
generation: { temperature: 0.2 },
|
||||||
|
options: {},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import { describe, expect } from "bun:test"
|
|
||||||
import { Catalog } from "@opencode-ai/core/catalog"
|
|
||||||
import { Credential } from "@opencode-ai/core/credential"
|
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
|
||||||
import { Integration } from "@opencode-ai/core/integration"
|
|
||||||
import { Location } from "@opencode-ai/core/location"
|
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
|
||||||
import { Policy } from "@opencode-ai/core/policy"
|
|
||||||
import { VariantPlugin } from "@opencode-ai/core/plugin/variant"
|
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
||||||
import { Effect, Layer } from "effect"
|
|
||||||
import { location } from "../fixture/location"
|
|
||||||
import { testEffect } from "../lib/effect"
|
|
||||||
import { catalogHost, host } from "./host"
|
|
||||||
|
|
||||||
const events = EventV2.defaultLayer
|
|
||||||
const locationLayer = Layer.succeed(
|
|
||||||
Location.Service,
|
|
||||||
Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
|
|
||||||
)
|
|
||||||
const connections = Credential.defaultLayer.pipe(Layer.fresh)
|
|
||||||
const integrations = Integration.locationLayer.pipe(Layer.provide(events), Layer.provide(connections))
|
|
||||||
const catalog = Catalog.layer.pipe(
|
|
||||||
Layer.provide(
|
|
||||||
Layer.mergeAll(events, locationLayer, Policy.layer.pipe(Layer.provide(locationLayer)), connections, integrations),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const it = testEffect(
|
|
||||||
Layer.mergeAll(catalog.pipe(Layer.provide(connections)), integrations, connections, events, locationLayer),
|
|
||||||
)
|
|
||||||
|
|
||||||
describe("VariantPlugin", () => {
|
|
||||||
it.effect("adds GLM 5.2 variants after catalog sources", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const service = yield* Catalog.Service
|
|
||||||
yield* service.transform((catalog) => {
|
|
||||||
catalog.provider.update(ProviderV2.ID.opencode, (provider) => {
|
|
||||||
provider.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
|
|
||||||
})
|
|
||||||
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
|
|
||||||
model.api = {
|
|
||||||
id: ModelV2.ID.make("glm-5.2"),
|
|
||||||
type: "aisdk",
|
|
||||||
package: "@ai-sdk/openai-compatible",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
|
||||||
|
|
||||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
|
||||||
expect.objectContaining({ id: "high", body: { reasoning_effort: "high" } }),
|
|
||||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
|
||||||
])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("keeps explicit variants over generated defaults", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const service = yield* Catalog.Service
|
|
||||||
yield* service.transform((catalog) => {
|
|
||||||
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
|
|
||||||
model.api = {
|
|
||||||
id: ModelV2.ID.make("glm-5.2"),
|
|
||||||
type: "aisdk",
|
|
||||||
package: "@ai-sdk/openai-compatible",
|
|
||||||
}
|
|
||||||
model.variants = [{ id: ModelV2.VariantID.make("high"), headers: { custom: "true" }, body: {} }]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
|
|
||||||
|
|
||||||
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
|
|
||||||
expect.objectContaining({ id: "high", headers: { custom: "true" } }),
|
|
||||||
expect.objectContaining({ id: "max", body: { reasoning_effort: "max" } }),
|
|
||||||
])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
@@ -31,6 +31,8 @@ const model = (api: Api, variants: ModelV2.Info["variants"] = []) =>
|
|||||||
request: {
|
request: {
|
||||||
headers: { "x-test": "header" },
|
headers: { "x-test": "header" },
|
||||||
body: { apiKey: "secret", custom_extension: { enabled: true } },
|
body: { apiKey: "secret", custom_extension: { enabled: true } },
|
||||||
|
generation: { temperature: 0.7 },
|
||||||
|
options: { store: false, serviceTier: "priority" },
|
||||||
},
|
},
|
||||||
variants,
|
variants,
|
||||||
time: { released: 0 },
|
time: { released: 0 },
|
||||||
@@ -54,6 +56,8 @@ describe("SessionRunnerModel", () => {
|
|||||||
defaults: {
|
defaults: {
|
||||||
headers: { "x-test": "header" },
|
headers: { "x-test": "header" },
|
||||||
limits: { context: 100, output: 20 },
|
limits: { context: 100, output: 20 },
|
||||||
|
generation: { temperature: 0.7 },
|
||||||
|
providerOptions: { openai: { store: false, serviceTier: "priority" } },
|
||||||
http: { body: { custom_extension: { enabled: true } } },
|
http: { body: { custom_extension: { enabled: true } } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -82,7 +86,7 @@ describe("SessionRunnerModel", () => {
|
|||||||
url: "https://compatible.example/v1",
|
url: "https://compatible.example/v1",
|
||||||
settings: { apiKey: "settings-secret", compatibility: "strict" },
|
settings: { apiKey: "settings-secret", compatibility: "strict" },
|
||||||
}),
|
}),
|
||||||
request: { headers: {}, body: {} },
|
request: { headers: {}, body: {}, generation: {}, options: {} },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const request = LLM.request({ model: resolved, prompt: "Hello" })
|
const request = LLM.request({ model: resolved, prompt: "Hello" })
|
||||||
@@ -99,20 +103,21 @@ describe("SessionRunnerModel", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("overlays selected OpenAI Session variant bodies", () =>
|
it.effect("lowers selected OpenAI Session variants into Responses options", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
|
const base = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
|
||||||
{
|
{
|
||||||
id: ModelV2.VariantID.make("high"),
|
id: ModelV2.VariantID.make("high"),
|
||||||
headers: { "x-variant": "high" },
|
headers: { "x-variant": "high" },
|
||||||
body: {
|
body: {},
|
||||||
store: false,
|
generation: { temperature: 0.2 },
|
||||||
service_tier: "priority",
|
options: { reasoningEffort: "high" },
|
||||||
temperature: 0.2,
|
|
||||||
reasoning: { effort: "high" },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
const catalog = ModelV2.Info.make({
|
||||||
|
...base,
|
||||||
|
request: { ...base.request, options: { ...base.request.options, reasoningEffort: "medium" } },
|
||||||
|
})
|
||||||
const session = SessionV2.Info.make({
|
const session = SessionV2.Info.make({
|
||||||
id: SessionV2.ID.make("ses_model_variant"),
|
id: SessionV2.ID.make("ses_model_variant"),
|
||||||
projectID: ProjectV2.ID.global,
|
projectID: ProjectV2.ID.global,
|
||||||
@@ -129,19 +134,21 @@ describe("SessionRunnerModel", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||||
|
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||||
|
|
||||||
expect(resolved.route.defaults.headers).toMatchObject({ "x-test": "header", "x-variant": "high" })
|
expect(resolved.route.defaults.headers).toMatchObject({ "x-test": "header", "x-variant": "high" })
|
||||||
expect(resolved.route.defaults.http?.body).toEqual({
|
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||||
custom_extension: { enabled: true },
|
expect(prepared.body).toMatchObject({
|
||||||
store: false,
|
store: false,
|
||||||
service_tier: "priority",
|
service_tier: "priority",
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
reasoning: { effort: "high" },
|
reasoning: { effort: "high" },
|
||||||
})
|
})
|
||||||
|
expect(prepared.body).not.toHaveProperty("reasoningEffort")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("overlays selected OpenAI-compatible Session variant bodies", () =>
|
it.effect("lowers selected OpenAI-compatible Session variants into Chat options", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = model(
|
const catalog = model(
|
||||||
{ type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://compatible.example/v1" },
|
{ type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://compatible.example/v1" },
|
||||||
@@ -149,7 +156,9 @@ describe("SessionRunnerModel", () => {
|
|||||||
{
|
{
|
||||||
id: ModelV2.VariantID.make("high"),
|
id: ModelV2.VariantID.make("high"),
|
||||||
headers: {},
|
headers: {},
|
||||||
body: { store: false, reasoning_effort: "high" },
|
body: {},
|
||||||
|
generation: {},
|
||||||
|
options: { reasoningEffort: "high" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -165,12 +174,14 @@ describe("SessionRunnerModel", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||||
|
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||||
|
|
||||||
expect(resolved.route.defaults.http?.body).toEqual({
|
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||||
custom_extension: { enabled: true },
|
expect(prepared.body).toMatchObject({
|
||||||
store: false,
|
store: false,
|
||||||
reasoning_effort: "high",
|
reasoning_effort: "high",
|
||||||
})
|
})
|
||||||
|
expect(prepared.body).not.toHaveProperty("reasoningEffort")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -204,13 +215,15 @@ describe("SessionRunnerModel", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("overlays selected Anthropic Session variant bodies", () =>
|
it.effect("lowers selected Anthropic Session variants into Messages options", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [
|
const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [
|
||||||
{
|
{
|
||||||
id: ModelV2.VariantID.make("high"),
|
id: ModelV2.VariantID.make("high"),
|
||||||
headers: {},
|
headers: {},
|
||||||
body: { thinking: { type: "enabled", budget_tokens: 12000 } },
|
body: {},
|
||||||
|
generation: {},
|
||||||
|
options: { thinking: { type: "enabled", budgetTokens: 12000 } },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const session = SessionV2.Info.make({
|
const session = SessionV2.Info.make({
|
||||||
@@ -225,11 +238,13 @@ describe("SessionRunnerModel", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
const resolved = yield* SessionRunnerModel.resolve(session, catalog)
|
||||||
|
const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
|
||||||
|
|
||||||
expect(resolved.route.defaults.http?.body).toEqual({
|
expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
|
||||||
custom_extension: { enabled: true },
|
expect(prepared.body).toMatchObject({
|
||||||
thinking: { type: "enabled", budget_tokens: 12000 },
|
thinking: { type: "enabled", budget_tokens: 12000 },
|
||||||
})
|
})
|
||||||
|
expect(JSON.stringify(prepared.body)).not.toContain("budgetTokens")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -251,7 +266,7 @@ describe("SessionRunnerModel", () => {
|
|||||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||||
request: { headers: {}, body: {} },
|
request: { headers: {}, body: {}, generation: {}, options: {} },
|
||||||
}),
|
}),
|
||||||
Credential.Key.make({ type: "key", key: "secret" }),
|
Credential.Key.make({ type: "key", key: "secret" }),
|
||||||
)
|
)
|
||||||
@@ -274,7 +289,7 @@ describe("SessionRunnerModel", () => {
|
|||||||
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
const resolved = yield* SessionRunnerModel.fromCatalogModel(
|
||||||
ModelV2.Info.make({
|
ModelV2.Info.make({
|
||||||
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
|
||||||
request: { headers: {}, body: { apiKey: "configured-secret" } },
|
request: { headers: {}, body: { apiKey: "configured-secret" }, generation: {}, options: {} },
|
||||||
}),
|
}),
|
||||||
credential,
|
credential,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { Credential } from "@opencode-ai/schema/credential"
|
|||||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||||
import { Integration } from "@opencode-ai/schema/integration"
|
import { Integration } from "@opencode-ai/schema/integration"
|
||||||
import { LLM } from "@opencode-ai/schema/llm"
|
import { LLM } from "@opencode-ai/schema/llm"
|
||||||
|
import { ModelRequest } from "@opencode-ai/schema/model-request"
|
||||||
import { Permission } from "@opencode-ai/schema/permission"
|
import { Permission } from "@opencode-ai/schema/permission"
|
||||||
import { Reference } from "@opencode-ai/schema/reference"
|
import { Reference } from "@opencode-ai/schema/reference"
|
||||||
import { Skill } from "@opencode-ai/schema/skill"
|
import { Skill } from "@opencode-ai/schema/skill"
|
||||||
@@ -34,6 +35,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
coreIntegration,
|
coreIntegration,
|
||||||
coreLocation,
|
coreLocation,
|
||||||
coreLLM,
|
coreLLM,
|
||||||
|
coreModelRequest,
|
||||||
corePermission,
|
corePermission,
|
||||||
coreProject,
|
coreProject,
|
||||||
coreReference,
|
coreReference,
|
||||||
@@ -51,6 +53,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
import("@opencode-ai/core/integration"),
|
import("@opencode-ai/core/integration"),
|
||||||
import("@opencode-ai/core/location"),
|
import("@opencode-ai/core/location"),
|
||||||
import("@opencode-ai/llm"),
|
import("@opencode-ai/llm"),
|
||||||
|
import("@opencode-ai/core/model-request"),
|
||||||
import("@opencode-ai/core/permission"),
|
import("@opencode-ai/core/permission"),
|
||||||
import("@opencode-ai/core/project/schema"),
|
import("@opencode-ai/core/project/schema"),
|
||||||
import("@opencode-ai/core/reference"),
|
import("@opencode-ai/core/reference"),
|
||||||
@@ -108,6 +111,8 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
[ProviderV2.Api, Provider.Api],
|
[ProviderV2.Api, Provider.Api],
|
||||||
[ProviderV2.Request, Provider.Request],
|
[ProviderV2.Request, Provider.Request],
|
||||||
[ProviderV2.Info, Provider.Info],
|
[ProviderV2.Info, Provider.Info],
|
||||||
|
[coreModelRequest.Generation, ModelRequest.Generation],
|
||||||
|
[coreModelRequest.Request, ModelRequest.Request],
|
||||||
[corePermission.Effect, Permission.Effect],
|
[corePermission.Effect, Permission.Effect],
|
||||||
[corePermission.Rule, Permission.Rule],
|
[corePermission.Rule, Permission.Rule],
|
||||||
[corePermission.Ruleset, Permission.Ruleset],
|
[corePermission.Ruleset, Permission.Ruleset],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://opencode.ai",
|
"homepage": "https://opencode.ai",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/session-ui": "workspace:*",
|
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
@import "@opencode-ai/ui/styles/tailwind";
|
@import "@opencode-ai/ui/styles/tailwind";
|
||||||
@import "@opencode-ai/session-ui/styles";
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Message, Model, Part, Session, SessionStatus, SnapshotFileDiff, UserMessage } from "@opencode-ai/sdk/v2"
|
import { Message, Model, Part, Session, SessionStatus, SnapshotFileDiff, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { SessionTurn } from "@opencode-ai/session-ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
import { SessionReview } from "@opencode-ai/session-ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import { DataProvider } from "@opencode-ai/session-ui/context"
|
import { DataProvider } from "@opencode-ai/ui/context"
|
||||||
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
||||||
import { WorkerPoolProvider } from "@opencode-ai/ui/context/worker-pool"
|
import { WorkerPoolProvider } from "@opencode-ai/ui/context/worker-pool"
|
||||||
import { createAsync, query, useParams } from "@solidjs/router"
|
import { createAsync, query, useParams } from "@solidjs/router"
|
||||||
@@ -17,15 +17,15 @@ import { DateTime } from "luxon"
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import NotFound from "../[...404]"
|
import NotFound from "../[...404]"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { MessageNav } from "@opencode-ai/session-ui/message-nav"
|
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
||||||
import { FileSSR } from "@opencode-ai/session-ui/file-ssr"
|
import { FileSSR } from "@opencode-ai/ui/file-ssr"
|
||||||
import { clientOnly } from "@solidjs/start"
|
import { clientOnly } from "@solidjs/start"
|
||||||
import { Meta, Title } from "@solidjs/meta"
|
import { Meta, Title } from "@solidjs/meta"
|
||||||
import { Base64 } from "js-base64"
|
import { Base64 } from "js-base64"
|
||||||
import { getRequestEvent } from "solid-js/web"
|
import { getRequestEvent } from "solid-js/web"
|
||||||
|
|
||||||
const ClientOnlyWorkerPoolProvider = clientOnly(() =>
|
const ClientOnlyWorkerPoolProvider = clientOnly(() =>
|
||||||
import("@opencode-ai/session-ui/pierre/worker").then((m) => ({
|
import("@opencode-ai/ui/pierre/worker").then((m) => ({
|
||||||
default: (props: { children: any }) => (
|
default: (props: { children: any }) => (
|
||||||
<WorkerPoolProvider pools={m.getWorkerPools()}>{props.children}</WorkerPoolProvider>
|
<WorkerPoolProvider pools={m.getWorkerPools()}>{props.children}</WorkerPoolProvider>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "@opencode-ai/llm",
|
"name": "@opencode-ai/llm",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.17.10",
|
"version": "1.17.9",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
"@opencode-ai/llm": "workspace:*",
|
"@opencode-ai/llm": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/server": "workspace:*",
|
"@opencode-ai/server": "workspace:*",
|
||||||
|
|||||||
@@ -2,20 +2,29 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { EffectBridge } from "@/effect/bridge"
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
import type { InstanceContext } from "@/project/instance-context"
|
import type { InstanceContext } from "@/project/instance-context"
|
||||||
|
import { SessionID, MessageID } from "@/session/schema"
|
||||||
import { Effect, Layer, Context, Schema } from "effect"
|
import { Effect, Layer, Context, Schema } from "effect"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
import { Skill } from "../skill"
|
import { Skill } from "../skill"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||||
import PROMPT_REVIEW from "./template/review.txt"
|
import PROMPT_REVIEW from "./template/review.txt"
|
||||||
import { LegacyEvent } from "@opencode-ai/schema/legacy-event"
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
commands: Record<string, Info>
|
commands: Record<string, Info>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Executed: LegacyEvent.CommandExecuted,
|
Executed: EventV2.define({
|
||||||
|
type: "command.executed",
|
||||||
|
schema: {
|
||||||
|
name: Schema.String,
|
||||||
|
sessionID: SessionID,
|
||||||
|
arguments: Schema.String,
|
||||||
|
messageID: MessageID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Info = Schema.Struct({
|
export const Info = Schema.Struct({
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import { Vcs } from "@/project/vcs"
|
|||||||
import { InstanceStore } from "@/project/instance-store"
|
import { InstanceStore } from "@/project/instance-store"
|
||||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||||
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
|
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
|
||||||
import { WorkspaceEvent } from "@opencode-ai/schema/workspace-event"
|
|
||||||
|
|
||||||
export const Info = Schema.Struct({
|
export const Info = Schema.Struct({
|
||||||
...WorkspaceInfoSchema.fields,
|
...WorkspaceInfoSchema.fields,
|
||||||
@@ -41,10 +40,27 @@ export const Info = Schema.Struct({
|
|||||||
}).annotate({ identifier: "Workspace" })
|
}).annotate({ identifier: "Workspace" })
|
||||||
export type Info = WorkspaceInfo & { timeUsed: number }
|
export type Info = WorkspaceInfo & { timeUsed: number }
|
||||||
|
|
||||||
export const ConnectionStatus = WorkspaceEvent.ConnectionStatus
|
export const ConnectionStatus = Schema.Struct({
|
||||||
export type ConnectionStatus = WorkspaceEvent.ConnectionStatus
|
workspaceID: WorkspaceV2.ID,
|
||||||
|
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
|
||||||
|
})
|
||||||
|
export type ConnectionStatus = Schema.Schema.Type<typeof ConnectionStatus>
|
||||||
|
|
||||||
export const Event = WorkspaceEvent
|
export const Event = {
|
||||||
|
Ready: EventV2.define({
|
||||||
|
type: "workspace.ready",
|
||||||
|
schema: {
|
||||||
|
name: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Failed: EventV2.define({
|
||||||
|
type: "workspace.failed",
|
||||||
|
schema: {
|
||||||
|
message: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Status: EventV2.define({ type: "workspace.status", schema: ConnectionStatus.fields }),
|
||||||
|
}
|
||||||
|
|
||||||
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export * as EventManifest from "./event-manifest"
|
|
||||||
|
|
||||||
export { Definitions, Durable, Latest } from "@opencode-ai/schema/event-manifest"
|
|
||||||
@@ -7,6 +7,9 @@ import { EventV2 } from "@opencode-ai/core/event"
|
|||||||
import { Location } from "@opencode-ai/core/location"
|
import { Location } from "@opencode-ai/core/location"
|
||||||
import { Project } from "@opencode-ai/core/project"
|
import { Project } from "@opencode-ai/core/project"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
|
import "@opencode-ai/core/account"
|
||||||
|
import "@opencode-ai/core/catalog"
|
||||||
|
import "@opencode-ai/core/session/event"
|
||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
|
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
|
||||||
@@ -42,7 +45,10 @@ export const layer = Layer.effect(
|
|||||||
workspace: workspaceID,
|
workspace: workspaceID,
|
||||||
payload: { id: event.id, type: event.type, properties: event.data },
|
payload: { id: event.id, type: event.type, properties: event.data },
|
||||||
})
|
})
|
||||||
if (event.durable === undefined) return
|
const durable = EventV2.registry.get(event.type)?.durable
|
||||||
|
if (durable === undefined || event.durable === undefined) return
|
||||||
|
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
|
||||||
|
if (typeof aggregateID !== "string") return
|
||||||
GlobalBus.emit("event", {
|
GlobalBus.emit("event", {
|
||||||
directory: event.location?.directory ?? ctx?.directory,
|
directory: event.location?.directory ?? ctx?.directory,
|
||||||
project: ctx?.project.id,
|
project: ctx?.project.id,
|
||||||
@@ -53,7 +59,7 @@ export const layer = Layer.effect(
|
|||||||
id: event.id,
|
id: event.id,
|
||||||
type: EventV2.versionedType(event.type, event.durable.version),
|
type: EventV2.versionedType(event.type, event.durable.version),
|
||||||
seq: event.durable.seq,
|
seq: event.durable.seq,
|
||||||
aggregateID: event.durable.aggregateID,
|
aggregateID,
|
||||||
data: event.data,
|
data: event.data,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { NamedError } from "@opencode-ai/core/util/error"
|
import { NamedError } from "@opencode-ai/core/util/error"
|
||||||
import { Process } from "@/util/process"
|
import { Process } from "@/util/process"
|
||||||
import { IdeEvent } from "@opencode-ai/schema/ide-event"
|
|
||||||
|
|
||||||
const SUPPORTED_IDES = [
|
const SUPPORTED_IDES = [
|
||||||
{ name: "Windsurf" as const, cmd: "windsurf" },
|
{ name: "Windsurf" as const, cmd: "windsurf" },
|
||||||
@@ -11,7 +11,14 @@ const SUPPORTED_IDES = [
|
|||||||
{ name: "VSCodium" as const, cmd: "codium" },
|
{ name: "VSCodium" as const, cmd: "codium" },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const Event = IdeEvent
|
export const Event = {
|
||||||
|
Installed: EventV2.define({
|
||||||
|
type: "ide.installed",
|
||||||
|
schema: {
|
||||||
|
ide: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
|
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,30 @@ import { errorMessage } from "@/util/error"
|
|||||||
import { ChildProcess } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import { AppProcess } from "@opencode-ai/core/process"
|
import { AppProcess } from "@opencode-ai/core/process"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
|
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||||
import { NpmConfig } from "@opencode-ai/core/npm-config"
|
import { NpmConfig } from "@opencode-ai/core/npm-config"
|
||||||
import { InstallationEvent } from "@opencode-ai/schema/installation-event"
|
|
||||||
|
|
||||||
export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown"
|
export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown"
|
||||||
|
|
||||||
export type ReleaseType = "patch" | "minor" | "major"
|
export type ReleaseType = "patch" | "minor" | "major"
|
||||||
|
|
||||||
export const Event = InstallationEvent
|
export const Event = {
|
||||||
|
Updated: EventV2.define({
|
||||||
|
type: "installation.updated",
|
||||||
|
schema: {
|
||||||
|
version: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
UpdateAvailable: EventV2.define({
|
||||||
|
type: "installation.update-available",
|
||||||
|
schema: {
|
||||||
|
version: Schema.String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
export function getReleaseType(current: string, latest: string): ReleaseType {
|
export function getReleaseType(current: string, latest: string): ReleaseType {
|
||||||
const currMajor = semver.major(current)
|
const currMajor = semver.major(current)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import * as LSPClient from "./client"
|
import * as LSPClient from "./client"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { pathToFileURL, fileURLToPath } from "url"
|
import { pathToFileURL, fileURLToPath } from "url"
|
||||||
@@ -13,9 +14,10 @@ import { InstanceState } from "@/effect/instance-state"
|
|||||||
import { containsPath } from "@/project/instance-context"
|
import { containsPath } from "@/project/instance-context"
|
||||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { LspEvent } from "@opencode-ai/schema/lsp-event"
|
|
||||||
|
|
||||||
export const Event = LspEvent
|
export const Event = {
|
||||||
|
Updated: EventV2.define({ type: "lsp.updated", schema: {} }),
|
||||||
|
}
|
||||||
|
|
||||||
const Position = Schema.Struct({
|
const Position = Schema.Struct({
|
||||||
line: NonNegativeInt,
|
line: NonNegativeInt,
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ export function fetch<T extends { name: string }>(
|
|||||||
|
|
||||||
export const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, "_")
|
export const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, "_")
|
||||||
|
|
||||||
export const toolName = (clientName: string, name: string) => sanitize(clientName) + "_" + sanitize(name)
|
|
||||||
|
|
||||||
export function prompts(client: Client, timeout?: number) {
|
export function prompts(client: Client, timeout?: number) {
|
||||||
if (!client.getServerCapabilities()?.prompts) return Promise.resolve([])
|
if (!client.getServerCapabilities()?.prompts) return Promise.resolve([])
|
||||||
return paginate(
|
return paginate(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { McpOAuthProvider, OAUTH_CALLBACK_PATH } from "./oauth-provider"
|
|||||||
import { McpOAuthCallback } from "./oauth-callback"
|
import { McpOAuthCallback } from "./oauth-callback"
|
||||||
import { McpAuth } from "./auth"
|
import { McpAuth } from "./auth"
|
||||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { TuiEvent } from "@/server/tui-event"
|
import { TuiEvent } from "@/server/tui-event"
|
||||||
import open from "open"
|
import open from "open"
|
||||||
import { Cause, Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect"
|
import { Cause, Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect"
|
||||||
@@ -34,7 +35,6 @@ import { InstanceState } from "@/effect/instance-state"
|
|||||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
import { McpCatalog } from "./catalog"
|
import { McpCatalog } from "./catalog"
|
||||||
import { McpEvent } from "@opencode-ai/schema/mcp-event"
|
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT = 30_000
|
const DEFAULT_TIMEOUT = 30_000
|
||||||
const CLIENT_OPTIONS = {
|
const CLIENT_OPTIONS = {
|
||||||
@@ -59,9 +59,20 @@ export const Resource = Schema.Struct({
|
|||||||
}).annotate({ identifier: "McpResource" })
|
}).annotate({ identifier: "McpResource" })
|
||||||
export type Resource = Schema.Schema.Type<typeof Resource>
|
export type Resource = Schema.Schema.Type<typeof Resource>
|
||||||
|
|
||||||
export const ToolsChanged = McpEvent.ToolsChanged
|
export const ToolsChanged = EventV2.define({
|
||||||
|
type: "mcp.tools.changed",
|
||||||
|
schema: {
|
||||||
|
server: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const BrowserOpenFailed = McpEvent.BrowserOpenFailed
|
export const BrowserOpenFailed = EventV2.define({
|
||||||
|
type: "mcp.browser.open.failed",
|
||||||
|
schema: {
|
||||||
|
mcpName: Schema.String,
|
||||||
|
url: Schema.String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const Failed = NamedError.create("MCPFailed", {
|
export const Failed = NamedError.create("MCPFailed", {
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
@@ -129,7 +140,6 @@ interface CreateResult {
|
|||||||
mcpClient?: MCPClient
|
mcpClient?: MCPClient
|
||||||
status: Status
|
status: Status
|
||||||
defs?: MCPToolDef[]
|
defs?: MCPToolDef[]
|
||||||
instructions?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthResult {
|
interface AuthResult {
|
||||||
@@ -145,19 +155,11 @@ interface State {
|
|||||||
status: Record<string, Status>
|
status: Record<string, Status>
|
||||||
clients: Record<string, MCPClient>
|
clients: Record<string, MCPClient>
|
||||||
defs: Record<string, MCPToolDef[]>
|
defs: Record<string, MCPToolDef[]>
|
||||||
instructions: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerInstructions {
|
|
||||||
name: string
|
|
||||||
instructions: string
|
|
||||||
tools: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly status: () => Effect.Effect<Record<string, Status>>
|
readonly status: () => Effect.Effect<Record<string, Status>>
|
||||||
readonly clients: () => Effect.Effect<Record<string, MCPClient>>
|
readonly clients: () => Effect.Effect<Record<string, MCPClient>>
|
||||||
readonly instructions: () => Effect.Effect<ServerInstructions[]>
|
|
||||||
readonly tools: () => Effect.Effect<Record<string, Tool>>
|
readonly tools: () => Effect.Effect<Record<string, Tool>>
|
||||||
readonly prompts: () => Effect.Effect<Record<string, PromptInfo & { client: string }>>
|
readonly prompts: () => Effect.Effect<Record<string, PromptInfo & { client: string }>>
|
||||||
readonly resources: (clientName?: string) => Effect.Effect<Record<string, ResourceInfo & { client: string }>>
|
readonly resources: (clientName?: string) => Effect.Effect<Record<string, ResourceInfo & { client: string }>>
|
||||||
@@ -381,12 +383,7 @@ export const layer = Layer.effect(
|
|||||||
if (!listed) {
|
if (!listed) {
|
||||||
return yield* Effect.fail(new Error("Failed to get tools"))
|
return yield* Effect.fail(new Error("Failed to get tools"))
|
||||||
}
|
}
|
||||||
return {
|
return { mcpClient, status, defs: listed } satisfies CreateResult
|
||||||
mcpClient,
|
|
||||||
status,
|
|
||||||
defs: listed,
|
|
||||||
instructions: mcpClient.getInstructions()?.trim(),
|
|
||||||
} satisfies CreateResult
|
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Effect.catchCause((cause) =>
|
Effect.catchCause((cause) =>
|
||||||
Effect.tryPromise(() => mcpClient.close()).pipe(Effect.ignore, Effect.andThen(Effect.failCause(cause))),
|
Effect.tryPromise(() => mcpClient.close()).pipe(Effect.ignore, Effect.andThen(Effect.failCause(cause))),
|
||||||
@@ -433,7 +430,6 @@ export const layer = Layer.effect(
|
|||||||
if (s.clients[name] !== client) return
|
if (s.clients[name] !== client) return
|
||||||
delete s.clients[name]
|
delete s.clients[name]
|
||||||
delete s.defs[name]
|
delete s.defs[name]
|
||||||
delete s.instructions[name]
|
|
||||||
s.status[name] = { status: "failed", error: "Connection closed" }
|
s.status[name] = { status: "failed", error: "Connection closed" }
|
||||||
bridge.fork(
|
bridge.fork(
|
||||||
Effect.logWarning("MCP connection closed", { server: name }).pipe(
|
Effect.logWarning("MCP connection closed", { server: name }).pipe(
|
||||||
@@ -488,7 +484,6 @@ export const layer = Layer.effect(
|
|||||||
status: {},
|
status: {},
|
||||||
clients: {},
|
clients: {},
|
||||||
defs: {},
|
defs: {},
|
||||||
instructions: {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield* Effect.forEach(
|
yield* Effect.forEach(
|
||||||
@@ -510,7 +505,6 @@ export const layer = Layer.effect(
|
|||||||
if (result.mcpClient) {
|
if (result.mcpClient) {
|
||||||
s.clients[key] = result.mcpClient
|
s.clients[key] = result.mcpClient
|
||||||
s.defs[key] = result.defs!
|
s.defs[key] = result.defs!
|
||||||
if (result.instructions) s.instructions[key] = result.instructions
|
|
||||||
watch(s, key, result.mcpClient, bridge, mcp.timeout)
|
watch(s, key, result.mcpClient, bridge, mcp.timeout)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -522,7 +516,6 @@ export const layer = Layer.effect(
|
|||||||
const clients = Object.values(s.clients)
|
const clients = Object.values(s.clients)
|
||||||
s.clients = {}
|
s.clients = {}
|
||||||
s.defs = {}
|
s.defs = {}
|
||||||
s.instructions = {}
|
|
||||||
yield* Effect.forEach(
|
yield* Effect.forEach(
|
||||||
clients,
|
clients,
|
||||||
(client) =>
|
(client) =>
|
||||||
@@ -552,7 +545,6 @@ export const layer = Layer.effect(
|
|||||||
const client = s.clients[name]
|
const client = s.clients[name]
|
||||||
delete s.clients[name]
|
delete s.clients[name]
|
||||||
delete s.defs[name]
|
delete s.defs[name]
|
||||||
delete s.instructions[name]
|
|
||||||
if (!client) return Effect.void
|
if (!client) return Effect.void
|
||||||
return Effect.tryPromise(() => client.close()).pipe(Effect.ignore)
|
return Effect.tryPromise(() => client.close()).pipe(Effect.ignore)
|
||||||
}
|
}
|
||||||
@@ -562,7 +554,6 @@ export const layer = Layer.effect(
|
|||||||
name: string,
|
name: string,
|
||||||
client: MCPClient,
|
client: MCPClient,
|
||||||
listed: MCPToolDef[],
|
listed: MCPToolDef[],
|
||||||
instructions: string | undefined,
|
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
) {
|
) {
|
||||||
const bridge = yield* EffectBridge.make()
|
const bridge = yield* EffectBridge.make()
|
||||||
@@ -570,8 +561,6 @@ export const layer = Layer.effect(
|
|||||||
s.status[name] = { status: "connected" }
|
s.status[name] = { status: "connected" }
|
||||||
s.clients[name] = client
|
s.clients[name] = client
|
||||||
s.defs[name] = listed
|
s.defs[name] = listed
|
||||||
if (instructions) s.instructions[name] = instructions
|
|
||||||
else delete s.instructions[name]
|
|
||||||
watch(s, name, client, bridge, timeout)
|
watch(s, name, client, bridge, timeout)
|
||||||
if (previous) yield* Effect.tryPromise(() => previous.close()).pipe(Effect.ignore)
|
if (previous) yield* Effect.tryPromise(() => previous.close()).pipe(Effect.ignore)
|
||||||
return s.status[name]
|
return s.status[name]
|
||||||
@@ -601,18 +590,6 @@ export const layer = Layer.effect(
|
|||||||
return s.clients
|
return s.clients
|
||||||
})
|
})
|
||||||
|
|
||||||
const instructions = Effect.fn("MCP.instructions")(function* () {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
return Object.entries(s.instructions)
|
|
||||||
.filter(([name]) => s.status[name]?.status === "connected")
|
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
|
||||||
.map(([name, item]) => ({
|
|
||||||
name,
|
|
||||||
instructions: item,
|
|
||||||
tools: (s.defs[name] ?? []).map((tool) => McpCatalog.toolName(name, tool.name)),
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const createAndStore = Effect.fn("MCP.createAndStore")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
const createAndStore = Effect.fn("MCP.createAndStore")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
||||||
const s = yield* InstanceState.get(state)
|
const s = yield* InstanceState.get(state)
|
||||||
const result = yield* create(name, mcp)
|
const result = yield* create(name, mcp)
|
||||||
@@ -624,7 +601,7 @@ export const layer = Layer.effect(
|
|||||||
return result.status
|
return result.status
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield* storeClient(s, name, result.mcpClient, result.defs!, result.instructions, mcp.timeout)
|
return yield* storeClient(s, name, result.mcpClient, result.defs!, mcp.timeout)
|
||||||
})
|
})
|
||||||
|
|
||||||
const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
const add = Effect.fn("MCP.add")(function* (name: string, mcp: ConfigMCPV1.Info) {
|
||||||
@@ -670,7 +647,7 @@ export const layer = Layer.effect(
|
|||||||
}
|
}
|
||||||
const timeout = requestTimeout(s, clientName, mcpConfig, defaultTimeout)
|
const timeout = requestTimeout(s, clientName, mcpConfig, defaultTimeout)
|
||||||
for (const mcpTool of listed) {
|
for (const mcpTool of listed) {
|
||||||
const key = McpCatalog.toolName(clientName, mcpTool.name)
|
const key = McpCatalog.sanitize(clientName) + "_" + McpCatalog.sanitize(mcpTool.name)
|
||||||
result[key] = McpCatalog.convertTool(mcpTool, client, timeout)
|
result[key] = McpCatalog.convertTool(mcpTool, client, timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -878,7 +855,7 @@ export const layer = Layer.effect(
|
|||||||
|
|
||||||
const s = yield* InstanceState.get(state)
|
const s = yield* InstanceState.get(state)
|
||||||
yield* auth.clearOAuthState(mcpName)
|
yield* auth.clearOAuthState(mcpName)
|
||||||
return yield* storeClient(s, mcpName, client, listed, client.getInstructions()?.trim(), mcpConfig.timeout)
|
return yield* storeClient(s, mcpName, client, listed, mcpConfig.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbackPromise = McpOAuthCallback.waitForCallback(result.oauthState, mcpName)
|
const callbackPromise = McpOAuthCallback.waitForCallback(result.oauthState, mcpName)
|
||||||
@@ -965,7 +942,6 @@ export const layer = Layer.effect(
|
|||||||
return Service.of({
|
return Service.of({
|
||||||
status,
|
status,
|
||||||
clients,
|
clients,
|
||||||
instructions,
|
|
||||||
tools,
|
tools,
|
||||||
prompts,
|
prompts,
|
||||||
resources,
|
resources,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createConnection } from "net"
|
||||||
import { createServer } from "http"
|
import { createServer } from "http"
|
||||||
import { escapeHtml } from "@/util/html"
|
import { escapeHtml } from "@/util/html"
|
||||||
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider"
|
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider"
|
||||||
@@ -56,7 +57,6 @@ interface PendingAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let server: ReturnType<typeof createServer> | undefined
|
let server: ReturnType<typeof createServer> | undefined
|
||||||
let starting: Promise<void> | undefined
|
|
||||||
const pendingAuths = new Map<string, PendingAuth>()
|
const pendingAuths = new Map<string, PendingAuth>()
|
||||||
// Reverse index: mcpName → oauthState, so cancelPending(mcpName) can
|
// Reverse index: mcpName → oauthState, so cancelPending(mcpName) can
|
||||||
// find the right entry in pendingAuths (which is keyed by oauthState).
|
// find the right entry in pendingAuths (which is keyed by oauthState).
|
||||||
@@ -144,49 +144,31 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http").
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureRunning(redirectUri?: string): Promise<void> {
|
export async function ensureRunning(redirectUri?: string): Promise<void> {
|
||||||
if (starting) {
|
|
||||||
await starting
|
|
||||||
return ensureRunning(redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
const operation = (async () => {
|
|
||||||
// Parse the redirect URI to get port and path (uses defaults if not provided)
|
// Parse the redirect URI to get port and path (uses defaults if not provided)
|
||||||
const { port, path } = parseRedirectUri(redirectUri)
|
const { port, path } = parseRedirectUri(redirectUri)
|
||||||
|
|
||||||
// If server is running on a different port/path, stop it first
|
// If server is running on a different port/path, stop it first
|
||||||
if (server && (currentPort !== port || currentPath !== path)) await stop()
|
if (server && (currentPort !== port || currentPath !== path)) {
|
||||||
|
await stop()
|
||||||
|
}
|
||||||
|
|
||||||
if (server) return
|
if (server) return
|
||||||
|
|
||||||
const candidate = createServer(handleRequest)
|
const running = await isPortInUse(port)
|
||||||
try {
|
if (running) {
|
||||||
await new Promise<void>((resolve, reject) => {
|
return
|
||||||
candidate.once("error", reject)
|
|
||||||
candidate.listen(port, OAUTH_CALLBACK_HOST, resolve)
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
candidate.close()
|
|
||||||
if ((error as NodeJS.ErrnoException).code === "EADDRINUSE") {
|
|
||||||
throw new Error(
|
|
||||||
`OAuth callback port ${port} is already in use. ` +
|
|
||||||
`Set "oauth.callbackPort" (or "oauth.redirectUri") on the MCP server ` +
|
|
||||||
`entry in your opencode config to use a different port.`,
|
|
||||||
{ cause: error },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPort = port
|
currentPort = port
|
||||||
currentPath = path
|
currentPath = path
|
||||||
server = candidate
|
|
||||||
})()
|
|
||||||
|
|
||||||
starting = operation
|
server = createServer(handleRequest)
|
||||||
try {
|
await new Promise<void>((resolve, reject) => {
|
||||||
await operation
|
server!.listen(currentPort, OAUTH_CALLBACK_HOST, () => {
|
||||||
} finally {
|
resolve()
|
||||||
starting = undefined
|
})
|
||||||
}
|
server!.on("error", reject)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function waitForCallback(oauthState: string, mcpName?: string): Promise<string> {
|
export function waitForCallback(oauthState: string, mcpName?: string): Promise<string> {
|
||||||
@@ -219,6 +201,19 @@ export function cancelPending(mcpName: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function isPortInUse(port: number = OAUTH_CALLBACK_PORT): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = createConnection(port, "127.0.0.1")
|
||||||
|
socket.on("connect", () => {
|
||||||
|
socket.destroy()
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
socket.on("error", () => {
|
||||||
|
resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function stop(): Promise<void> {
|
export async function stop(): Promise<void> {
|
||||||
if (server) {
|
if (server) {
|
||||||
await new Promise<void>((resolve) => server!.close(() => resolve()))
|
await new Promise<void>((resolve) => server!.close(() => resolve()))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user