Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02d67a6f96 | |||
| 460cdc5aec | |||
| b0ca5520a1 | |||
| bc2e270f82 | |||
| 33705e632a | |||
| cff2345c12 | |||
| 0405518180 | |||
| c9604c86ec | |||
| 967c44552e | |||
| ec95694e2f | |||
| c176baee82 | |||
| 016d296f7c | |||
| dbaa53329c | |||
| 7843f8fb38 | |||
| 4045041554 | |||
| 7ec7413fdb | |||
| f016392368 | |||
| 140224b0fc | |||
| cfd35c9354 | |||
| 674d08f9be | |||
| 02f012f5d1 | |||
| bea6e1499d | |||
| 50a1fe49bc | |||
| 6380919fda | |||
| d972aa9d83 | |||
| d544ab4d91 | |||
| ce228bfd7c | |||
| eabf85aea2 | |||
| 2f4a688790 | |||
| 4a90ffedfb | |||
| 95cf5039be | |||
| 041215996c | |||
| d0b920e187 | |||
| 8c94e9005f | |||
| 932a40cfd9 | |||
| 716f6658db | |||
| 9f1a8f2149 | |||
| b3650e2316 | |||
| fb884bb91e | |||
| 98c09884bf | |||
| 5e47501b8b | |||
| d51ba6ed94 | |||
| a6983b65fc | |||
| 1a06198954 | |||
| 72ec09cf74 | |||
| e2bca216a2 | |||
| a10733dbf4 | |||
| b5823d1077 | |||
| e1fafa2e54 | |||
| ae49b21376 | |||
| 5710064ae1 | |||
| 4617b03ca9 | |||
| 6a91a682e4 | |||
| f8626865b9 | |||
| 2324a63fc6 | |||
| 1ce607c230 | |||
| c26f6f95f7 | |||
| d77b87ea65 | |||
| 6ca6566bd3 | |||
| 8dd993d25a | |||
| 24ab17e718 | |||
| 0a60662d71 | |||
| 194b0615e0 | |||
| a5c51e11d0 | |||
| 8f1db7d06d | |||
| b1ca070b3b | |||
| 12887e572e | |||
| 75715e2115 | |||
| a1250cd690 | |||
| 23adaaaeab | |||
| 524ee8fc03 | |||
| 0f9719a7b5 | |||
| 1b83c08b8a | |||
| 02cb350880 | |||
| 4ce830a919 | |||
| 461a1c3ab4 | |||
| 6846542115 | |||
| f928b5be07 | |||
| ecfa918760 | |||
| f80624cf17 | |||
| fe59174c23 | |||
| 2fe057324f | |||
| 19a5b5a05d | |||
| ff4cab03c1 | |||
| b2d46ecd7e | |||
| 360d85a521 | |||
| c65a7d50c1 | |||
| fa73546a86 | |||
| 935ac2db91 | |||
| 01edae4a7f | |||
| 381d67572e | |||
| e8ac44430b | |||
| 5ae93092aa | |||
| 595c6bd4a7 | |||
| 7073e8797f | |||
| f7034a35a8 | |||
| 53b93b6991 | |||
| 6067019434 | |||
| 42a3cf9645 |
@@ -34,10 +34,48 @@ jobs:
|
||||
|
||||
const now = Date.now();
|
||||
const twoHours = 2 * 60 * 60 * 1000;
|
||||
const orgMemberAssociations = new Set(['OWNER', 'MEMBER']);
|
||||
const agentLogin = 'opencode-agent[bot]';
|
||||
const { data: file } = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: '.github/TEAM_MEMBERS',
|
||||
ref: 'dev',
|
||||
});
|
||||
const teamMembers = new Set(
|
||||
Buffer.from(file.content, 'base64')
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map((line) => line.trim().toLowerCase())
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
function isExempt(item) {
|
||||
const login = item.user?.login?.toLowerCase();
|
||||
return (
|
||||
login === agentLogin ||
|
||||
orgMemberAssociations.has(item.author_association) ||
|
||||
(login && teamMembers.has(login))
|
||||
);
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const isPR = !!item.pull_request;
|
||||
const kind = isPR ? 'PR' : 'issue';
|
||||
const login = item.user?.login;
|
||||
|
||||
if (isExempt(item)) {
|
||||
core.info(`Skipping ${kind} #${item.number}; author ${login || 'unknown'} is exempt`);
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: item.number,
|
||||
name: 'needs:compliance',
|
||||
});
|
||||
} catch (e) {}
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
|
||||
@@ -17,12 +17,31 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check exempt issue author
|
||||
id: author
|
||||
run: |
|
||||
LOGIN="${{ github.event.issue.user.login }}"
|
||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
||||
|
||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
|
||||
- name: Install opencode
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Check duplicates and compliance
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -38,6 +57,7 @@ jobs:
|
||||
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
|
||||
|
||||
Issue number: ${{ github.event.issue.number }}
|
||||
Issue author association: ${{ github.event.issue.author_association }}
|
||||
|
||||
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
||||
|
||||
@@ -49,6 +69,8 @@ jobs:
|
||||
|
||||
Check whether the issue follows our contributing guidelines and issue templates.
|
||||
|
||||
If the issue author association is OWNER or MEMBER, skip this compliance check. Do not add the needs:compliance label for organization-owned issues.
|
||||
|
||||
This project has three issue templates that every issue MUST use one of:
|
||||
|
||||
1. Bug Report - requires a Description field with real content
|
||||
@@ -83,7 +105,7 @@ jobs:
|
||||
|
||||
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
|
||||
|
||||
If the issue is NOT compliant, start the comment with:
|
||||
If the issue is NOT compliant and the author association is not OWNER or MEMBER, start the comment with:
|
||||
<!-- issue-compliance -->
|
||||
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
|
||||
|
||||
@@ -129,12 +151,31 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check exempt issue author
|
||||
id: author
|
||||
run: |
|
||||
LOGIN="${{ github.event.issue.user.login }}"
|
||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
||||
|
||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
|
||||
- name: Install opencode
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Recheck compliance
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -148,9 +189,12 @@ jobs:
|
||||
}
|
||||
run: |
|
||||
opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
|
||||
Issue author association: ${{ github.event.issue.author_association }}
|
||||
|
||||
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
|
||||
|
||||
If the issue author association is OWNER or MEMBER, remove the needs:compliance label if present, delete the previous compliance comment if present, and do not post a new comment.
|
||||
|
||||
Re-check whether the issue now follows our contributing guidelines and issue templates.
|
||||
|
||||
This project has three issue templates that every issue MUST use one of:
|
||||
|
||||
@@ -16,13 +16,32 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check exempt issue author
|
||||
id: author
|
||||
run: |
|
||||
LOGIN="${{ github.event.issue.user.login }}"
|
||||
ASSOCIATION="${{ github.event.issue.author_association }}"
|
||||
|
||||
if [ "$LOGIN" = "opencode-agent[bot]" ] ||
|
||||
[ "$ASSOCIATION" = "OWNER" ] ||
|
||||
[ "$ASSOCIATION" = "MEMBER" ] ||
|
||||
grep -qxiF "$LOGIN" .github/TEAM_MEMBERS; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Skipping issue automation for exempt author: $LOGIN ($ASSOCIATION)"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install opencode
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Triage issue
|
||||
if: steps.author.outputs.skip != 'true'
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export default {
|
||||
id: "Orchestrator",
|
||||
setup: async (ctx) => {
|
||||
await ctx.agent.transform((agents) => {
|
||||
agents.update("orchestrator", (agent) => {
|
||||
agent.description = "Coordinates work by delegating implementation tasks to the minion subagent."
|
||||
agent.mode = "primary"
|
||||
agent.system = [
|
||||
"You are Orchestrator, the primary coordinating agent for this repository. You do meta work only: you coordinate, brief, and synthesize — you do not perform the work itself.",
|
||||
"Delegate ALL actual work to the minion subagent — implementation, exploration, discovery, searching the codebase, reading files to understand a problem, and even trivial one-line edits. Task size is never a reason to do it yourself, and there is no 'final integration' exception.",
|
||||
"You are not hard-banned from tools, but direct tool use is reserved for coordination overhead: a quick peek to phrase a better brief, a fast read-only check to verify a minion's reported result, or answering a question about coordination state. If a tool call is producing the answer or the artifact the user asked for, that call belongs to a minion, not you.",
|
||||
"Exploration is work. If the user asks how something works or where something lives, delegate the investigation to a minion rather than exploring yourself.",
|
||||
"Always start minion subagents in the background. Even if you have nothing else to coordinate right now, the user may assign you new work while a Minion runs, and you must stay free to receive it. Never poll; you will be notified when they finish.",
|
||||
"Give each minion a clear, self-contained brief: the goal, constraints, expected output, and any files or context already known from the user or previous minion reports.",
|
||||
"Synthesize minion results, decide next steps, and report back concisely.",
|
||||
].join("\n")
|
||||
})
|
||||
|
||||
agents.update("minion", (agent) => {
|
||||
agent.description = "Subagent that executes focused tasks delegated by Orchestrator."
|
||||
agent.mode = "subagent"
|
||||
agent.model = { providerID: "opencode", id: "glm-5.2" }
|
||||
agent.system = [
|
||||
"You are minion, a focused execution subagent for this repository.",
|
||||
"Complete the specific task delegated to you by Orchestrator using the available tools.",
|
||||
"Inspect the codebase before making assumptions, make targeted changes when requested, and verify your work when feasible.",
|
||||
"Follow the repository's AGENTS.md conventions: respect the style guide, run `bun typecheck` from the affected package directory after code changes, never run tests from the repo root, and do not modify packages/opencode unless the task explicitly says V1 work.",
|
||||
"If the task is ambiguous or you hit a blocker, stop and report your findings instead of guessing.",
|
||||
"Keep your final response concise: summarize what you did, list important files changed or findings, and call out blockers or verification gaps.",
|
||||
"Do not delegate to other subagents; execute the assigned work yourself.",
|
||||
].join("\n")
|
||||
agent.permissions.push({ action: "subagent", resource: "*", effect: "deny" })
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
name: debug-opencode
|
||||
description: Use when interactively running, debugging, or verifying opencode's own V2 CLI/TUI or server during development in this repo — starting the dev TUI, driving it with termctrl, comparing V2 against the legacy TUI, hitting the V2 server/API directly, reading log files, or attaching Bun's inspector.
|
||||
---
|
||||
|
||||
# Debugging opencode itself
|
||||
|
||||
Workflow for interactively exercising the V2 CLI/TUI and server while developing in this repo. All commands below run from `packages/cli` unless noted otherwise.
|
||||
|
||||
## Migration context
|
||||
|
||||
- The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state.
|
||||
- Preserve established TUI behavior unless the task intentionally changes it. When behavior, copy, keyboard interaction, or layout is unclear, compare the local V2 TUI with the latest released legacy TUI (see "Comparing V2 against the legacy TUI" below) rather than guessing.
|
||||
|
||||
## Server/client model
|
||||
|
||||
opencode V2 is a client/server system, not a single monolithic process:
|
||||
|
||||
- **Server process** runs the Effect HTTP API (`packages/server`) and owns all domain state: sessions, database, plugins, permissions, Location services. It's started by the `serve` command (`packages/cli/src/commands/handlers/serve.ts`).
|
||||
- **TUI process** is a separate process that runs no application logic itself — it's an HTTP/SSE client of the server via the generated SDK (`createOpencodeClient` / `sdk.client.v2`).
|
||||
- **Discovery**: CLI processes find the shared server through a JSON registration file at `~/.local/state/opencode/service.json` (or `service-local.json` for the local/dev channel) containing `{id, version, url, pid}`. A separate password file under `~/.config/opencode/service.json` provides HTTP Basic auth. Before reusing a registration, the client calls `GET /health` to confirm the server is alive, authenticated, and version-compatible.
|
||||
- **Sharing**: because of this registration/health-check dance, many concurrent `opencode`/TUI invocations converge on one shared background daemon rather than each spawning their own. If no compatible healthy daemon is found, a new one is spawned detached (`serve --service`) and registers itself.
|
||||
- **`bun dev service start|status|stop|restart`** manages this shared background daemon's lifecycle directly — useful when you need to force a fresh server, confirm one is running, or kill a stuck one.
|
||||
- **Standalone mode** (`--standalone`) opts a single invocation out of the shared daemon: it spawns a private one-off `serve --stdio --port 0` child tied to that invocation's lifetime, with its own random password. Use this to isolate a debugging session from your other running opencode sessions.
|
||||
- Every log line is tagged `role=server` or `role=cli` and a per-process `run=<id>`, so you can distinguish server-side and client-side activity in one shared log file (see "Logs" below) even when both roles are interleaved from concurrent processes.
|
||||
|
||||
## Starting the dev TUI
|
||||
|
||||
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
|
||||
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
|
||||
|
||||
## Interactive debugging with termctrl
|
||||
|
||||
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
|
||||
- Use a dedicated session name and do not reuse or kill an unrelated session.
|
||||
|
||||
```bash
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
|
||||
termctrl show opencode-v2-dev
|
||||
```
|
||||
|
||||
- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`.
|
||||
- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input.
|
||||
|
||||
```bash
|
||||
termctrl send opencode-v2-dev 'text:example prompt' enter
|
||||
termctrl send opencode-v2-dev ctrl-c
|
||||
```
|
||||
|
||||
- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits.
|
||||
- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed.
|
||||
|
||||
```bash
|
||||
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png
|
||||
```
|
||||
|
||||
- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again:
|
||||
|
||||
```bash
|
||||
termctrl resize opencode-v2-dev --cols 100 --rows 30
|
||||
termctrl show opencode-v2-dev
|
||||
```
|
||||
|
||||
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
|
||||
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
||||
- Always clean up the Terminal Control session when the check is complete:
|
||||
|
||||
```bash
|
||||
termctrl stop opencode-v2-dev
|
||||
```
|
||||
|
||||
## Comparing V2 against the legacy TUI
|
||||
|
||||
Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states:
|
||||
|
||||
```bash
|
||||
# From packages/cli: local V2 TUI
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||
|
||||
# Released legacy TUI behavior reference
|
||||
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
|
||||
|
||||
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png
|
||||
termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
|
||||
```
|
||||
|
||||
- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints.
|
||||
- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`.
|
||||
|
||||
## Server/API debugging
|
||||
|
||||
- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI.
|
||||
- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering.
|
||||
- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path:
|
||||
|
||||
```bash
|
||||
bun dev api get /health
|
||||
bun dev api get /openapi.json
|
||||
bun dev api <operationId> --param key=value
|
||||
```
|
||||
|
||||
- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`.
|
||||
- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control.
|
||||
- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters.
|
||||
|
||||
## Logs
|
||||
|
||||
- Log files live under `~/.local/share/opencode/log/`. In a local/dev checkout the active file is `opencode-local.log`; `opencode.log` is used for non-local (released) channel installs. Both are append-only, shared across every CLI and server process on the machine.
|
||||
- Each line is structured `key=value` text: `timestamp`, `level`, `run=<id>` (per-process run ID), `message`, and a `role=cli` or `role=server` tag. Use `run=` to isolate one process's activity and `role=` to separate client-side from server-side log lines, since a shared daemon interleaves many processes' output in one file.
|
||||
- Tail the live file while reproducing an issue instead of guessing from stale output:
|
||||
|
||||
```bash
|
||||
tail -f ~/.local/share/opencode/log/opencode-local.log
|
||||
```
|
||||
|
||||
- Filter to one run or role when the file is noisy:
|
||||
|
||||
```bash
|
||||
grep 'run=8fc3b1d5' ~/.local/share/opencode/log/opencode-local.log
|
||||
grep 'role=server' ~/.local/share/opencode/log/opencode-local.log
|
||||
```
|
||||
|
||||
- `OPENCODE_LOG_LEVEL` controls verbosity (default `INFO`); set it before starting `bun dev` or `serve` to get `DEBUG` output for a specific repro.
|
||||
- `OPENCODE_PRINT_LOGS=1` additionally tees log output to stderr of the process that emitted it, which is useful when a process fails before you'd think to check the shared log file.
|
||||
- `termctrl logs <session>` surfaces stdout/stderr for a Terminal Control session specifically (e.g. inspector output or startup failures before the TUI renderer starts) — use the log file above for anything emitted by a separate server/daemon process instead.
|
||||
|
||||
## Debugger
|
||||
|
||||
- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL:
|
||||
|
||||
```bash
|
||||
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
|
||||
bun run --inspect=ws://localhost:6499/ src/index.ts
|
||||
```
|
||||
|
||||
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
|
||||
- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI.
|
||||
|
||||
## Verification
|
||||
|
||||
- Run `bun typecheck` from `packages/cli` after CLI adapter changes.
|
||||
- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root.
|
||||
- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: sample-skill
|
||||
description: Use when the user says sample skill, skill demo, or asks how an opencode SKILL.md should be structured; demonstrates a tiny project-local skill with practical assistant workflow guidance.
|
||||
---
|
||||
|
||||
# Sample Skill
|
||||
|
||||
This is a minimal project-local opencode skill. It exists as a reference for how a skill is structured and as a tiny reusable workflow the assistant can load when the user asks for a skill example.
|
||||
|
||||
## When To Use
|
||||
|
||||
- Use when the user asks for a sample skill or skill template.
|
||||
- Use when demonstrating the required `SKILL.md` frontmatter and body format.
|
||||
- Do not use for unrelated coding tasks just because a skill exists.
|
||||
|
||||
## Workflow
|
||||
|
||||
- Confirm the specific outcome the user wants if the request is ambiguous.
|
||||
- Inspect the relevant files before changing anything.
|
||||
- Make the smallest correct change.
|
||||
- Verify the result with a focused read, typecheck, test, or other lightweight check when available.
|
||||
- Summarize the changed files and any required restart or reload step.
|
||||
|
||||
## Example Response Style
|
||||
|
||||
When this skill is relevant, keep responses direct and actionable:
|
||||
|
||||
```text
|
||||
I created a project-local skill at .opencode/skills/sample-skill/SKILL.md.
|
||||
Restart opencode for the new skill to be discovered by future sessions.
|
||||
```
|
||||
@@ -1,6 +1,7 @@
|
||||
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
|
||||
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
|
||||
- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
|
||||
- The default branch in this repo is `dev`.
|
||||
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
|
||||
|
||||
@@ -158,4 +159,5 @@ const table = sqliteTable("session", {
|
||||
- Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.
|
||||
- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
|
||||
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
|
||||
- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
|
||||
- Keep the System Context algebra and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Checkpoint persistence Session-owned. The runner composes all context producers explicitly in `loadSystemContext`; there is no context registry.
|
||||
- The durable Applied record is what the model was last told, per source. Reconcile narrates drift as chronological System updates and never rewrites the baseline; only completed compaction rebaselines, and move or committed revert resets the checkpoint. Unavailable sources keep the model's prior belief, blocking only a session's first baseline.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
@@ -92,12 +92,13 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"bin": {
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
},
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
@@ -143,7 +144,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -179,7 +180,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -206,7 +207,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -228,7 +229,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -252,7 +253,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -272,7 +273,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -303,6 +304,7 @@
|
||||
"@effect/sql-sqlite-bun": "catalog:",
|
||||
"@ff-labs/fff-bun": "0.9.4",
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@npmcli/config": "10.8.1",
|
||||
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
||||
@@ -324,7 +326,7 @@
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "6.9.3",
|
||||
"gitlab-ai-provider": "6.10.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
@@ -337,7 +339,7 @@
|
||||
"npm-package-arg": "13.0.2",
|
||||
"semver": "^7.6.3",
|
||||
"turndown": "7.2.0",
|
||||
"venice-ai-sdk-provider": "2.0.2",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
"which": "6.0.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"zod": "catalog:",
|
||||
@@ -366,7 +368,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
@@ -420,7 +422,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -434,7 +436,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -446,7 +448,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -478,7 +480,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -494,7 +496,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -525,7 +527,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -544,7 +546,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -581,6 +583,7 @@
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -616,7 +619,7 @@
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "6.9.3",
|
||||
"gitlab-ai-provider": "6.10.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
@@ -640,7 +643,7 @@
|
||||
"tree-sitter-powershell": "0.25.10",
|
||||
"turndown": "7.2.0",
|
||||
"ulid": "catalog:",
|
||||
"venice-ai-sdk-provider": "2.0.2",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"web-tree-sitter": "0.25.10",
|
||||
"ws": "8.21.0",
|
||||
@@ -674,9 +677,12 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
"zod": "catalog:",
|
||||
@@ -685,6 +691,7 @@
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/keymap": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
@@ -703,6 +710,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode-ai/protocol",
|
||||
"version": "1.17.11",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -711,10 +719,12 @@
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode-ai/schema",
|
||||
"version": "1.17.11",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -722,6 +732,7 @@
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/script": {
|
||||
@@ -739,6 +750,7 @@
|
||||
"dependencies": {
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -750,7 +762,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -765,7 +777,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -780,7 +792,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -824,7 +836,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -837,7 +849,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@opencode-ai/stats-core": "workspace:*",
|
||||
@@ -870,7 +882,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -889,7 +901,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -930,8 +942,9 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -958,7 +971,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1009,7 +1022,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -3011,7 +3024,7 @@
|
||||
|
||||
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
|
||||
|
||||
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
@@ -3181,7 +3194,7 @@
|
||||
|
||||
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
||||
|
||||
"body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
|
||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="],
|
||||
|
||||
@@ -3349,7 +3362,7 @@
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
||||
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
@@ -3359,7 +3372,7 @@
|
||||
|
||||
"cookie-es": ["cookie-es@2.0.1", "", {}, "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
|
||||
@@ -3667,7 +3680,7 @@
|
||||
|
||||
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
|
||||
|
||||
"express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
|
||||
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
|
||||
|
||||
@@ -3725,7 +3738,7 @@
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
|
||||
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
|
||||
|
||||
@@ -3763,7 +3776,7 @@
|
||||
|
||||
"framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="],
|
||||
|
||||
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
||||
|
||||
@@ -3815,7 +3828,7 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"gitlab-ai-provider": ["gitlab-ai-provider@6.9.3", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-lWo6b6es5+k9iXaDIvE9ECzyK4zfEza4+dQ5FN8SJpEuVRi3ZBCpHIOTa32QoYEDCBaiPh+tcyca86PfNodmlg=="],
|
||||
"gitlab-ai-provider": ["gitlab-ai-provider@6.10.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-oWEZ06rDO6JjB7INHO882wyBAQqCZVHiDHwCs5M+VPmdDj8TzhGXcYesA2CcV5RoI5lfHLKwGp5uKFB62VWpqw=="],
|
||||
|
||||
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
|
||||
|
||||
@@ -4327,11 +4340,11 @@
|
||||
|
||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||
|
||||
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
@@ -4817,7 +4830,7 @@
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
|
||||
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
|
||||
@@ -5003,7 +5016,7 @@
|
||||
|
||||
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
|
||||
|
||||
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
|
||||
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
|
||||
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
|
||||
|
||||
@@ -5013,7 +5026,7 @@
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="],
|
||||
|
||||
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
|
||||
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
@@ -5329,7 +5342,7 @@
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
"type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
|
||||
@@ -5433,7 +5446,7 @@
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.0.2", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.47", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.27" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-aoa05nI3BTK5aGbjBflq+Gfln2AHAkwNbWuGGvCzUIsOfp5Y3iPD4O4PUGDAEiWVJWbjpPn0KfDa0H/HebwsaA=="],
|
||||
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.1.1", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.51", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.30" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-w3OHkuzzKZ3r2TOxER6myBYzZJNoDqol+DUHu3NnfBN/GETnUVxecZJab0CHQQ8GZc0jjzpFymepjcLDPS4SQg=="],
|
||||
|
||||
"verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
|
||||
|
||||
@@ -5907,14 +5920,10 @@
|
||||
|
||||
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||
@@ -5989,6 +5998,8 @@
|
||||
|
||||
"@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
|
||||
"@opencode-ai/core/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
|
||||
|
||||
"@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
@@ -5999,6 +6010,8 @@
|
||||
|
||||
"@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/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -6047,8 +6060,12 @@
|
||||
|
||||
"@shikijs/themes/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
|
||||
|
||||
"@slack/bolt/express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
|
||||
|
||||
"@slack/bolt/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
"@slack/bolt/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
|
||||
|
||||
"@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
|
||||
|
||||
"@slack/socket-mode/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
|
||||
@@ -6115,10 +6132,6 @@
|
||||
|
||||
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
|
||||
|
||||
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
|
||||
@@ -6173,10 +6186,6 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"builder-util/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
@@ -6255,16 +6264,10 @@
|
||||
|
||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
|
||||
|
||||
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
|
||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||
@@ -6347,6 +6350,8 @@
|
||||
|
||||
"opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
|
||||
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
@@ -6387,8 +6392,6 @@
|
||||
|
||||
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
|
||||
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
@@ -6397,10 +6400,6 @@
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
@@ -6445,7 +6444,7 @@
|
||||
|
||||
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
|
||||
|
||||
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
@@ -6455,11 +6454,11 @@
|
||||
|
||||
"unzipper/fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg=="],
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.32" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SoPSkrL5cbNQnAljRsJ7pOzJ2FmWgnhC0lfFOda873ycCdFJL1A+h3Ib7mX2spcv3XnNaO13y/45/0RyqNWlIQ=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.32", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Kwj499fTcN9bP/AfGoPU7JWIXeP6VZqKI6omsH062c9E2G4gdjeJczkz4z/tYSkzYjLE2AI3DtZbMfs6D7vn2Q=="],
|
||||
|
||||
"verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
|
||||
|
||||
@@ -6715,28 +6714,6 @@
|
||||
|
||||
"@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
|
||||
|
||||
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||
|
||||
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
|
||||
@@ -6859,6 +6836,34 @@
|
||||
|
||||
"@shikijs/stream/@shikijs/core/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
|
||||
|
||||
"@slack/bolt/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"@slack/bolt/express/body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
|
||||
|
||||
"@slack/bolt/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
||||
|
||||
"@slack/bolt/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"@slack/bolt/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
|
||||
|
||||
"@slack/bolt/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"@slack/bolt/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
|
||||
|
||||
"@slack/bolt/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
|
||||
"@slack/bolt/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
|
||||
|
||||
"@slack/bolt/express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
|
||||
|
||||
"@slack/bolt/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
|
||||
|
||||
"@slack/bolt/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
|
||||
|
||||
"@slack/bolt/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
|
||||
"@slack/bolt/raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
@@ -6889,8 +6894,6 @@
|
||||
|
||||
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
@@ -6955,8 +6958,6 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
@@ -7015,12 +7016,8 @@
|
||||
|
||||
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
|
||||
|
||||
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
|
||||
@@ -7057,8 +7054,6 @@
|
||||
|
||||
"rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -7073,12 +7068,14 @@
|
||||
|
||||
"tw-to-css/tailwindcss/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
|
||||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"unzipper/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
@@ -7263,10 +7260,6 @@
|
||||
|
||||
"@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
@@ -7299,6 +7292,20 @@
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"@slack/bolt/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/bolt/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"@slack/bolt/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"@slack/bolt/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"@slack/bolt/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"@slack/bolt/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||
|
||||
"@slack/bolt/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
@@ -7427,6 +7434,10 @@
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@slack/bolt/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@slack/bolt/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-JXQ9PAqqRlJtHa8T3ZxqdRRyxC+0ip+2wSnehvKXUbI=",
|
||||
"aarch64-linux": "sha256-nI+RaxDXmAcjhSjCtIvyi292xBEg0E5NXaoyGrJE69s=",
|
||||
"aarch64-darwin": "sha256-UleKpm7Khf3kibm4BqZJ9bmu0N2kOjNd77g1Bd2tmfw=",
|
||||
"x86_64-darwin": "sha256-V4AQH868dOfVhEj1mKTnZlhpZwFqBYsHS28Tx9VXHkE="
|
||||
"x86_64-linux": "sha256-zjYaBa+TKuHVBO0z0YqOOFLOxpxj2hB6Iq1o+BXBeVc=",
|
||||
"aarch64-linux": "sha256-S2QDDoKcg8QCqjxCnMmLp/3xmiVHAbCJ0CuHQRjDBbE=",
|
||||
"aarch64-darwin": "sha256-GtdfHbk4fhvosgo6iEjXrWSxteK+eNrT0P9swEccbew=",
|
||||
"x86_64-darwin": "sha256-zmWFdPSTS/DpU40CgHA/KKiSIs0PlI092TzLv/cG0jE="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function installStressSessionTabs(page: Page, input?: { draftID?: s
|
||||
}),
|
||||
)
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify([
|
||||
...sessionIDs.map((sessionId) => ({
|
||||
type: "session",
|
||||
|
||||
@@ -14,7 +14,7 @@ test("closing the active server's last tab opens the remaining server tab", asyn
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify([
|
||||
{ type: "session", server: "http://127.0.0.1:4096", sessionId: sessionA },
|
||||
{ type: "session", server: serverB, sessionId: sessionB },
|
||||
@@ -52,7 +52,7 @@ test("legacy session routes preserve an existing tab's server", async ({ page })
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify([{ type: "session", server: serverB, sessionId: sessionB }]),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ test("shows the V2 thinking level control while relevant", async ({ page }) => {
|
||||
await expect(control).toBeVisible()
|
||||
|
||||
await control.locator('[data-action="prompt-model-variant"]').click()
|
||||
const high = page.getByRole("option", { name: "high" })
|
||||
const high = page.getByRole("menuitemradio", { name: "high" })
|
||||
await expect(high).toBeVisible()
|
||||
await page.mouse.move(0, 0)
|
||||
await expect(control).toBeVisible()
|
||||
|
||||
@@ -38,6 +38,29 @@ test("shows a pending question dock", async ({ page }) => {
|
||||
await expect(question.getByRole("radio", { name: /Extended/ })).toBeVisible()
|
||||
await expect(page.locator('[data-component="session-composer"]')).toHaveCount(0)
|
||||
|
||||
const rejectRequests: string[] = []
|
||||
page.on("request", (request) => {
|
||||
if (request.method() !== "POST") return
|
||||
if (new URL(request.url()).pathname === "/question/question-request/reject") rejectRequests.push(request.url())
|
||||
})
|
||||
|
||||
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
||||
await expect(question).toBeVisible()
|
||||
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||
await expect(question.getByText("Select one answer")).toBeHidden()
|
||||
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeHidden()
|
||||
await expect(question.getByRole("radio", { name: /Extended/ })).toBeHidden()
|
||||
await expect(question.getByRole("button", { name: "Dismiss" })).toBeVisible()
|
||||
await expect(question.getByRole("button", { name: "Submit" })).toBeVisible()
|
||||
await expect(page.locator('[data-component="question-minimized-dock"]')).toHaveCount(0)
|
||||
expect(rejectRequests).toEqual([])
|
||||
|
||||
await question.locator('[data-component="icon-button"][data-icon="chevron-down"]').click()
|
||||
await expect(question).toBeVisible()
|
||||
await expect(question.getByText("Which implementation should be used?")).toBeVisible()
|
||||
await expect(question.getByRole("radio", { name: /Minimal/ })).toBeVisible()
|
||||
expect(rejectRequests).toEqual([])
|
||||
|
||||
await question.getByRole("radio", { name: /Minimal/ }).click()
|
||||
const reply = page.waitForRequest(
|
||||
(request) => request.method() === "POST" && new URL(request.url()).pathname === "/question/question-request/reply",
|
||||
|
||||
@@ -145,7 +145,7 @@ async function configurePage(page: Page) {
|
||||
}),
|
||||
)
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify(sessionIDs.map((sessionId) => ({ type: "session", server, dirBase64, sessionId }))),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -127,7 +127,7 @@ test.describe("smoke: session timeline", () => {
|
||||
await page.addInitScript(
|
||||
({ dirBase64, sourceID, targetID }) => {
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify(
|
||||
[sourceID, targetID].map((sessionId) => ({
|
||||
type: "session",
|
||||
@@ -253,7 +253,7 @@ test.describe("smoke: session timeline", () => {
|
||||
await page.addInitScript(
|
||||
({ dirBase64, sourceID, targetID }) => {
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:tabs",
|
||||
"opencode.window.browser.dat:tabs",
|
||||
JSON.stringify(
|
||||
[sourceID, targetID].map((sessionId) => ({
|
||||
type: "session",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
+36
-110
@@ -28,22 +28,22 @@ import {
|
||||
Show,
|
||||
} from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { CommandProvider } from "@/context/command"
|
||||
import { CommandProvider, useCommand, type CommandOption } from "@/context/command"
|
||||
import { CommentsProvider } from "@/context/comments"
|
||||
import { FileProvider } from "@/context/file"
|
||||
import { ServerSDKProvider, useServerSDK } from "@/context/server-sdk"
|
||||
import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
|
||||
import { ServerSDKProvider } from "@/context/server-sdk"
|
||||
import { ServerSyncProvider } from "@/context/server-sync"
|
||||
import { GlobalProvider, useGlobal } from "@/context/global"
|
||||
import { HighlightsProvider } from "@/context/highlights"
|
||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
||||
import { LayoutProvider } from "@/context/layout"
|
||||
import { ModelsProvider } from "@/context/models"
|
||||
import { NotificationProvider, useNotification } from "@/context/notification"
|
||||
import { NotificationProvider } from "@/context/notification"
|
||||
import { PermissionProvider } from "@/context/permission"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { PromptProvider } from "@/context/prompt"
|
||||
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
|
||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||
import { TerminalProvider } from "@/context/terminal"
|
||||
import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs"
|
||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
||||
import { WslServersProvider } from "@/wsl/context"
|
||||
@@ -52,16 +52,9 @@ import LegacyLayout from "@/pages/layout"
|
||||
import NewLayout from "@/pages/layout-new"
|
||||
import { ErrorPage } from "./pages/error"
|
||||
import { useCheckServerHealth } from "./utils/server-health"
|
||||
import {
|
||||
legacySessionHref,
|
||||
legacySessionServer,
|
||||
requireServerKey,
|
||||
selectSessionLineage,
|
||||
sessionHref,
|
||||
} from "./utils/session-route"
|
||||
import { isSessionNotFoundError } from "./utils/server-errors"
|
||||
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
||||
|
||||
import Session from "@/pages/session"
|
||||
import { SessionPage, TargetSessionRoute as TargetSessionRouteContent } from "@/pages/session"
|
||||
import { NewHome, LegacyHome } from "@/pages/home"
|
||||
|
||||
const NewSession = lazy(() => import("@/pages/new-session"))
|
||||
@@ -95,11 +88,7 @@ const SessionRoute = () => {
|
||||
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
||||
})
|
||||
|
||||
return (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
)
|
||||
return <SessionPage />
|
||||
}
|
||||
|
||||
const TargetSessionRoute = () => {
|
||||
@@ -114,76 +103,13 @@ const TargetSessionRoute = () => {
|
||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
||||
<ServerSDKProvider server={conn}>
|
||||
<ServerSyncProvider server={conn}>
|
||||
<ResolvedTargetSessionRoute />
|
||||
<TargetSessionRouteContent />
|
||||
</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function ResolvedTargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
const settings = useSettings()
|
||||
const tabs = useTabs()
|
||||
const sync = useServerSync()
|
||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||
const cached = createMemo(() => sync().session.lineage.peek(params.id))
|
||||
const [resolved] = createResource(
|
||||
() => {
|
||||
if (cached()) return
|
||||
return { id: params.id, server: serverKey(), sync: sync() }
|
||||
},
|
||||
({ id, server, sync }) =>
|
||||
sync.session.lineage.resolve(id).catch((error) => {
|
||||
if (isSessionNotFoundError(error, id)) tabs.removeSessionTab({ server, sessionId: id })
|
||||
throw error
|
||||
}),
|
||||
)
|
||||
const current = createMemo(() => selectSessionLineage(params.id, cached(), resolved()))
|
||||
const directory = createMemo(() => current()?.session.directory)
|
||||
const targetDirectory = () => directory()!
|
||||
|
||||
createEffect(() => {
|
||||
const session = current()
|
||||
if (!session) return
|
||||
tabs.addSessionTab({
|
||||
server: serverKey(),
|
||||
sessionId: session.root.id,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<TargetServerScopedProviders directory={directory} sessionID={() => params.id}>
|
||||
<Show when={!!current() || resolved.state !== "errored"} fallback={<ErrorPage error={resolved.error} />}>
|
||||
<Show when={directory()}>
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
||||
>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
</TargetServerScopedProviders>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetSessionPage() {
|
||||
const sdk = useSDK()
|
||||
const serverSDK = useServerSDK()
|
||||
return (
|
||||
<Show when={`${serverSDK().scope}\0${sdk().directory}`} keyed>
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
</SessionProviders>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
||||
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
||||
// Notification/Models + the visual Layout) for that server.
|
||||
@@ -231,7 +157,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) {
|
||||
<Show when={`${props.draft.server}\0${props.draft.directory}`} keyed>
|
||||
<ServerSDKProvider server={conn}>
|
||||
<ServerSyncProvider server={conn}>
|
||||
<TargetServerScopedProviders directory={directory}>
|
||||
<DraftServerScopedProviders directory={directory}>
|
||||
<SDKProvider directory={directory}>
|
||||
<DirectoryDataProvider directory={directory} server={serverKey}>
|
||||
<DraftProviders>
|
||||
@@ -239,7 +165,7 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) {
|
||||
</DraftProviders>
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</TargetServerScopedProviders>
|
||||
</DraftServerScopedProviders>
|
||||
</ServerSyncProvider>
|
||||
</ServerSDKProvider>
|
||||
</Show>
|
||||
@@ -300,12 +226,36 @@ function SharedProviders(props: ParentProps) {
|
||||
<>
|
||||
<BodyDesignClass />
|
||||
<CommandProvider>
|
||||
<DesktopCommands />
|
||||
<HighlightsProvider>{props.children}</HighlightsProvider>
|
||||
</CommandProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function DesktopCommands() {
|
||||
const command = useCommand()
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
command.register("desktop", () => {
|
||||
const commands: CommandOption[] = []
|
||||
if (platform.platform === "desktop" && platform.exportDebugLogs) {
|
||||
commands.push({
|
||||
id: "logs.export",
|
||||
title: "Export logs",
|
||||
category: language.t("command.category.settings"),
|
||||
onSelect: () => {
|
||||
void platform.exportDebugLogs?.()
|
||||
},
|
||||
})
|
||||
}
|
||||
return commands
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Server-scoped providers shared by the legacy shell and the top-level new shell.
|
||||
type ServerScopedShellProps = ParentProps<{
|
||||
directory?: () => string | undefined
|
||||
@@ -340,38 +290,14 @@ function NewAppLayout(props: ParentProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function TargetServerScopedProviders(props: ServerScopedShellProps) {
|
||||
function DraftServerScopedProviders(props: ParentProps<{ directory?: () => string | undefined }>) {
|
||||
return (
|
||||
<PermissionProvider directory={props.directory}>
|
||||
<MarkSessionNotificationsViewed sessionID={props.sessionID} />
|
||||
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
|
||||
</PermissionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) {
|
||||
const notification = useNotification()
|
||||
createEffect(() => {
|
||||
const sessionID = props.sessionID?.()
|
||||
if (!notification.ready() || !sessionID) return
|
||||
if (notification.session.unseenCount(sessionID) === 0) return
|
||||
notification.session.markViewed(sessionID)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
function SessionProviders(props: ParentProps) {
|
||||
return (
|
||||
<TerminalProvider>
|
||||
<FileProvider>
|
||||
<PromptProvider>
|
||||
<CommentsProvider>{props.children}</CommentsProvider>
|
||||
</PromptProvider>
|
||||
</FileProvider>
|
||||
</TerminalProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// The draft page only renders the prompt composer, so it drops TerminalProvider.
|
||||
// FileProvider and CommentsProvider stay because PromptInput uses file search and comment context.
|
||||
function DraftProviders(props: ParentProps) {
|
||||
|
||||
@@ -3,13 +3,26 @@ import { List } from "@opencode-ai/ui/list"
|
||||
import { Switch } from "@opencode-ai/ui/switch"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import type { Component } from "solid-js"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { Dialog as DialogV2, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
import { Switch as SwitchV2 } from "@opencode-ai/ui/v2/switch-v2"
|
||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { For, Show, type Component } from "solid-js"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { popularProviders } from "@/hooks/use-providers"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { SettingsListV2 } from "./settings-v2/parts/list"
|
||||
import { SettingsRowV2 } from "./settings-v2/parts/row"
|
||||
import "./settings-v2/settings-v2.css"
|
||||
|
||||
type ModelItem = ReturnType<ReturnType<typeof useLocal>["model"]["list"]>[number]
|
||||
|
||||
export const DialogManageModels: Component = () => {
|
||||
const local = useLocal()
|
||||
@@ -102,3 +115,151 @@ export const DialogManageModels: Component = () => {
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export const DialogManageModelsV2: Component = () => {
|
||||
const local = useLocal()
|
||||
const language = useLanguage()
|
||||
const dialog = useDialog()
|
||||
const directory = () => decode64(local.slug())
|
||||
|
||||
const handleConnectProvider = () => {
|
||||
dialog.show(() => <DialogSelectProvider directory={directory} />)
|
||||
}
|
||||
const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID)
|
||||
const providerVisible = (providerID: string) =>
|
||||
providerList(providerID).every((x) => local.model.visible({ modelID: x.id, providerID: x.provider.id }))
|
||||
const setProviderVisibility = (providerID: string, checked: boolean) => {
|
||||
providerList(providerID).forEach((x) => {
|
||||
local.model.setVisibility({ modelID: x.id, providerID: x.provider.id }, checked)
|
||||
})
|
||||
}
|
||||
const setModelVisibility = (item: ModelItem, checked: boolean) => {
|
||||
local.model.setVisibility({ modelID: item.id, providerID: item.provider.id }, checked)
|
||||
}
|
||||
const list = useFilteredList<ModelItem>({
|
||||
items: () => local.model.list(),
|
||||
key: (x) => `${x.provider.id}:${x.id}`,
|
||||
filterKeys: ["provider.name", "name", "id"],
|
||||
sortBy: (a, b) => a.name.localeCompare(b.name),
|
||||
groupBy: (x) => x.provider.id,
|
||||
sortGroupsBy: (a, b) => {
|
||||
const aRank = popularProviders.indexOf(a.category)
|
||||
const bRank = popularProviders.indexOf(b.category)
|
||||
const aPopular = aRank >= 0
|
||||
const bPopular = bRank >= 0
|
||||
if (aPopular && !bPopular) return -1
|
||||
if (!aPopular && bPopular) return 1
|
||||
return aRank - bRank
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogV2 size="large" variant="settings" class="settings-v2-manage-models-dialog">
|
||||
<DialogHeader hideClose={true} closeLabel={language.t("common.close")}>
|
||||
<DialogTitleGroup
|
||||
title={language.t("dialog.model.manage")}
|
||||
description={language.t("dialog.model.manage.description")}
|
||||
/>
|
||||
<ButtonV2 variant="neutral" icon="plus" onClick={handleConnectProvider}>
|
||||
{language.t("command.provider.connect")}
|
||||
</ButtonV2>
|
||||
</DialogHeader>
|
||||
<DialogBody class="flex min-h-0 flex-1 flex-col">
|
||||
<div class="px-4 pt-px pb-3">
|
||||
<div class="relative">
|
||||
<TextInputV2
|
||||
type="search"
|
||||
appearance="base"
|
||||
class="!w-full self-stretch"
|
||||
value={list.filter()}
|
||||
onInput={(event) => list.onInput(event.currentTarget.value)}
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
autofocus
|
||||
aria-label={language.t("dialog.model.search.placeholder")}
|
||||
/>
|
||||
<Show when={list.filter()}>
|
||||
<IconButtonV2
|
||||
type="button"
|
||||
variant="ghost-muted"
|
||||
size="small"
|
||||
class="settings-v2-tab-search-clear"
|
||||
icon={<IconV2 name="close" size="large" class="text-v2-icon-icon-muted" />}
|
||||
onClick={() => list.clear()}
|
||||
aria-label={language.t("common.clear")}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="manage-models-scroll" class="relative min-h-0 flex-1">
|
||||
<div class="settings-v2-panel settings-v2-models h-full px-4 pt-4 pb-4">
|
||||
<Show
|
||||
when={!list.grouped.loading}
|
||||
fallback={
|
||||
<div class="settings-v2-models-status">
|
||||
{language.t("common.loading")}
|
||||
{language.t("common.loading.ellipsis")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={list.flat().length > 0}
|
||||
fallback={
|
||||
<div class="settings-v2-models-status">
|
||||
<span>{language.t("dialog.model.empty")}</span>
|
||||
<Show when={list.filter()}>
|
||||
<span class="settings-v2-models-status-filter">"{list.filter()}"</span>
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={list.grouped.latest}>
|
||||
{(group) => (
|
||||
<div class="settings-v2-section" data-component="settings-models-provider">
|
||||
<div class="settings-v2-models-group-header justify-between">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<ProviderIcon id={group.category} width={16} height={16} class="ml-4 shrink-0" />
|
||||
<h3 class="settings-v2-section-title">{group.items[0].provider.name}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<SwitchV2
|
||||
class="mr-6"
|
||||
checked={providerVisible(group.category)}
|
||||
onChange={(checked) => setProviderVisibility(group.category, checked)}
|
||||
hideLabel
|
||||
>
|
||||
{group.items[0].provider.name}
|
||||
</SwitchV2>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsListV2>
|
||||
<For each={group.items}>
|
||||
{(item) => (
|
||||
<SettingsRowV2 title={item.name} description="">
|
||||
<div>
|
||||
<SwitchV2
|
||||
checked={local.model.visible({ modelID: item.id, providerID: item.provider.id })}
|
||||
onChange={(checked) => setModelVisibility(item, checked)}
|
||||
hideLabel
|
||||
>
|
||||
{item.name}
|
||||
</SwitchV2>
|
||||
</div>
|
||||
</SettingsRowV2>
|
||||
)}
|
||||
</For>
|
||||
</SettingsListV2>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
</DialogV2>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Popover as Kobalte } from "@kobalte/core/popover"
|
||||
import { Component, ComponentProps, createMemo, JSX, Show, ValidComponent } from "solid-js"
|
||||
import { Component, ComponentProps, createMemo, For, JSX, Show, ValidComponent } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { popularProviders } from "@/hooks/use-providers"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { Tag } from "@opencode-ai/ui/tag"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { List } from "@opencode-ai/ui/list"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
|
||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||
import { ModelTooltip } from "./model-tooltip"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
@@ -18,6 +22,22 @@ const isFree = (provider: string, cost: { input: number } | undefined) =>
|
||||
provider === "opencode" && (!cost || cost.input === 0)
|
||||
|
||||
type ModelState = ReturnType<typeof useLocal>["model"]
|
||||
type ModelItem = ReturnType<ModelState["list"]>[number]
|
||||
|
||||
const modelKey = (model: ModelItem) => `${model.provider.id}:${model.id}`
|
||||
const manageKey = "action:manage"
|
||||
|
||||
const sortModelGroups = (a: { category: string; items: ModelItem[] }, b: { category: string; items: ModelItem[] }) => {
|
||||
const aIndex = popularProviders.indexOf(a.category)
|
||||
const bIndex = popularProviders.indexOf(b.category)
|
||||
const aPopular = aIndex >= 0
|
||||
const bPopular = bIndex >= 0
|
||||
|
||||
if (aPopular && !bPopular) return -1
|
||||
if (!aPopular && bPopular) return 1
|
||||
if (aPopular && bPopular) return aIndex - bIndex
|
||||
return a.items[0].provider.name.localeCompare(b.items[0].provider.name)
|
||||
}
|
||||
|
||||
const ModelList: Component<{
|
||||
provider?: string
|
||||
@@ -200,6 +220,271 @@ export function ModelSelectorPopover(props: {
|
||||
)
|
||||
}
|
||||
|
||||
export function ModelSelectorPopoverV2(props: {
|
||||
provider?: string
|
||||
model?: ModelState
|
||||
children?: JSX.Element
|
||||
triggerAs?: ValidComponent
|
||||
triggerProps?: ModelSelectorTriggerProps
|
||||
onClose?: () => void
|
||||
}) {
|
||||
const model = props.model ?? useLocal().model
|
||||
const language = useLanguage()
|
||||
const dialog = useDialog()
|
||||
const [store, setStore] = createStore({ open: false, search: "", active: "" })
|
||||
let searchRef: HTMLInputElement | undefined
|
||||
let contentRef: HTMLDivElement | undefined
|
||||
let restoreTrigger = true
|
||||
|
||||
const allModels = createMemo(() =>
|
||||
model
|
||||
.list()
|
||||
.filter((item) => model.visible({ modelID: item.id, providerID: item.provider.id }))
|
||||
.filter((item) => (props.provider ? item.provider.id === props.provider : true)),
|
||||
)
|
||||
const models = createMemo(() => {
|
||||
const search = store.search.trim().toLowerCase()
|
||||
const filtered = search
|
||||
? allModels().filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(search) ||
|
||||
item.id.toLowerCase().includes(search) ||
|
||||
item.provider.name.toLowerCase().includes(search),
|
||||
)
|
||||
: allModels()
|
||||
|
||||
return [...filtered].sort((a, b) => a.name.localeCompare(b.name))
|
||||
})
|
||||
const groups = createMemo(() => {
|
||||
const byProvider = new Map<string, ModelItem[]>()
|
||||
for (const item of models()) {
|
||||
byProvider.set(item.provider.id, [...(byProvider.get(item.provider.id) ?? []), item])
|
||||
}
|
||||
return Array.from(byProvider, ([category, items]) => ({ category, items })).sort(sortModelGroups)
|
||||
})
|
||||
const keys = () => [...models().map(modelKey), manageKey]
|
||||
const current = () => {
|
||||
const value = model.current()
|
||||
return value ? `${value.provider.id}:${value.id}` : undefined
|
||||
}
|
||||
const initialActive = () => {
|
||||
const selected = current()
|
||||
const options = keys()
|
||||
if (selected && options.includes(selected)) return selected
|
||||
return options[0] ?? ""
|
||||
}
|
||||
const activeItem = () =>
|
||||
store.active ? contentRef?.querySelector<HTMLElement>(`[data-option-key="${CSS.escape(store.active)}"]`) : undefined
|
||||
const afterClose = (callback: () => void) => {
|
||||
const complete = () => {
|
||||
if (contentRef?.isConnected) {
|
||||
requestAnimationFrame(complete)
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(() => requestAnimationFrame(callback))
|
||||
}
|
||||
requestAnimationFrame(complete)
|
||||
}
|
||||
const setOpen = (open: boolean) => {
|
||||
if (open) {
|
||||
restoreTrigger = true
|
||||
setStore({ open: true, active: initialActive() })
|
||||
setTimeout(() =>
|
||||
requestAnimationFrame(() => {
|
||||
searchRef?.focus()
|
||||
activeItem()?.scrollIntoView({ block: "nearest" })
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
setStore({ open: false, search: "", active: "" })
|
||||
}
|
||||
const select = (item: ModelItem) => {
|
||||
model.set({ modelID: item.id, providerID: item.provider.id }, { recent: true })
|
||||
props.onClose?.()
|
||||
}
|
||||
const selectModel = (item: ModelItem) => {
|
||||
restoreTrigger = false
|
||||
setOpen(false)
|
||||
afterClose(() => select(item))
|
||||
}
|
||||
const manage = () => {
|
||||
restoreTrigger = false
|
||||
setOpen(false)
|
||||
afterClose(() => {
|
||||
void import("./dialog-manage-models").then((x) => {
|
||||
dialog.show(() => <x.DialogManageModelsV2 />)
|
||||
})
|
||||
})
|
||||
}
|
||||
const selectActive = () => {
|
||||
const item = models().find((item) => modelKey(item) === store.active)
|
||||
if (item) {
|
||||
selectModel(item)
|
||||
return
|
||||
}
|
||||
if (store.active === manageKey) manage()
|
||||
}
|
||||
const moveActive = (delta: number) => {
|
||||
const options = keys()
|
||||
if (options.length === 0) return
|
||||
const index = options.indexOf(store.active)
|
||||
const start = index === -1 ? 0 : index
|
||||
setStore("active", options[(start + delta + options.length) % options.length])
|
||||
queueMicrotask(() => activeItem()?.scrollIntoView({ block: "nearest" }))
|
||||
}
|
||||
const setSearch = (value: string) => {
|
||||
const search = value.trim().toLowerCase()
|
||||
const first = [...allModels()]
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.find(
|
||||
(item) =>
|
||||
!search ||
|
||||
item.name.toLowerCase().includes(search) ||
|
||||
item.id.toLowerCase().includes(search) ||
|
||||
item.provider.name.toLowerCase().includes(search),
|
||||
)
|
||||
setStore({ search: value, active: first ? modelKey(first) : manageKey })
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuV2 open={store.open} modal={false} placement="top-start" gutter={6} onOpenChange={setOpen}>
|
||||
<MenuV2.Trigger as={props.triggerAs ?? "div"} {...props.triggerProps}>
|
||||
{props.children}
|
||||
</MenuV2.Trigger>
|
||||
<MenuV2.Portal>
|
||||
<MenuV2.Content
|
||||
ref={(el: HTMLDivElement) => (contentRef = el)}
|
||||
class="w-[284px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none"
|
||||
onPointerDownOutside={() => (restoreTrigger = false)}
|
||||
onFocusOutside={() => (restoreTrigger = false)}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!restoreTrigger) event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col p-0.5">
|
||||
<div class="flex h-7 items-center gap-2 rounded-sm pl-3 pr-2.5 text-v2-icon-icon-muted">
|
||||
<Icon name="magnifying-glass" size="small" class="shrink-0" />
|
||||
<input
|
||||
ref={(el) => (searchRef = el)}
|
||||
value={store.search}
|
||||
placeholder={language.t("dialog.model.search.placeholder")}
|
||||
class="h-7 min-w-0 flex-1 border-0 bg-transparent text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base outline-none placeholder:text-v2-text-text-faint"
|
||||
spellcheck={false}
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
onInput={(event) => setSearch(event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Tab") return
|
||||
event.stopPropagation()
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault()
|
||||
restoreTrigger = false
|
||||
setOpen(false)
|
||||
afterClose(() => props.onClose?.())
|
||||
return
|
||||
}
|
||||
if (event.altKey || event.metaKey) return
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
moveActive(1)
|
||||
return
|
||||
}
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault()
|
||||
moveActive(-1)
|
||||
return
|
||||
}
|
||||
if (event.key === "Enter" && !event.isComposing) {
|
||||
event.preventDefault()
|
||||
selectActive()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Show when={store.search.trim()}>
|
||||
<button
|
||||
type="button"
|
||||
class="flex size-5 items-center justify-center rounded-sm text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover"
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
onClick={() => setSearch("")}
|
||||
aria-label={language.t("common.clear")}
|
||||
>
|
||||
<Icon name="close" size="small" />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-px bg-v2-border-border-muted" />
|
||||
<ScrollView data-slot="model-selector-scroll" class="max-h-[220px] min-h-0">
|
||||
<div class="flex flex-col p-0.5 pt-0">
|
||||
<Show
|
||||
when={models().length > 0}
|
||||
fallback={
|
||||
<div class="flex h-12 items-center px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint">
|
||||
{language.t("dialog.model.empty")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={groups()}>
|
||||
{(group) => (
|
||||
<MenuV2.Group>
|
||||
<MenuV2.GroupLabel class="gap-2 px-3">
|
||||
<span class="min-w-0 truncate">{group.items[0].provider.name}</span>
|
||||
</MenuV2.GroupLabel>
|
||||
<MenuV2.RadioGroup value={current()}>
|
||||
<For each={group.items}>
|
||||
{(item) => (
|
||||
<MenuV2.RadioItem
|
||||
value={modelKey(item)}
|
||||
data-option-key={modelKey(item)}
|
||||
data-selected-model={current() === modelKey(item) ? true : undefined}
|
||||
class="scroll-my-6"
|
||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
|
||||
onMouseEnter={() => {
|
||||
setStore("active", modelKey(item))
|
||||
setTimeout(() => searchRef?.focus())
|
||||
}}
|
||||
onSelect={() => selectModel(item)}
|
||||
>
|
||||
<span class="min-w-0 truncate">{item.name}</span>
|
||||
<Show when={isFree(item.provider.id, item.cost)}>
|
||||
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
|
||||
</Show>
|
||||
<Show when={item.latest}>
|
||||
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
|
||||
</Show>
|
||||
</MenuV2.RadioItem>
|
||||
)}
|
||||
</For>
|
||||
</MenuV2.RadioGroup>
|
||||
</MenuV2.Group>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</ScrollView>
|
||||
<div class="h-px bg-v2-border-border-muted" />
|
||||
<div class="flex flex-col p-0.5">
|
||||
<MenuV2.Item
|
||||
data-option-key={manageKey}
|
||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === manageKey }}
|
||||
onMouseEnter={() => {
|
||||
setStore("active", manageKey)
|
||||
setTimeout(() => searchRef?.focus())
|
||||
}}
|
||||
onSelect={manage}
|
||||
>
|
||||
<Icon name="outline-sliders" size="small" />
|
||||
<span class="min-w-0 flex-1 truncate leading-5">{language.t("dialog.model.manage")}</span>
|
||||
</MenuV2.Item>
|
||||
</div>
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
)
|
||||
}
|
||||
|
||||
export const DialogSelectModel: Component<{ provider?: string; model?: ModelState }> = (props) => {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
|
||||
@@ -35,12 +35,14 @@ import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { ModelSelectorPopover } from "@/components/dialog-select-model"
|
||||
import { ModelSelectorPopover, ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
|
||||
import { useCommand } from "@/context/command"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { usePermission } from "@/context/permission"
|
||||
@@ -68,6 +70,7 @@ import { promptPlaceholder } from "./prompt-input/placeholder"
|
||||
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||
import type { ReferenceInfo } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
export type PromptInputState = ReturnType<typeof usePrompt>
|
||||
|
||||
@@ -214,6 +217,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
let fileInputRef: HTMLInputElement | undefined
|
||||
let scrollRef!: HTMLDivElement
|
||||
let slashPopoverRef!: HTMLDivElement
|
||||
let restoreEndOnFocus = true
|
||||
|
||||
const mirror = { input: false }
|
||||
const inset = 56
|
||||
@@ -593,6 +597,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
if (!restoreEndOnFocus) return
|
||||
restoreEndOnFocus = false
|
||||
requestAnimationFrame(() => {
|
||||
if (document.activeElement !== editorRef) return
|
||||
setCursorPosition(editorRef, prompt.cursor() ?? promptLength(prompt.current()))
|
||||
queueScroll()
|
||||
})
|
||||
}
|
||||
|
||||
const renderEditorWithCursor = (parts: Prompt) => {
|
||||
const cursor = currentCursor()
|
||||
renderEditor(parts)
|
||||
@@ -629,24 +643,89 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const referenceDescription = (reference: ReferenceInfo) =>
|
||||
reference.source.type === "git" ? reference.source.repository : reference.source.path
|
||||
|
||||
const referenceList = createMemo(() =>
|
||||
sync()
|
||||
.data.reference.filter((reference) => !reference.hidden)
|
||||
.map(
|
||||
(reference): AtOption => ({
|
||||
type: "reference",
|
||||
name: reference.name,
|
||||
path: reference.path,
|
||||
display: reference.name,
|
||||
description: reference.description ?? referenceDescription(reference),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const agentList = createMemo(() =>
|
||||
props.controls.agents.available
|
||||
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
||||
.map((agent): AtOption => ({ type: "agent", name: agent.name, display: agent.name })),
|
||||
)
|
||||
|
||||
const mcpResourceList = createMemo(() =>
|
||||
Object.values(sync().data.mcp_resource).map(
|
||||
(resource): AtOption => ({
|
||||
type: "resource",
|
||||
name: resource.name,
|
||||
uri: resource.uri,
|
||||
client: resource.client,
|
||||
display: resource.name,
|
||||
description: resource.description,
|
||||
mime: resource.mimeType,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const handleAtSelect = (option: AtOption | undefined) => {
|
||||
if (!option) return
|
||||
if (option.type === "agent") {
|
||||
addPart({ type: "agent", name: option.name, content: "@" + option.name, start: 0, end: 0 })
|
||||
} else {
|
||||
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
||||
return
|
||||
}
|
||||
if (option.type === "reference") {
|
||||
addPart({
|
||||
type: "file",
|
||||
path: option.path,
|
||||
content: "@" + option.name,
|
||||
start: 0,
|
||||
end: 0,
|
||||
mime: "application/x-directory",
|
||||
filename: option.name,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (option.type === "resource") {
|
||||
addPart({
|
||||
type: "file",
|
||||
path: option.uri,
|
||||
content: "@" + option.name,
|
||||
start: 0,
|
||||
end: 0,
|
||||
mime: option.mime ?? "text/plain",
|
||||
filename: option.name,
|
||||
url: option.uri,
|
||||
source: {
|
||||
type: "resource",
|
||||
text: { value: "@" + option.name, start: 0, end: 0 },
|
||||
clientName: option.client,
|
||||
uri: option.uri,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
||||
}
|
||||
|
||||
const atKey = (x: AtOption | undefined) => {
|
||||
if (!x) return ""
|
||||
return x.type === "agent" ? `agent:${x.name}` : `file:${x.path}`
|
||||
if (x.type === "agent") return `agent:${x.name}`
|
||||
if (x.type === "reference") return `reference:${x.name}`
|
||||
if (x.type === "resource") return `resource:${x.client}:${x.uri}`
|
||||
return `file:${x.path}`
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -657,30 +736,36 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onKeyDown: atOnKeyDown,
|
||||
} = useFilteredList<AtOption>({
|
||||
items: async (query) => {
|
||||
const references = referenceList()
|
||||
const agents = agentList()
|
||||
const mcpResources = mcpResourceList()
|
||||
const open = recent()
|
||||
const seen = new Set(open)
|
||||
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||
if (!query.trim()) return [...agents, ...pinned]
|
||||
if (!query.trim()) return [...references, ...agents, ...mcpResources, ...pinned]
|
||||
const paths = await files.searchFilesAndDirectories(query)
|
||||
const fileOptions: AtOption[] = paths
|
||||
.filter((path) => !seen.has(path))
|
||||
.map((path) => ({ type: "file", path, display: path }))
|
||||
return [...agents, ...pinned, ...fileOptions]
|
||||
return [...references, ...agents, ...mcpResources, ...pinned, ...fileOptions]
|
||||
},
|
||||
key: atKey,
|
||||
filterKeys: ["display"],
|
||||
skipFilter: (item) => item.type === "file" && !item.recent,
|
||||
groupBy: (item) => {
|
||||
if (item.type === "reference") return "reference"
|
||||
if (item.type === "agent") return "agent"
|
||||
if (item.type === "resource") return "resource"
|
||||
if (item.recent) return "recent"
|
||||
return "file"
|
||||
},
|
||||
sortGroupsBy: (a, b) => {
|
||||
const rank = (category: string) => {
|
||||
if (category === "agent") return 0
|
||||
if (category === "recent") return 1
|
||||
return 2
|
||||
if (category === "reference") return 0
|
||||
if (category === "agent") return 1
|
||||
if (category === "resource") return 2
|
||||
if (category === "recent") return 3
|
||||
return 4
|
||||
}
|
||||
return rank(a.category) - rank(b.category)
|
||||
},
|
||||
@@ -746,7 +831,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const pill = document.createElement("span")
|
||||
pill.textContent = part.content
|
||||
pill.setAttribute("data-type", part.type)
|
||||
if (part.type === "file") pill.setAttribute("data-path", part.path)
|
||||
if (part.type === "file") {
|
||||
pill.setAttribute("data-path", part.path)
|
||||
if (part.mime) pill.setAttribute("data-mime", part.mime)
|
||||
if (part.filename) pill.setAttribute("data-filename", part.filename)
|
||||
if (part.url) pill.setAttribute("data-url", part.url)
|
||||
if (part.source?.type === "resource") {
|
||||
pill.setAttribute("data-source-type", part.source.type)
|
||||
pill.setAttribute("data-source-client-name", part.source.clientName)
|
||||
pill.setAttribute("data-source-uri", part.source.uri)
|
||||
}
|
||||
}
|
||||
if (part.type === "agent") pill.setAttribute("data-name", part.name)
|
||||
pill.setAttribute("contenteditable", "false")
|
||||
pill.style.userSelect = "text"
|
||||
@@ -791,8 +886,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-scroll active command into view when navigating with keyboard
|
||||
createEffect(() => {
|
||||
const scrollSlashActiveIntoView = () => {
|
||||
const activeId = slashActive()
|
||||
if (!activeId || !slashPopoverRef) return
|
||||
|
||||
@@ -800,7 +894,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const element = slashPopoverRef.querySelector(`[data-slash-id="${activeId}"]`)
|
||||
element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
|
||||
})
|
||||
})
|
||||
}
|
||||
const selectPopoverActive = () => {
|
||||
if (store.popover === "at") {
|
||||
const items = atFlat()
|
||||
@@ -862,12 +956,29 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const pushFile = (file: HTMLElement) => {
|
||||
const content = file.textContent ?? ""
|
||||
const source =
|
||||
file.dataset.sourceType === "resource" && file.dataset.sourceClientName && file.dataset.sourceUri
|
||||
? {
|
||||
type: "resource" as const,
|
||||
text: {
|
||||
value: content,
|
||||
start: position,
|
||||
end: position + content.length,
|
||||
},
|
||||
clientName: file.dataset.sourceClientName,
|
||||
uri: file.dataset.sourceUri,
|
||||
}
|
||||
: undefined
|
||||
parts.push({
|
||||
type: "file",
|
||||
path: file.dataset.path!,
|
||||
content,
|
||||
start: position,
|
||||
end: position + content.length,
|
||||
...(file.dataset.mime ? { mime: file.dataset.mime } : {}),
|
||||
...(file.dataset.filename ? { filename: file.dataset.filename } : {}),
|
||||
...(file.dataset.url ? { url: file.dataset.url } : {}),
|
||||
...(source ? { source } : {}),
|
||||
})
|
||||
position += content.length
|
||||
}
|
||||
@@ -1287,6 +1398,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
if (store.popover === "slash") {
|
||||
slashOnKeyDown(event)
|
||||
if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) {
|
||||
scrollSlashActiveIntoView()
|
||||
}
|
||||
}
|
||||
event.preventDefault()
|
||||
return
|
||||
@@ -1368,6 +1482,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
model: props.controls.model.selection,
|
||||
providerID: props.controls.model.selection.current()?.provider?.id,
|
||||
modelName: props.controls.model.selection.current()?.name ?? language.t("dialog.model.select.title"),
|
||||
newLayoutDesigns: props.controls.newLayoutDesigns,
|
||||
style: control(),
|
||||
onClose: restoreFocus,
|
||||
onUnpaidClick: () => {
|
||||
@@ -1378,6 +1493,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}))
|
||||
|
||||
const newSession = () => props.variant === "new-session"
|
||||
const bindEditorRef = (el: HTMLDivElement) => {
|
||||
editorRef = el
|
||||
restoreEndOnFocus = true
|
||||
props.ref?.(el)
|
||||
}
|
||||
const showAgentControl = createMemo(() => props.controls.agents.visible && props.controls.agents.options.length > 0)
|
||||
const agentControlState = createMemo<ComposerAgentControlState>(() => ({
|
||||
title: language.t("command.agent.cycle"),
|
||||
@@ -1406,6 +1526,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
setSlashActive={setSlashActive}
|
||||
onSlashSelect={handleSlashSelect}
|
||||
commandKeybind={command.keybind}
|
||||
commandKeybindParts={command.keybindParts}
|
||||
newLayoutDesigns={props.controls.newLayoutDesigns}
|
||||
t={(key) => language.t(key as Parameters<typeof language.t>[0])}
|
||||
/>
|
||||
<Switch>
|
||||
@@ -1459,10 +1581,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<div class="relative max-h-[180px] overflow-y-auto no-scrollbar" ref={(el) => (scrollRef = el)}>
|
||||
<div
|
||||
data-component="prompt-input"
|
||||
ref={(el) => {
|
||||
editorRef = el
|
||||
props.ref?.(el)
|
||||
}}
|
||||
ref={bindEditorRef}
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
aria-label={designPlaceholder()}
|
||||
@@ -1477,6 +1596,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onPaste={handlePaste}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
@@ -1497,7 +1617,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex h-11 items-center px-2">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-0">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-1">
|
||||
{fileAttachmentInput()}
|
||||
<TooltipV2
|
||||
placement="top"
|
||||
@@ -1529,6 +1649,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
<Show when={!providersLoading() && store.mode !== "shell" && showVariantControl()}>
|
||||
<div
|
||||
data-component="prompt-variant-control"
|
||||
class="[&_[data-action=prompt-model-variant]]:![font-weight:440]"
|
||||
classList={{
|
||||
"animate-in fade-in": providersShouldFadeIn(),
|
||||
"hidden group-hover/prompt-input:block group-focus-within/prompt-input:block":
|
||||
@@ -1545,22 +1666,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
size="normal"
|
||||
options={variants()}
|
||||
current={props.controls.model.selection.variant.current() ?? "default"}
|
||||
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||
<MenuV2
|
||||
gutter={6}
|
||||
modal={false}
|
||||
placement="top-start"
|
||||
onOpenChange={(open) => setStore("variantOpen", open)}
|
||||
onSelect={(value) => {
|
||||
props.controls.model.selection.variant.set(value === "default" ? undefined : value)
|
||||
restoreFocus()
|
||||
}}
|
||||
class="capitalize max-w-[160px] justify-start text-v2-text-text-faint"
|
||||
valueClass="truncate text-[13px] font-[440] leading-5 text-v2-text-text-faint"
|
||||
triggerStyle={control()}
|
||||
triggerProps={{ "data-action": "prompt-model-variant" }}
|
||||
variant="ghost"
|
||||
/>
|
||||
>
|
||||
<MenuV2.Trigger
|
||||
as={ButtonV2}
|
||||
data-action="prompt-model-variant"
|
||||
variant="ghost-muted"
|
||||
size="normal"
|
||||
class="max-w-[160px] justify-start capitalize"
|
||||
style={control()}
|
||||
>
|
||||
<span class="truncate">
|
||||
{props.controls.model.selection.variant.current() ?? language.t("common.default")}
|
||||
</span>
|
||||
<span class="-ml-0.5 -mr-1 flex shrink-0">
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</span>
|
||||
</MenuV2.Trigger>
|
||||
<MenuV2.Portal>
|
||||
<MenuV2.Content>
|
||||
<MenuV2.RadioGroup
|
||||
value={props.controls.model.selection.variant.current() ?? "default"}
|
||||
onChange={(value) => {
|
||||
props.controls.model.selection.variant.set(value === "default" ? undefined : value)
|
||||
restoreFocus()
|
||||
}}
|
||||
>
|
||||
{variants().map((value) => (
|
||||
<MenuV2.RadioItem value={value} class="capitalize">
|
||||
{value === "default" ? language.t("common.default") : value}
|
||||
</MenuV2.RadioItem>
|
||||
))}
|
||||
</MenuV2.RadioGroup>
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
</TooltipV2>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -1640,10 +1784,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
>
|
||||
<div
|
||||
data-component="prompt-input"
|
||||
ref={(el) => {
|
||||
editorRef = el
|
||||
props.ref?.(el)
|
||||
}}
|
||||
ref={bindEditorRef}
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
aria-label={placeholder()}
|
||||
@@ -1658,6 +1799,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
onPaste={handlePaste}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
classList={{
|
||||
@@ -1942,6 +2084,7 @@ type ComposerModelControlState = {
|
||||
model: ReturnType<typeof useLocal>["model"]
|
||||
providerID?: string
|
||||
modelName: string
|
||||
newLayoutDesigns: boolean
|
||||
style: JSX.CSSProperties | undefined
|
||||
onClose: () => void
|
||||
onUnpaidClick: () => void
|
||||
@@ -2032,36 +2175,65 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ModelSelectorPopover
|
||||
model={props.state.model}
|
||||
triggerAs={Button}
|
||||
triggerProps={{
|
||||
variant: "ghost",
|
||||
size: "normal",
|
||||
style: props.state.style,
|
||||
class:
|
||||
"min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group",
|
||||
classList: { "animate-in fade-in": props.state.shouldAnimate },
|
||||
"data-action": "prompt-model",
|
||||
}}
|
||||
onClose={props.state.onClose}
|
||||
<Show
|
||||
when={props.state.newLayoutDesigns}
|
||||
fallback={
|
||||
<ModelSelectorPopover
|
||||
model={props.state.model}
|
||||
triggerAs={Button}
|
||||
triggerProps={{
|
||||
variant: "ghost",
|
||||
size: "normal",
|
||||
style: props.state.style,
|
||||
class:
|
||||
"min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group",
|
||||
classList: { "animate-in fade-in": props.state.shouldAnimate },
|
||||
"data-action": "prompt-model",
|
||||
}}
|
||||
onClose={props.state.onClose}
|
||||
>
|
||||
<ModelControlContent state={props.state} />
|
||||
</ModelSelectorPopover>
|
||||
}
|
||||
>
|
||||
<Show when={props.state.providerID}>
|
||||
{(providerID) => (
|
||||
<ProviderIcon
|
||||
id={providerID()}
|
||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<span class="truncate">{props.state.modelName}</span>
|
||||
<span class="-ml-1 shrink-0 flex size-fit">
|
||||
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
|
||||
</span>
|
||||
</ModelSelectorPopover>
|
||||
<ModelSelectorPopoverV2
|
||||
model={props.state.model}
|
||||
triggerAs={ButtonV2}
|
||||
triggerProps={{
|
||||
variant: "ghost-muted",
|
||||
size: "normal",
|
||||
style: props.state.style,
|
||||
class: "min-w-0 max-w-[220px] justify-start ![font-weight:440] group",
|
||||
classList: { "animate-in fade-in": props.state.shouldAnimate },
|
||||
"data-action": "prompt-model",
|
||||
}}
|
||||
onClose={props.state.onClose}
|
||||
>
|
||||
<ModelControlContent state={props.state} v2 />
|
||||
</ModelSelectorPopoverV2>
|
||||
</Show>
|
||||
</TooltipV2>
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelControlContent(props: { state: ComposerModelControlState; v2?: boolean }) {
|
||||
return (
|
||||
<>
|
||||
<Show when={props.state.providerID}>
|
||||
{(providerID) => (
|
||||
<ProviderIcon
|
||||
id={providerID()}
|
||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<span class="truncate">{props.state.modelName}</span>
|
||||
<span class={props.v2 ? "-ml-0.5 -mr-1 flex shrink-0" : "-ml-1 shrink-0 flex size-fit"}>
|
||||
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,6 +100,41 @@ describe("buildRequestParts", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("preserves reference aliases as directory file parts", () => {
|
||||
const result = buildRequestParts({
|
||||
prompt: [
|
||||
{
|
||||
type: "file",
|
||||
path: "/repo/../docs",
|
||||
content: "@docs",
|
||||
start: 0,
|
||||
end: 5,
|
||||
mime: "application/x-directory",
|
||||
filename: "docs",
|
||||
},
|
||||
],
|
||||
context: [],
|
||||
images: [],
|
||||
text: "@docs",
|
||||
messageID: "msg_reference",
|
||||
sessionID: "ses_reference",
|
||||
sessionDirectory: "/repo/app",
|
||||
})
|
||||
|
||||
const filePart = result.requestParts.find((part) => part.type === "file")
|
||||
expect(filePart).toBeDefined()
|
||||
if (filePart?.type === "file") {
|
||||
expect(filePart.mime).toBe("application/x-directory")
|
||||
expect(filePart.filename).toBe("docs")
|
||||
expect(filePart.url).toBe("file:///repo/../docs")
|
||||
expect(filePart.source?.type).toBe("file")
|
||||
if (filePart.source?.type === "file") {
|
||||
expect(filePart.source.path).toBe("/repo/../docs")
|
||||
expect(filePart.source.text.value).toBe("@docs")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test("deduplicates context files when prompt already includes same path", () => {
|
||||
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
||||
|
||||
|
||||
@@ -99,21 +99,31 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
|
||||
|
||||
const files = input.prompt.filter(isFileAttachment).map((attachment) => {
|
||||
const path = absolute(input.sessionDirectory, attachment.path)
|
||||
const source = attachment.source
|
||||
? {
|
||||
...attachment.source,
|
||||
text: {
|
||||
value: attachment.content,
|
||||
start: attachment.start,
|
||||
end: attachment.end,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: "file" as const,
|
||||
text: {
|
||||
value: attachment.content,
|
||||
start: attachment.start,
|
||||
end: attachment.end,
|
||||
},
|
||||
path,
|
||||
}
|
||||
return {
|
||||
id: Identifier.ascending("part"),
|
||||
type: "file",
|
||||
mime: "text/plain",
|
||||
url: `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
||||
filename: getFilename(attachment.path),
|
||||
source: {
|
||||
type: "file",
|
||||
text: {
|
||||
value: attachment.content,
|
||||
start: attachment.start,
|
||||
end: attachment.end,
|
||||
},
|
||||
path,
|
||||
},
|
||||
mime: attachment.mime ?? "text/plain",
|
||||
url: attachment.url ?? `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
||||
filename: attachment.filename ?? getFilename(attachment.path),
|
||||
source,
|
||||
} satisfies PromptRequestPart
|
||||
})
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
||||
</span>
|
||||
}
|
||||
placement="top"
|
||||
openDelay={2000}
|
||||
openDelay={800}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { Component, For, Match, Show, Switch } from "solid-js"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||
|
||||
export type AtOption =
|
||||
| { type: "agent"; name: string; display: string }
|
||||
| {
|
||||
type: "resource"
|
||||
name: string
|
||||
uri: string
|
||||
client: string
|
||||
display: string
|
||||
description?: string
|
||||
mime?: string
|
||||
}
|
||||
| { type: "reference"; name: string; path: string; display: string; description: string }
|
||||
| { type: "file"; path: string; display: string; recent?: boolean }
|
||||
|
||||
export interface SlashCommand {
|
||||
@@ -30,6 +42,8 @@ type PromptPopoverProps = {
|
||||
setSlashActive: (id: string) => void
|
||||
onSlashSelect: (item: SlashCommand) => void
|
||||
commandKeybind: (id: string) => string | undefined
|
||||
commandKeybindParts: (id: string) => string[]
|
||||
newLayoutDesigns: boolean
|
||||
t: (key: string) => string
|
||||
}
|
||||
|
||||
@@ -41,15 +55,29 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
||||
if (props.popover === "slash") props.setSlashPopoverRef(el)
|
||||
}}
|
||||
class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
||||
overflow-auto no-scrollbar flex flex-col p-2 rounded-[12px]
|
||||
bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]"
|
||||
overflow-auto no-scrollbar flex flex-col p-2"
|
||||
classList={{
|
||||
"z-[70] rounded-[10px] bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]": props.newLayoutDesigns,
|
||||
"rounded-[12px] bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]":
|
||||
!props.newLayoutDesigns,
|
||||
}}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={props.popover === "at"}>
|
||||
<Show
|
||||
when={props.atFlat.length > 0}
|
||||
fallback={<div class="text-text-weak px-2 py-1">{props.t("prompt.popover.emptyResults")}</div>}
|
||||
fallback={
|
||||
<div
|
||||
class="px-2 py-1"
|
||||
classList={{
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{props.t("prompt.popover.emptyResults")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={props.atFlat.slice(0, 10)}>
|
||||
{(item) => {
|
||||
@@ -58,13 +86,117 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
||||
if (item.type === "agent") {
|
||||
return (
|
||||
<button
|
||||
class="w-full flex items-center gap-x-2 rounded-md px-2 py-0.5"
|
||||
classList={{ "bg-surface-raised-base-hover": props.atActive === key }}
|
||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
||||
classList={{
|
||||
"rounded-[4px]": props.newLayoutDesigns,
|
||||
"rounded-md": !props.newLayoutDesigns,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
||||
}}
|
||||
onClick={() => props.onAtSelect(item)}
|
||||
onMouseEnter={() => props.setAtActive(key)}
|
||||
onPointerMove={() => props.setAtActive(key)}
|
||||
>
|
||||
<Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
|
||||
<span class="text-14-regular text-text-strong whitespace-nowrap">@{item.name}</span>
|
||||
<span
|
||||
class="whitespace-nowrap"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
"text-text-strong": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
@{item.name}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.type === "resource") {
|
||||
return (
|
||||
<button
|
||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
||||
classList={{
|
||||
"rounded-[4px]": props.newLayoutDesigns,
|
||||
"rounded-md": !props.newLayoutDesigns,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
||||
}}
|
||||
onClick={() => props.onAtSelect(item)}
|
||||
onPointerMove={() => props.setAtActive(key)}
|
||||
>
|
||||
<FileIcon node={{ path: item.uri, type: "file" }} class="shrink-0 size-4" />
|
||||
<div
|
||||
class="flex items-center min-w-0"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
class="text-text-strong whitespace-nowrap"
|
||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
||||
>
|
||||
@{item.name}
|
||||
</span>
|
||||
<Show when={item.description}>
|
||||
{(description) => (
|
||||
<span
|
||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
||||
classList={{
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{description()}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.type === "reference") {
|
||||
return (
|
||||
<button
|
||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
||||
classList={{
|
||||
"rounded-[4px]": props.newLayoutDesigns,
|
||||
"rounded-md": !props.newLayoutDesigns,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
||||
}}
|
||||
onClick={() => props.onAtSelect(item)}
|
||||
onPointerMove={() => props.setAtActive(key)}
|
||||
>
|
||||
<FileIcon node={{ path: item.path, type: "directory" }} class="shrink-0 size-4" />
|
||||
<div
|
||||
class="flex items-center min-w-0"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
class="text-text-strong whitespace-nowrap"
|
||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
||||
>
|
||||
@{item.name}
|
||||
</span>
|
||||
<span
|
||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
||||
classList={{
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{item.description}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -75,16 +207,44 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
class="w-full flex items-center gap-x-2 rounded-md px-2 py-0.5"
|
||||
classList={{ "bg-surface-raised-base-hover": props.atActive === key }}
|
||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
||||
classList={{
|
||||
"rounded-[4px]": props.newLayoutDesigns,
|
||||
"rounded-md": !props.newLayoutDesigns,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
||||
}}
|
||||
onClick={() => props.onAtSelect(item)}
|
||||
onMouseEnter={() => props.setAtActive(key)}
|
||||
onPointerMove={() => props.setAtActive(key)}
|
||||
>
|
||||
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
|
||||
<div class="flex items-center text-14-regular min-w-0">
|
||||
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{directory}</span>
|
||||
<div
|
||||
class="flex items-center min-w-0"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
class="whitespace-nowrap truncate min-w-0"
|
||||
classList={{
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{directory}
|
||||
</span>
|
||||
<Show when={!isDirectory}>
|
||||
<span class="text-text-strong whitespace-nowrap">{filename}</span>
|
||||
<span
|
||||
class="whitespace-nowrap"
|
||||
classList={{
|
||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
||||
"text-text-strong": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{filename}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
@@ -96,41 +256,98 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
||||
<Match when={props.popover === "slash"}>
|
||||
<Show
|
||||
when={props.slashFlat.length > 0}
|
||||
fallback={<div class="text-text-weak px-2 py-1">{props.t("prompt.popover.emptyCommands")}</div>}
|
||||
fallback={
|
||||
<div
|
||||
class="px-2 py-1"
|
||||
classList={{
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{props.t("prompt.popover.emptyCommands")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={props.slashFlat}>
|
||||
{(cmd) => (
|
||||
<button
|
||||
data-slash-id={cmd.id}
|
||||
classList={{
|
||||
"w-full flex items-center justify-between gap-4 rounded-md px-2 py-1": true,
|
||||
"bg-surface-raised-base-hover": props.slashActive === cmd.id,
|
||||
}}
|
||||
onClick={() => props.onSlashSelect(cmd)}
|
||||
onMouseEnter={() => props.setSlashActive(cmd.id)}
|
||||
>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span class="text-14-regular text-text-strong whitespace-nowrap">/{cmd.trigger}</span>
|
||||
<Show when={cmd.description}>
|
||||
<span class="text-14-regular text-text-weak truncate">{cmd.description}</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Show when={cmd.type === "custom" && cmd.source !== "command"}>
|
||||
<span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
|
||||
{cmd.source === "skill"
|
||||
? props.t("prompt.slash.badge.skill")
|
||||
: cmd.source === "mcp"
|
||||
? props.t("prompt.slash.badge.mcp")
|
||||
: props.t("prompt.slash.badge.custom")}
|
||||
{(cmd) => {
|
||||
const keybind = () => props.commandKeybind(cmd.id)
|
||||
const keybindParts = () => props.commandKeybindParts(cmd.id)
|
||||
return (
|
||||
<button
|
||||
data-slash-id={cmd.id}
|
||||
classList={{
|
||||
"w-full flex items-center justify-between gap-4 px-2 py-1": true,
|
||||
"rounded-[4px] scroll-my-2": props.newLayoutDesigns,
|
||||
"rounded-md": !props.newLayoutDesigns,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.slashActive === cmd.id,
|
||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.slashActive === cmd.id,
|
||||
}}
|
||||
onClick={() => props.onSlashSelect(cmd)}
|
||||
onPointerMove={() => props.setSlashActive(cmd.id)}
|
||||
>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
class="whitespace-nowrap"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
"text-text-strong": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
/{cmd.trigger}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={props.commandKeybind(cmd.id)}>
|
||||
<span class="text-12-regular text-text-subtle">{props.commandKeybind(cmd.id)}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
<Show when={cmd.description}>
|
||||
<span
|
||||
class="truncate"
|
||||
classList={{
|
||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
||||
props.newLayoutDesigns,
|
||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
||||
"text-14-regular": !props.newLayoutDesigns,
|
||||
"text-text-weak": !props.newLayoutDesigns,
|
||||
}}
|
||||
>
|
||||
{cmd.description}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Show when={cmd.type === "custom" && cmd.source !== "command"}>
|
||||
<Show
|
||||
when={props.newLayoutDesigns}
|
||||
fallback={
|
||||
<span class="text-11-regular px-1.5 py-0.5 rounded bg-surface-base text-text-subtle">
|
||||
{cmd.source === "skill"
|
||||
? props.t("prompt.slash.badge.skill")
|
||||
: cmd.source === "mcp"
|
||||
? props.t("prompt.slash.badge.mcp")
|
||||
: props.t("prompt.slash.badge.custom")}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Tag>
|
||||
{cmd.source === "skill"
|
||||
? props.t("prompt.slash.badge.skill")
|
||||
: cmd.source === "mcp"
|
||||
? props.t("prompt.slash.badge.mcp")
|
||||
: props.t("prompt.slash.badge.custom")}
|
||||
</Tag>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={props.newLayoutDesigns ? keybindParts().length > 0 : keybind()}>
|
||||
<Show
|
||||
when={props.newLayoutDesigns}
|
||||
fallback={<span class="text-12-regular text-text-subtle">{keybind()}</span>}
|
||||
>
|
||||
<KeybindV2 keys={keybindParts()} variant="neutral" />
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
</Match>
|
||||
|
||||
@@ -92,11 +92,17 @@ export function PromptWorkspaceSelector(props: {
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||
<span class="min-w-0 truncate">{props.branch || "main"}</span>
|
||||
</div>
|
||||
<Show when={props.branch}>
|
||||
{(branch) => (
|
||||
<>
|
||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||
<span class="min-w-0 truncate">{branch()}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Match, Show, Switch, createMemo } from "solid-js"
|
||||
import { Tooltip, type TooltipProps } from "@opencode-ai/ui/tooltip"
|
||||
import { Match, Show, Switch, createMemo, type ComponentProps, type JSX } from "solid-js"
|
||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||
import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||
|
||||
import { useFile } from "@/context/file"
|
||||
import { useLayout } from "@/context/layout"
|
||||
@@ -9,13 +11,23 @@ import { useSync } from "@/context/sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { getSessionContextMetrics } from "@/components/session/session-context-metrics"
|
||||
import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { createSessionTabs } from "@/pages/session/helpers"
|
||||
|
||||
interface SessionContextUsageProps {
|
||||
variant?: "button" | "indicator"
|
||||
placement?: TooltipProps["placement"]
|
||||
buttonAppearance?: "default" | "v2"
|
||||
placement?: ComponentProps<typeof TooltipV2>["placement"]
|
||||
}
|
||||
|
||||
function ContextTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
|
||||
return (
|
||||
<div class="flex min-w-0 items-center gap-4">
|
||||
<span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
|
||||
<span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function openSessionContext(args: {
|
||||
@@ -23,7 +35,7 @@ function openSessionContext(args: {
|
||||
layout: ReturnType<typeof useLayout>
|
||||
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
||||
}) {
|
||||
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
|
||||
args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button")
|
||||
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
||||
void args.tabs.open("context")
|
||||
args.tabs.setActive("context")
|
||||
@@ -39,12 +51,14 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||
const { params, tabs, view } = useSessionLayout()
|
||||
|
||||
const variant = createMemo(() => props.variant ?? "button")
|
||||
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
|
||||
const tabState = createSessionTabs({
|
||||
tabs,
|
||||
pathFromTab: file.pathFromTab,
|
||||
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||
})
|
||||
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
|
||||
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
||||
|
||||
const usd = createMemo(
|
||||
() =>
|
||||
@@ -54,21 +68,30 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||
}),
|
||||
)
|
||||
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), [...providers.all().values()]))
|
||||
const context = createMemo(() => metrics().context)
|
||||
const context = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
|
||||
const tokens = createMemo(() => info()?.tokens)
|
||||
const cost = createMemo(() => {
|
||||
return usd().format(metrics().totalCost)
|
||||
return usd().format(info()?.cost ?? 0)
|
||||
})
|
||||
const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context")
|
||||
const hasOtherTabs = createMemo(() =>
|
||||
tabs()
|
||||
.all()
|
||||
.some((tab) => tab !== "context" && tab !== "review"),
|
||||
)
|
||||
|
||||
const openContext = () => {
|
||||
if (!params.id) return
|
||||
|
||||
if (tabState.activeTab() === "context") {
|
||||
const sessionView = view()
|
||||
if (contextVisible()) {
|
||||
tabs().close("context")
|
||||
if (sessionView.reviewPanel.source() === "context-button" && !hasOtherTabs()) sessionView.reviewPanel.close()
|
||||
return
|
||||
}
|
||||
|
||||
openSessionContext({
|
||||
view: view(),
|
||||
view: sessionView,
|
||||
layout,
|
||||
tabs: tabs(),
|
||||
})
|
||||
@@ -76,38 +99,54 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||
|
||||
const circle = () => (
|
||||
<div class="flex items-center justify-center">
|
||||
<ProgressCircle size={16} strokeWidth={2} percentage={context()?.usage ?? 0} />
|
||||
<ProgressCircle
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
percentage={context()?.usage ?? 0}
|
||||
style={
|
||||
variant() === "indicator"
|
||||
? {
|
||||
"--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
|
||||
"--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
|
||||
"--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
const circleV2 = () => (
|
||||
<div class="flex items-center justify-center">
|
||||
<ProgressCircleV2 percentage={context()?.usage ?? 0} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const tooltipValue = () => (
|
||||
<div>
|
||||
<Show when={context()}>
|
||||
{(ctx) => (
|
||||
<>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-text-invert-strong">{ctx().total.toLocaleString(language.intl())}</span>
|
||||
<span class="text-text-invert-base">{language.t("context.usage.tokens")}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-text-invert-strong">{ctx().usage ?? 0}%</span>
|
||||
<span class="text-text-invert-base">{language.t("context.usage.usage")}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-text-invert-strong">{cost()}</span>
|
||||
<span class="text-text-invert-base">{language.t("context.usage.cost")}</span>
|
||||
</div>
|
||||
<div class="flex w-[120px] flex-col gap-2">
|
||||
<ContextTooltipRow name={language.t("context.usage.cost")} value={cost()} />
|
||||
<ContextTooltipRow name={language.t("context.usage.usage")} value={`${context()?.usage ?? 0}%`} />
|
||||
<ContextTooltipRow
|
||||
name={language.t("context.usage.tokens")}
|
||||
value={getSessionTokenTotal(tokens())?.toLocaleString(language.intl()) ?? "0"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Show when={params.id}>
|
||||
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
||||
<TooltipV2 value={tooltipValue()} placement={props.placement ?? "top"} shift={-8}>
|
||||
<Switch>
|
||||
<Match when={variant() === "indicator"}>{circle()}</Match>
|
||||
<Match when={buttonAppearance() === "v2"}>
|
||||
<IconButtonV2
|
||||
type="button"
|
||||
variant="ghost-muted"
|
||||
size="large"
|
||||
icon={circleV2()}
|
||||
onClick={openContext}
|
||||
aria-label={language.t("context.usage.view")}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -120,7 +159,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||
</Button>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Tooltip>
|
||||
</TooltipV2>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { Message } from "@opencode-ai/sdk/v2/client"
|
||||
import { getSessionContextMetrics } from "./session-context-metrics"
|
||||
import { getSessionContext, getSessionTokenTotal } from "./session-context-metrics"
|
||||
|
||||
const assistant = (
|
||||
id: string,
|
||||
@@ -37,8 +37,8 @@ const user = (id: string) => {
|
||||
} as unknown as Message
|
||||
}
|
||||
|
||||
describe("getSessionContextMetrics", () => {
|
||||
test("computes totals and usage from latest assistant with tokens", () => {
|
||||
describe("getSessionContext", () => {
|
||||
test("computes usage from latest assistant with tokens", () => {
|
||||
const messages = [
|
||||
user("u1"),
|
||||
assistant("a1", { input: 0, output: 0, reasoning: 0, read: 0, write: 0 }, 0.5),
|
||||
@@ -57,45 +57,52 @@ describe("getSessionContextMetrics", () => {
|
||||
},
|
||||
]
|
||||
|
||||
const metrics = getSessionContextMetrics(messages, providers)
|
||||
const ctx = getSessionContext(messages, providers)
|
||||
|
||||
expect(metrics.totalCost).toBe(1.75)
|
||||
expect(metrics.context?.message.id).toBe("a2")
|
||||
expect(metrics.context?.total).toBe(500)
|
||||
expect(metrics.context?.usage).toBe(50)
|
||||
expect(metrics.context?.providerLabel).toBe("OpenAI")
|
||||
expect(metrics.context?.modelLabel).toBe("GPT-4.1")
|
||||
expect(ctx?.message.id).toBe("a2")
|
||||
expect(ctx?.usage).toBe(50)
|
||||
expect(ctx?.providerLabel).toBe("OpenAI")
|
||||
expect(ctx?.modelLabel).toBe("GPT-4.1")
|
||||
})
|
||||
|
||||
test("preserves fallback labels and null usage when model metadata is missing", () => {
|
||||
const messages = [assistant("a1", { input: 40, output: 10, reasoning: 0, read: 0, write: 0 }, 0.1, "p-1", "m-1")]
|
||||
const providers = [{ id: "p-1", models: {} }]
|
||||
|
||||
const metrics = getSessionContextMetrics(messages, providers)
|
||||
const ctx = getSessionContext(messages, providers)
|
||||
|
||||
expect(metrics.context?.providerLabel).toBe("p-1")
|
||||
expect(metrics.context?.modelLabel).toBe("m-1")
|
||||
expect(metrics.context?.limit).toBeUndefined()
|
||||
expect(metrics.context?.usage).toBeNull()
|
||||
expect(ctx?.providerLabel).toBe("p-1")
|
||||
expect(ctx?.modelLabel).toBe("m-1")
|
||||
expect(ctx?.limit).toBeUndefined()
|
||||
expect(ctx?.usage).toBeNull()
|
||||
})
|
||||
|
||||
test("recomputes when message array is mutated in place", () => {
|
||||
const messages = [assistant("a1", { input: 10, output: 10, reasoning: 10, read: 10, write: 10 }, 0.25)]
|
||||
const providers = [{ id: "openai", models: {} }]
|
||||
|
||||
const one = getSessionContextMetrics(messages, providers)
|
||||
const one = getSessionContext(messages, providers)
|
||||
messages.push(assistant("a2", { input: 100, output: 20, reasoning: 0, read: 0, write: 0 }, 0.75))
|
||||
const two = getSessionContextMetrics(messages, providers)
|
||||
const two = getSessionContext(messages, providers)
|
||||
|
||||
expect(one.context?.message.id).toBe("a1")
|
||||
expect(two.context?.message.id).toBe("a2")
|
||||
expect(two.totalCost).toBe(1)
|
||||
expect(one?.message.id).toBe("a1")
|
||||
expect(two?.message.id).toBe("a2")
|
||||
})
|
||||
|
||||
test("returns empty metrics when inputs are undefined", () => {
|
||||
const metrics = getSessionContextMetrics(undefined, undefined)
|
||||
test("returns undefined when inputs are undefined", () => {
|
||||
const ctx = getSessionContext(undefined, undefined)
|
||||
|
||||
expect(metrics.totalCost).toBe(0)
|
||||
expect(metrics.context).toBeUndefined()
|
||||
expect(ctx).toBeUndefined()
|
||||
})
|
||||
|
||||
test("computes stored session token totals", () => {
|
||||
expect(
|
||||
getSessionTokenTotal({
|
||||
input: 10,
|
||||
output: 20,
|
||||
reasoning: 30,
|
||||
cache: { read: 40, write: 50 },
|
||||
}),
|
||||
).toBe(150)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AssistantMessage, Message } from "@opencode-ai/sdk/v2/client"
|
||||
import type { AssistantMessage, Message, Session } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
type Provider = {
|
||||
id: string
|
||||
@@ -21,19 +21,9 @@ type Context = {
|
||||
modelLabel: string
|
||||
limit: number | undefined
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cacheRead: number
|
||||
cacheWrite: number
|
||||
total: number
|
||||
usage: number | null
|
||||
}
|
||||
|
||||
type Metrics = {
|
||||
totalCost: number
|
||||
context: Context | undefined
|
||||
}
|
||||
|
||||
const tokenTotal = (msg: AssistantMessage) => {
|
||||
return msg.tokens.input + msg.tokens.output + msg.tokens.reasoning + msg.tokens.cache.read + msg.tokens.cache.write
|
||||
}
|
||||
@@ -47,10 +37,9 @@ const lastAssistantWithTokens = (messages: Message[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const build = (messages: Message[] = [], providers: Provider[] = []): Metrics => {
|
||||
const totalCost = messages.reduce((sum, msg) => sum + (msg.role === "assistant" ? msg.cost : 0), 0)
|
||||
const build = (messages: Message[] = [], providers: Provider[] = []): Context | undefined => {
|
||||
const message = lastAssistantWithTokens(messages)
|
||||
if (!message) return { totalCost, context: undefined }
|
||||
if (!message) return undefined
|
||||
|
||||
const provider = providers.find((item) => item.id === message.providerID)
|
||||
const model = provider?.models[message.modelID]
|
||||
@@ -58,25 +47,22 @@ const build = (messages: Message[] = [], providers: Provider[] = []): Metrics =>
|
||||
const total = tokenTotal(message)
|
||||
|
||||
return {
|
||||
totalCost,
|
||||
context: {
|
||||
message,
|
||||
provider,
|
||||
model,
|
||||
providerLabel: provider?.name ?? message.providerID,
|
||||
modelLabel: model?.name ?? message.modelID,
|
||||
limit,
|
||||
input: message.tokens.input,
|
||||
output: message.tokens.output,
|
||||
reasoning: message.tokens.reasoning,
|
||||
cacheRead: message.tokens.cache.read,
|
||||
cacheWrite: message.tokens.cache.write,
|
||||
total,
|
||||
usage: limit ? Math.round((total / limit) * 100) : null,
|
||||
},
|
||||
message,
|
||||
provider,
|
||||
model,
|
||||
providerLabel: provider?.name ?? message.providerID,
|
||||
modelLabel: model?.name ?? message.modelID,
|
||||
limit,
|
||||
input: message.tokens.input,
|
||||
usage: limit ? Math.round((total / limit) * 100) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export function getSessionContextMetrics(messages: Message[] = [], providers: Provider[] = []) {
|
||||
export function getSessionContext(messages: Message[] = [], providers: Provider[] = []) {
|
||||
return build(messages, providers)
|
||||
}
|
||||
|
||||
export function getSessionTokenTotal(tokens: Session["tokens"] | undefined) {
|
||||
if (!tokens) return undefined
|
||||
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useLanguage } from "@/context/language"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { getSessionContextMetrics } from "./session-context-metrics"
|
||||
import { getSessionContext, getSessionTokenTotal } from "./session-context-metrics"
|
||||
import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown"
|
||||
import { createSessionContextFormatter } from "./session-context-format"
|
||||
|
||||
@@ -134,12 +134,12 @@ export function SessionContextTab() {
|
||||
}),
|
||||
)
|
||||
|
||||
const metrics = createMemo(() => getSessionContextMetrics(messages(), [...providers.all().values()]))
|
||||
const ctx = createMemo(() => metrics().context)
|
||||
const ctx = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
|
||||
const tokens = createMemo(() => info()?.tokens)
|
||||
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
||||
|
||||
const cost = createMemo(() => {
|
||||
return usd().format(metrics().totalCost)
|
||||
return usd().format(info()?.cost ?? 0)
|
||||
})
|
||||
|
||||
const counts = createMemo(() => {
|
||||
@@ -204,14 +204,14 @@ export function SessionContextTab() {
|
||||
{ label: "context.stats.provider", value: providerLabel },
|
||||
{ label: "context.stats.model", value: modelLabel },
|
||||
{ label: "context.stats.limit", value: () => formatter().number(ctx()?.limit) },
|
||||
{ label: "context.stats.totalTokens", value: () => formatter().number(ctx()?.total) },
|
||||
{ label: "context.stats.totalTokens", value: () => formatter().number(getSessionTokenTotal(tokens())) },
|
||||
{ label: "context.stats.usage", value: () => formatter().percent(ctx()?.usage) },
|
||||
{ label: "context.stats.inputTokens", value: () => formatter().number(ctx()?.input) },
|
||||
{ label: "context.stats.outputTokens", value: () => formatter().number(ctx()?.output) },
|
||||
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.reasoning) },
|
||||
{ label: "context.stats.inputTokens", value: () => formatter().number(tokens()?.input) },
|
||||
{ label: "context.stats.outputTokens", value: () => formatter().number(tokens()?.output) },
|
||||
{ label: "context.stats.reasoningTokens", value: () => formatter().number(tokens()?.reasoning) },
|
||||
{
|
||||
label: "context.stats.cacheTokens",
|
||||
value: () => `${formatter().number(ctx()?.cacheRead)} / ${formatter().number(ctx()?.cacheWrite)}`,
|
||||
value: () => `${formatter().number(tokens()?.cache.read)} / ${formatter().number(tokens()?.cache.write)}`,
|
||||
},
|
||||
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
||||
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
||||
|
||||
@@ -541,6 +541,7 @@ function SessionHeaderV2Actions(props: { state: SessionHeaderV2ActionsState }) {
|
||||
</Show>
|
||||
<Show when={props.state.reviewVisible}>
|
||||
<TooltipV2
|
||||
class="shrink-0"
|
||||
placement="bottom"
|
||||
value={
|
||||
<>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-titlebar-tab-slot]:not(:first-child)::before {
|
||||
[data-titlebar-tab-slot]:not(:first-child):not([data-active="true"])::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
@@ -32,6 +32,10 @@
|
||||
background: var(--v2-background-bg-layer-02);
|
||||
}
|
||||
|
||||
[data-titlebar-tab-slot][data-active="true"] + [data-titlebar-tab-slot]::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
@@ -5,19 +5,24 @@ import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection } from "@/context/server"
|
||||
import { projectForSession } from "@/pages/layout/helpers"
|
||||
import { ServerConnection, serverName } from "@/context/server"
|
||||
import { displayName, projectForSession } from "@/pages/layout/helpers"
|
||||
import { SessionTabAvatar } from "@/pages/layout/session-tab-avatar"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import type { Session } from "@opencode-ai/sdk/v2"
|
||||
import { canOpenTabRename, forwardTabRef } from "./titlebar-tab-gesture"
|
||||
import { TabPreviewPopover } from "./titlebar-tab-popover"
|
||||
import "./titlebar-tab-nav.css"
|
||||
|
||||
// MouseEvent.button uses 1 for the middle/wheel button.
|
||||
const MIDDLE_MOUSE_BUTTON = 1
|
||||
|
||||
export function TabNavItem(props: {
|
||||
ref?: Ref<HTMLDivElement>
|
||||
href: string
|
||||
server: ServerConnection.Key
|
||||
session: () => Session | undefined
|
||||
fallbackTitle?: string
|
||||
onTitleChange?: (title: string) => void
|
||||
onTitleChangeFailed?: (title: string) => void
|
||||
onClose: () => void
|
||||
@@ -53,6 +58,33 @@ export function TabNavItem(props: {
|
||||
if (!session) return
|
||||
return projectForSession(session, serverCtx()?.projects.list() ?? [])
|
||||
})
|
||||
const title = createMemo(() => props.session()?.title ?? props.fallbackTitle)
|
||||
|
||||
const projectName = createMemo(() => {
|
||||
const session = props.session()
|
||||
if (!session) return
|
||||
return displayName(project() ?? { worktree: session.directory })
|
||||
})
|
||||
const previewPath = createMemo(() => {
|
||||
const session = props.session()
|
||||
if (!session) return
|
||||
const home = serverCtx()?.sync.data.path.home
|
||||
return home ? session.directory.replace(home, "~") : session.directory
|
||||
})
|
||||
const branch = createMemo(() => {
|
||||
const session = props.session()
|
||||
if (!session) return
|
||||
return serverCtx()?.sync.child(session.directory, { bootstrap: false })[0].vcs?.branch
|
||||
})
|
||||
// Only label the server when multiple servers are connected.
|
||||
const serverLabel = createMemo(() => {
|
||||
if (global.servers.list().length <= 1) return
|
||||
const conn = global.servers.list().find((item) => ServerConnection.key(item) === props.server)
|
||||
return conn ? serverName(conn) : undefined
|
||||
})
|
||||
|
||||
const [popoverOpen, setPopoverOpen] = createSignal(false)
|
||||
const previewBlocked = () => !!props.dragging || editing() || !!props.pressed || !props.session()
|
||||
|
||||
const measureTitleOverflow = () => {
|
||||
if (!titleEl || editing()) {
|
||||
@@ -71,7 +103,7 @@ export function TabNavItem(props: {
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
props.session()?.title
|
||||
title()
|
||||
props.forceTruncate
|
||||
editing()
|
||||
scheduleTitleOverflow()
|
||||
@@ -130,9 +162,9 @@ export function TabNavItem(props: {
|
||||
createEffect(() => {
|
||||
if (editing()) return
|
||||
if (!titleEl) return
|
||||
const title = props.session()?.title
|
||||
if (title === undefined) return
|
||||
titleEl.textContent = title
|
||||
const value = title()
|
||||
if (value === undefined) return
|
||||
titleEl.textContent = value
|
||||
})
|
||||
|
||||
const openRename = (event: MouseEvent) => {
|
||||
@@ -168,7 +200,7 @@ export function TabNavItem(props: {
|
||||
onCleanup(cleanup)
|
||||
})
|
||||
|
||||
return (
|
||||
const tab = (
|
||||
<div
|
||||
ref={(el) => {
|
||||
tabRoot = el
|
||||
@@ -184,30 +216,35 @@ export function TabNavItem(props: {
|
||||
data-dragging={props.dragging}
|
||||
data-pressed={props.pressed}
|
||||
onMouseDown={(event) => {
|
||||
if (event.button !== 1) return
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onAuxClick={(event) => {
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||
closeTab(event)
|
||||
}}
|
||||
>
|
||||
<Show when={props.session()}>
|
||||
{(session) => {
|
||||
return (
|
||||
<a
|
||||
data-slot="tab-link"
|
||||
data-titlebar-tab-link
|
||||
href={props.href}
|
||||
draggable={false}
|
||||
onDragStart={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
if (editing()) return
|
||||
if (props.suppressNavigation?.()) return
|
||||
props.onNavigate()
|
||||
}}
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
||||
>
|
||||
<Show when={title() !== undefined}>
|
||||
<a
|
||||
data-slot="tab-link"
|
||||
data-titlebar-tab-link
|
||||
href={props.href}
|
||||
draggable={false}
|
||||
onDragStart={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
if (editing()) return
|
||||
if (props.suppressNavigation?.()) return
|
||||
props.onNavigate()
|
||||
}}
|
||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
||||
>
|
||||
<Show when={props.session()}>
|
||||
{(session) => (
|
||||
<span data-slot="project-avatar-slot">
|
||||
<SessionTabAvatar
|
||||
project={project()}
|
||||
@@ -216,45 +253,45 @@ export function TabNavItem(props: {
|
||||
activeServer={props.activeServer}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
ref={(el) => {
|
||||
titleEl = el
|
||||
titleEl.textContent = session().title
|
||||
}}
|
||||
data-slot="tab-title"
|
||||
data-titlebar-tab-title
|
||||
class="min-w-0 flex-1 outline-none leading-4"
|
||||
classList={{
|
||||
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
||||
"select-text": editing(),
|
||||
}}
|
||||
contenteditable={editing() ? true : undefined}
|
||||
onDblClick={openRename}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void closeRename(true)
|
||||
return
|
||||
}
|
||||
if (event.key !== "Escape") return
|
||||
event.preventDefault()
|
||||
titleEl.textContent = session().title
|
||||
void closeRename(false)
|
||||
}}
|
||||
onBlur={() => void closeRename(true)}
|
||||
onPointerDown={(event) => {
|
||||
if (!editing()) return
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (!editing()) return
|
||||
event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
)}
|
||||
</Show>
|
||||
<span
|
||||
ref={(el) => {
|
||||
titleEl = el
|
||||
titleEl.textContent = title() ?? ""
|
||||
}}
|
||||
data-slot="tab-title"
|
||||
data-titlebar-tab-title
|
||||
class="min-w-0 flex-1 outline-none leading-4"
|
||||
classList={{
|
||||
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
||||
"select-text": editing(),
|
||||
}}
|
||||
contenteditable={editing() ? true : undefined}
|
||||
onDblClick={openRename}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
void closeRename(true)
|
||||
return
|
||||
}
|
||||
if (event.key !== "Escape") return
|
||||
event.preventDefault()
|
||||
titleEl.textContent = props.session()?.title ?? ""
|
||||
void closeRename(false)
|
||||
}}
|
||||
onBlur={() => void closeRename(true)}
|
||||
onPointerDown={(event) => {
|
||||
if (!editing()) return
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (!editing()) return
|
||||
event.preventDefault()
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<div data-slot="tab-close" class="group-hover:bg-[var(--tab-bg)] group-data-[active=true]:bg-[var(--tab-bg)]">
|
||||
@@ -272,6 +309,24 @@ export function TabNavItem(props: {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<TabPreviewPopover
|
||||
trigger={tab}
|
||||
open={popoverOpen() && !previewBlocked()}
|
||||
onOpenChange={(value) => {
|
||||
if (value && previewBlocked()) return
|
||||
setPopoverOpen(value)
|
||||
}}
|
||||
data={{
|
||||
projectName: projectName(),
|
||||
title: props.session()?.title,
|
||||
path: previewPath(),
|
||||
branch: branch(),
|
||||
serverName: serverLabel(),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function DraftTabItem(props: {
|
||||
@@ -302,7 +357,12 @@ export function DraftTabItem(props: {
|
||||
class="group relative flex h-7 w-full min-w-0 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||
classList={{ invisible: props.hidden }}
|
||||
onMouseDown={(event) => {
|
||||
if (event.button !== 1) return
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onAuxClick={(event) => {
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON) return
|
||||
closeTab(event)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
[data-component="session-tab-popover-trigger"] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-component="session-tab-popover"] {
|
||||
z-index: 50;
|
||||
box-sizing: border-box;
|
||||
width: 256px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
|
||||
background: var(--v2-background-bg-base);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--v2-elevation-floating);
|
||||
|
||||
color: var(--v2-text-text-base);
|
||||
font-variant-numeric: tabular-nums;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
transform-origin: var(--kb-hovercard-content-transform-origin);
|
||||
/* Entrance: fade in while scaling up from 0.96 to 1.0. */
|
||||
animation: sessionTabPopoverIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
&:focus-visible,
|
||||
&:focus-within {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Exit: subtle fade + scale down, using the same ease-out curve as entry. */
|
||||
&[data-closed] {
|
||||
animation: sessionTabPopoverOut 100ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
[data-slot="header"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="project"] {
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
[data-slot="title"] {
|
||||
font-weight: 530;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
[data-slot="row"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="icon"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--v2-icon-icon-muted);
|
||||
}
|
||||
|
||||
[data-slot="detail"] {
|
||||
flex: 1 0 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
[data-slot="server"] {
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-muted);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sessionTabPopoverIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sessionTabPopoverOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-component="session-tab-popover"],
|
||||
[data-component="session-tab-popover"][data-closed] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { HoverCard as Kobalte } from "@kobalte/core/hover-card"
|
||||
import { Show, type JSXElement } from "solid-js"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import "./titlebar-tab-popover.css"
|
||||
|
||||
// Initial hover delay before the preview appears, per design.
|
||||
const OPEN_DELAY = 200
|
||||
// Mouse-out delay: begin closing immediately (a brief exit animation plays).
|
||||
const CLOSE_DELAY = 0
|
||||
// After a preview closes, hovering a neighbouring tab within this window skips
|
||||
// the open delay — mirrors the tooltip's skipDelayDuration so moving across
|
||||
// tabs doesn't re-wait the full delay each time.
|
||||
const SKIP_WINDOW = 300
|
||||
let lastClosedAt = 0
|
||||
|
||||
export interface TabPreviewData {
|
||||
projectName?: string
|
||||
title?: string
|
||||
path?: string
|
||||
branch?: string
|
||||
serverName?: string
|
||||
}
|
||||
|
||||
export function TabPreviewPopover(props: {
|
||||
trigger: JSXElement
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
data: TabPreviewData
|
||||
}) {
|
||||
let triggerEl: HTMLDivElement | undefined
|
||||
|
||||
// Kobalte reads openDelay lazily when the pointer enters the trigger, so this
|
||||
// resolves the skip window per-hover.
|
||||
const resolveOpenDelay = () => (Date.now() - lastClosedAt < SKIP_WINDOW ? 0 : OPEN_DELAY)
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) lastClosedAt = Date.now()
|
||||
props.onOpenChange(open)
|
||||
}
|
||||
|
||||
return (
|
||||
<Kobalte
|
||||
open={props.open}
|
||||
onOpenChange={handleOpenChange}
|
||||
openDelay={resolveOpenDelay()}
|
||||
closeDelay={CLOSE_DELAY}
|
||||
// The preview is non-interactive (pointer-events: none), so there is no
|
||||
// safe area to traverse — leaving the tab hides it immediately.
|
||||
ignoreSafeArea
|
||||
placement="bottom-start"
|
||||
gutter={6}
|
||||
>
|
||||
<Kobalte.Trigger ref={triggerEl} as="div" data-component="session-tab-popover-trigger" tabIndex={-1}>
|
||||
{props.trigger}
|
||||
</Kobalte.Trigger>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Content
|
||||
ref={(el) => {
|
||||
// Portalled content lives outside the themed subtree, so mirror the
|
||||
// active theme like the v2 tooltip does.
|
||||
const theme = triggerEl?.closest("[data-theme]")?.getAttribute("data-theme")
|
||||
if (theme) el.setAttribute("data-theme", theme)
|
||||
}}
|
||||
data-component="session-tab-popover"
|
||||
>
|
||||
<div data-slot="header">
|
||||
<Show when={props.data.projectName}>
|
||||
<span data-slot="project">{props.data.projectName}</span>
|
||||
</Show>
|
||||
<Show when={props.data.title}>
|
||||
<span data-slot="title">{props.data.title}</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={props.data.path}>
|
||||
<div data-slot="row">
|
||||
<span data-slot="icon">
|
||||
<IconV2 name="folder" />
|
||||
</span>
|
||||
<span data-slot="detail">{props.data.path}</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.data.branch}>
|
||||
<div data-slot="row">
|
||||
<span data-slot="icon">
|
||||
<IconV2 name="branch" />
|
||||
</span>
|
||||
<span data-slot="detail">{props.data.branch}</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.data.serverName}>
|
||||
<div data-slot="server">{props.data.serverName}</div>
|
||||
</Show>
|
||||
</Kobalte.Content>
|
||||
</Kobalte.Portal>
|
||||
</Kobalte>
|
||||
)
|
||||
}
|
||||
@@ -31,6 +31,7 @@ function SessionTabSlot(props: {
|
||||
onClose: () => void
|
||||
}) {
|
||||
const tabs = useTabs()
|
||||
const language = useLanguage()
|
||||
const sortable = useSortable({
|
||||
get id() {
|
||||
return props.id
|
||||
@@ -50,6 +51,7 @@ function SessionTabSlot(props: {
|
||||
({ id, ctx }) => ctx.sync.session.resolve(id).catch(() => undefined),
|
||||
)
|
||||
const session = createMemo(() => cachedSession() ?? loadedSession())
|
||||
const missingSession = createMemo(() => !!props.serverCtx() && !loadedSession.loading && !session())
|
||||
let prefetched = false
|
||||
|
||||
createEffect(() => {
|
||||
@@ -85,8 +87,9 @@ function SessionTabSlot(props: {
|
||||
ref={sortable.ref}
|
||||
data-titlebar-tab-slot
|
||||
data-tab-key={props.id}
|
||||
data-active={props.active()}
|
||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||
classList={{ hidden: !session() }}
|
||||
classList={{ hidden: !session() && !missingSession() }}
|
||||
>
|
||||
<TabNavItem
|
||||
ref={(el) => {
|
||||
@@ -95,6 +98,7 @@ function SessionTabSlot(props: {
|
||||
href={tabHref(props.tab)}
|
||||
server={props.tab.server}
|
||||
session={session}
|
||||
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
|
||||
onTitleChange={(title) => {
|
||||
const value = session()
|
||||
const ctx = props.serverCtx()
|
||||
@@ -140,6 +144,7 @@ function DraftTabSlot(props: {
|
||||
ref={sortable.ref}
|
||||
data-titlebar-tab-slot
|
||||
data-tab-key={props.id}
|
||||
data-active={props.active()}
|
||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||
>
|
||||
<DraftTabItem
|
||||
|
||||
@@ -229,7 +229,8 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
}}
|
||||
style={{
|
||||
"min-height": minHeight(),
|
||||
"padding-left": mac() && !mobile() ? `${84 / zoom()}px` : 0,
|
||||
// Keep native macOS traffic lights clear even when the desktop window is narrow.
|
||||
"padding-left": mac() ? `${84 / zoom()}px` : 0,
|
||||
width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined,
|
||||
"max-width": electronWindows()
|
||||
? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))`
|
||||
@@ -571,7 +572,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
placement="bottom"
|
||||
title={language.t("command.session.new")}
|
||||
keybind={command.keybind("session.new")}
|
||||
openDelay={2000}
|
||||
openDelay={800}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -601,7 +602,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
>
|
||||
<Show when={hasProjects() && nav()}>
|
||||
<div class="flex items-center gap-0 transition-transform">
|
||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={800}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="chevron-left"
|
||||
@@ -611,7 +612,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
||||
aria-label={language.t("common.goBack")}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={800}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="chevron-right"
|
||||
|
||||
@@ -16,6 +16,7 @@ describe("bootstrapDirectory", () => {
|
||||
status: "loading",
|
||||
agent: [],
|
||||
command: [],
|
||||
reference: [],
|
||||
project: "",
|
||||
projectMeta: undefined,
|
||||
icon: undefined,
|
||||
@@ -35,6 +36,7 @@ describe("bootstrapDirectory", () => {
|
||||
question: {},
|
||||
mcp_ready: true,
|
||||
mcp: {},
|
||||
mcp_resource: {},
|
||||
lsp_ready: true,
|
||||
lsp: [],
|
||||
vcs: undefined,
|
||||
@@ -67,6 +69,7 @@ describe("bootstrapDirectory", () => {
|
||||
},
|
||||
permission: { list: async () => ({ data: [] }) },
|
||||
question: { list: async () => ({ data: [] }) },
|
||||
v2: { reference: { list: async () => ({ data: { data: [] } }) } },
|
||||
mcp: {
|
||||
status: async () => {
|
||||
mcpReads.push("status")
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
Session,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { showToast } from "@/utils/toast"
|
||||
@@ -18,7 +19,7 @@ import type { ServerSession } from "../server-session"
|
||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { QueryClient, queryOptions } from "@tanstack/solid-query"
|
||||
import { loadMcpQuery } from "../server-sync"
|
||||
import { loadMcpQuery, loadMcpResourcesQuery } from "../server-sync"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
||||
|
||||
@@ -195,6 +196,13 @@ export const loadPathQuery = (scope: ServerScope, directory: string | null, sdk:
|
||||
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
||||
})
|
||||
|
||||
export const loadReferencesQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
||||
queryOptions<ReferenceInfo[]>({
|
||||
queryKey: [scope, directory, "references"] as const,
|
||||
queryFn: () => retry(() => sdk.v2.reference.list().then((x) => x.data?.data ?? [])).catch(() => []),
|
||||
placeholderData: [],
|
||||
})
|
||||
|
||||
export async function bootstrapDirectory(input: {
|
||||
directory: string
|
||||
scope: ServerScope
|
||||
@@ -277,6 +285,7 @@ export async function bootstrapDirectory(input: {
|
||||
}),
|
||||
),
|
||||
input.mcp && (() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])))),
|
||||
() => input.queryClient.fetchQuery(loadReferencesQuery(input.scope, input.directory, input.sdk)),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.sdk.permission.list().then((x) => {
|
||||
@@ -339,6 +348,7 @@ export async function bootstrapDirectory(input: {
|
||||
),
|
||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||
input.mcp && (() => input.queryClient.fetchQuery(loadMcpQuery(input.scope, input.directory, input.sdk))),
|
||||
input.mcp && (() => input.queryClient.fetchQuery(loadMcpResourcesQuery(input.scope, input.directory, input.sdk))),
|
||||
() =>
|
||||
input.queryClient.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.sdk)).catch((err) => {
|
||||
const project = getFilename(input.directory)
|
||||
|
||||
@@ -34,7 +34,9 @@ const queryOptionsApi = {
|
||||
}),
|
||||
agents: (directory: string) => ({ queryKey: [directory, "agents"], queryFn: async () => [] }),
|
||||
mcp: (directory: string) => ({ queryKey: [directory, "mcp"], queryFn: async () => ({}) }),
|
||||
mcpResources: (directory: string) => ({ queryKey: [directory, "mcpResources"], queryFn: async () => ({}) }),
|
||||
lsp: (directory: string) => ({ queryKey: [directory, "lsp"], queryFn: async () => [] }),
|
||||
references: (directory: string) => ({ queryKey: [directory, "references"], queryFn: async () => [] }),
|
||||
sessions: (directory: string) => ({ queryKey: [directory, "loadSessions"] as const }),
|
||||
} as unknown as QueryOptionsApi
|
||||
|
||||
@@ -197,14 +199,18 @@ describe("createChildStoreManager", () => {
|
||||
try {
|
||||
if (!manager) throw new Error("manager required")
|
||||
const [store, setStore] = manager.child("/project", { bootstrap: false })
|
||||
expect(querySingles.length - offset).toBe(4)
|
||||
expect(querySingles.length - offset).toBe(6)
|
||||
const query = querySingles[offset + 1]
|
||||
const resourceQuery = querySingles[offset + 2]
|
||||
if (!query) throw new Error("query required")
|
||||
if (!resourceQuery) throw new Error("resource query required")
|
||||
expect(query().enabled).toBe(false)
|
||||
expect(resourceQuery().enabled).toBe(false)
|
||||
|
||||
setStore("status", "complete")
|
||||
manager.child("/project", { bootstrap: false, mcp: true })
|
||||
expect(query().enabled).toBe(true)
|
||||
expect(resourceQuery().enabled).toBe(true)
|
||||
expect(store.mcp).toEqual({ demo: { status: "disabled" } })
|
||||
expect(mcpLoads).toEqual(["/project"])
|
||||
|
||||
|
||||
@@ -185,8 +185,10 @@ export function createChildStoreManager(input: {
|
||||
|
||||
const pathQuery = useQuery(() => input.queryOptions.path(key))
|
||||
const mcpQuery = useQuery(() => ({ ...input.queryOptions.mcp(key), enabled: mcpEnabled() }))
|
||||
const mcpResourceQuery = useQuery(() => ({ ...input.queryOptions.mcpResources(key), enabled: mcpEnabled() }))
|
||||
const lspQuery = useQuery(() => input.queryOptions.lsp(key))
|
||||
const providerQuery = useQuery(() => input.queryOptions.providers(key))
|
||||
const referenceQuery = useQuery(() => input.queryOptions.references(key))
|
||||
|
||||
const child = createStore<State>({
|
||||
project: "",
|
||||
@@ -210,6 +212,9 @@ export function createChildStoreManager(input: {
|
||||
status: "loading" as const,
|
||||
agent: [],
|
||||
command: [],
|
||||
get reference() {
|
||||
return referenceQuery.isLoading ? [] : (referenceQuery.data ?? [])
|
||||
},
|
||||
session: [],
|
||||
sessionTotal: 0,
|
||||
session_status: {},
|
||||
@@ -227,6 +232,9 @@ export function createChildStoreManager(input: {
|
||||
get mcp() {
|
||||
return mcpQuery.isLoading ? {} : (mcpQuery.data ?? {})
|
||||
},
|
||||
get mcp_resource() {
|
||||
return mcpResourceQuery.isLoading ? {} : (mcpResourceQuery.data ?? {})
|
||||
},
|
||||
get lsp_ready() {
|
||||
return !lspQuery.isLoading
|
||||
},
|
||||
|
||||
@@ -112,6 +112,7 @@ export function applyDirectoryEvent(input: {
|
||||
push: (directory: string) => void
|
||||
directory: string
|
||||
loadLsp: () => void
|
||||
loadReferences?: () => void
|
||||
vcsCache?: VcsCache
|
||||
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
||||
retainedLimit?: number
|
||||
@@ -404,5 +405,9 @@ export function applyDirectoryEvent(input: {
|
||||
input.loadLsp()
|
||||
break
|
||||
}
|
||||
case "reference.updated": {
|
||||
input.loadReferences?.()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import type {
|
||||
Command,
|
||||
Config,
|
||||
LspStatus,
|
||||
McpResource,
|
||||
McpStatus,
|
||||
Message,
|
||||
Part,
|
||||
Path,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
Session,
|
||||
SessionStatus,
|
||||
SnapshotFileDiff,
|
||||
@@ -34,6 +36,7 @@ export type State = {
|
||||
status: "loading" | "partial" | "complete"
|
||||
agent: Agent[]
|
||||
command: Command[]
|
||||
reference: ReferenceInfo[]
|
||||
project: string
|
||||
projectMeta: ProjectMeta | undefined
|
||||
icon: string | undefined
|
||||
@@ -63,6 +66,9 @@ export type State = {
|
||||
mcp: {
|
||||
[name: string]: McpStatus
|
||||
}
|
||||
mcp_resource: {
|
||||
[key: string]: McpResource
|
||||
}
|
||||
lsp_ready: boolean
|
||||
lsp: LspStatus[]
|
||||
vcs: VcsInfo | undefined
|
||||
|
||||
@@ -28,6 +28,7 @@ const DEFAULT_SIDEBAR_WIDTH = 344
|
||||
const DEFAULT_FILE_TREE_WIDTH = 200
|
||||
const DEFAULT_SESSION_WIDTH = 600
|
||||
const DEFAULT_TERMINAL_HEIGHT = 280
|
||||
const DEFAULT_REVIEW_PANEL_OPENED = false
|
||||
export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
|
||||
|
||||
export function getAvatarColors(key?: string) {
|
||||
@@ -77,6 +78,7 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
|
||||
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
|
||||
|
||||
export type ReviewDiffStyle = "unified" | "split"
|
||||
export type ReviewPanelSource = "context-button" | "other"
|
||||
|
||||
export type LayoutRoute =
|
||||
| { type: "home" }
|
||||
@@ -210,7 +212,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
if (!isRecord(review)) return review
|
||||
if (typeof review.panelOpened === "boolean") return review
|
||||
|
||||
const opened = isRecord(fileTree) && typeof fileTree.opened === "boolean" ? fileTree.opened : true
|
||||
const opened =
|
||||
isRecord(fileTree) && typeof fileTree.opened === "boolean" ? fileTree.opened : DEFAULT_REVIEW_PANEL_OPENED
|
||||
return {
|
||||
...review,
|
||||
panelOpened: opened,
|
||||
@@ -279,7 +282,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
review: {
|
||||
diffStyle: "split" as ReviewDiffStyle,
|
||||
panelOpened: true,
|
||||
panelOpened: DEFAULT_REVIEW_PANEL_OPENED,
|
||||
},
|
||||
fileTree: {
|
||||
opened: false,
|
||||
@@ -302,6 +305,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
}),
|
||||
)
|
||||
const [ephemeral, setEphemeral] = createStore({
|
||||
reviewPanelSource: "other" as ReviewPanelSource,
|
||||
})
|
||||
|
||||
const MAX_SESSION_KEYS = 50
|
||||
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
|
||||
@@ -662,7 +668,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
|
||||
setDiffStyle(diffStyle: ReviewDiffStyle) {
|
||||
if (!store.review) {
|
||||
setStore("review", { diffStyle, panelOpened: true })
|
||||
setStore("review", { diffStyle, panelOpened: DEFAULT_REVIEW_PANEL_OPENED })
|
||||
return
|
||||
}
|
||||
setStore("review", "diffStyle", diffStyle)
|
||||
@@ -777,7 +783,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
const key = createSessionKeyReader(sessionKey, ensureKey)
|
||||
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
|
||||
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
|
||||
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? true)
|
||||
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
|
||||
const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
|
||||
|
||||
function setTerminalOpened(next: boolean) {
|
||||
const current = store.terminal
|
||||
@@ -791,16 +798,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
setStore("terminal", "opened", next)
|
||||
}
|
||||
|
||||
function setReviewPanelOpened(next: boolean) {
|
||||
function setReviewPanelOpened(next: boolean, source: ReviewPanelSource) {
|
||||
const nextSource = next ? source : "other"
|
||||
const current = store.review
|
||||
if (!current) {
|
||||
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
||||
batch(() => {
|
||||
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
||||
setEphemeral("reviewPanelSource", nextSource)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const value = current.panelOpened ?? true
|
||||
if (value === next) return
|
||||
setStore("review", "panelOpened", next)
|
||||
const value = current.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED
|
||||
if (value === next) {
|
||||
if (ephemeral.reviewPanelSource !== nextSource) setEphemeral("reviewPanelSource", nextSource)
|
||||
return
|
||||
}
|
||||
batch(() => {
|
||||
setStore("review", "panelOpened", next)
|
||||
setEphemeral("reviewPanelSource", nextSource)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -836,14 +853,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
},
|
||||
reviewPanel: {
|
||||
opened: reviewPanelOpened,
|
||||
open() {
|
||||
setReviewPanelOpened(true)
|
||||
source: reviewPanelSource,
|
||||
open(source: ReviewPanelSource = "other") {
|
||||
setReviewPanelOpened(true, source)
|
||||
},
|
||||
close() {
|
||||
setReviewPanelOpened(false)
|
||||
setReviewPanelOpened(false, "other")
|
||||
},
|
||||
toggle() {
|
||||
setReviewPanelOpened(!reviewPanelOpened())
|
||||
setReviewPanelOpened(!reviewPanelOpened(), "other")
|
||||
},
|
||||
},
|
||||
review: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { batch, createEffect, createMemo } from "solid-js"
|
||||
import { batch, createEffect, createMemo, startTransition } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useModels } from "@/context/models"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
@@ -80,6 +80,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
const [store, setStore] = createStore<{
|
||||
current?: string
|
||||
draft?: State
|
||||
promoting?: State
|
||||
last?: {
|
||||
type: "agent" | "model" | "variant"
|
||||
agent?: string
|
||||
@@ -123,7 +124,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
|
||||
const scope = createMemo<State | undefined>(() => {
|
||||
const session = id()
|
||||
if (!session) return store.draft
|
||||
if (!session) return store.draft ?? store.promoting
|
||||
return saved.session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
|
||||
})
|
||||
|
||||
@@ -136,11 +137,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
if (!next) return
|
||||
if (saved.session[session] !== undefined) {
|
||||
handoff.delete(key)
|
||||
setStore("promoting", undefined)
|
||||
return
|
||||
}
|
||||
|
||||
setSaved("session", session, clone(next))
|
||||
handoff.delete(key)
|
||||
setStore("promoting", undefined)
|
||||
})
|
||||
|
||||
const configuredModel = () => {
|
||||
@@ -294,19 +297,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
model.set({ providerID: entry.provider.id, modelID: entry.id })
|
||||
},
|
||||
set(item: ModelKey | undefined, options?: { recent?: boolean }) {
|
||||
batch(() => {
|
||||
setStore("last", {
|
||||
type: "model",
|
||||
agent: agent.current()?.name,
|
||||
model: item ?? null,
|
||||
variant: selected(),
|
||||
})
|
||||
write({ model: item })
|
||||
if (!item) return
|
||||
models.setVisibility(item, true)
|
||||
if (!options?.recent) return
|
||||
models.recent.push(item)
|
||||
})
|
||||
startTransition(() =>
|
||||
batch(() => {
|
||||
setStore("last", {
|
||||
type: "model",
|
||||
agent: agent.current()?.name,
|
||||
model: item ?? null,
|
||||
variant: selected(),
|
||||
})
|
||||
write({ model: item })
|
||||
if (!item) return
|
||||
models.setVisibility(item, true)
|
||||
if (!options?.recent) return
|
||||
models.recent.push(item)
|
||||
}),
|
||||
)
|
||||
},
|
||||
visible(item: ModelKey) {
|
||||
return models.visible(item)
|
||||
@@ -335,19 +340,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
return Object.keys(item.variants)
|
||||
},
|
||||
set(value: string | undefined) {
|
||||
batch(() => {
|
||||
const model = current()
|
||||
setStore("last", {
|
||||
type: "variant",
|
||||
agent: agent.current()?.name,
|
||||
model: model ? { providerID: model.provider.id, modelID: model.id } : null,
|
||||
variant: value ?? null,
|
||||
})
|
||||
write({ variant: value ?? null })
|
||||
if (model) {
|
||||
models.variant.set({ providerID: model.provider.id, modelID: model.id }, value ?? undefined)
|
||||
}
|
||||
})
|
||||
startTransition(() =>
|
||||
batch(() => {
|
||||
const model = current()
|
||||
setStore("last", {
|
||||
type: "variant",
|
||||
agent: agent.current()?.name,
|
||||
model: model ? { providerID: model.provider.id, modelID: model.id } : null,
|
||||
variant: value ?? null,
|
||||
})
|
||||
write({ variant: value ?? null })
|
||||
if (model) {
|
||||
models.variant.set({ providerID: model.provider.id, modelID: model.id }, value ?? undefined)
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
cycle() {
|
||||
const items = this.list()
|
||||
@@ -369,19 +376,19 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
agent,
|
||||
session: {
|
||||
reset() {
|
||||
setStore("draft", undefined)
|
||||
setStore({ draft: undefined, promoting: undefined })
|
||||
},
|
||||
promote(dir: string, session: string) {
|
||||
const next = clone(snapshot())
|
||||
if (!next) return
|
||||
const key = handoffKey(serverSDK().scope, dir, session)
|
||||
handoff.set(key, next)
|
||||
|
||||
if (dir === sdk().directory) {
|
||||
setSaved("session", session, next)
|
||||
setStore("draft", undefined)
|
||||
return
|
||||
}
|
||||
|
||||
handoff.set(handoffKey(serverSDK().scope, dir, session), next)
|
||||
setStore("promoting", next)
|
||||
setStore("draft", undefined)
|
||||
},
|
||||
restore(msg: { sessionID: string; agent: string; model: ModelKey }) {
|
||||
|
||||
@@ -64,6 +64,9 @@ type PlatformBase = {
|
||||
/** Storage mechanism, defaults to localStorage */
|
||||
storage?: (name?: string) => SyncStorage | AsyncStorage
|
||||
|
||||
/** Stable platform window identity for window-scoped persistence */
|
||||
windowID?: string
|
||||
|
||||
/** Application-global desktop updater */
|
||||
updater?: UpdaterPlatform
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useTabs, type Tab } from "./tabs"
|
||||
import { ServerConnection } from "./server"
|
||||
import { requireServerKey } from "@/utils/session-route"
|
||||
import { useSettings } from "./settings"
|
||||
import type { FilePartSource } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
interface PartBase {
|
||||
content: string
|
||||
@@ -27,6 +28,10 @@ export interface FileAttachmentPart extends PartBase {
|
||||
type: "file"
|
||||
path: string
|
||||
selection?: FileSelection
|
||||
mime?: string
|
||||
filename?: string
|
||||
url?: string
|
||||
source?: FilePartSource
|
||||
}
|
||||
|
||||
export interface AgentPart extends PartBase {
|
||||
@@ -73,7 +78,13 @@ function isPartEqual(partA: ContentPart, partB: ContentPart) {
|
||||
case "text":
|
||||
return partB.type === "text" && partA.content === partB.content
|
||||
case "file":
|
||||
return partB.type === "file" && partA.path === partB.path && isSelectionEqual(partA.selection, partB.selection)
|
||||
return (
|
||||
partB.type === "file" &&
|
||||
partA.path === partB.path &&
|
||||
partA.mime === partB.mime &&
|
||||
partA.filename === partB.filename &&
|
||||
isSelectionEqual(partA.selection, partB.selection)
|
||||
)
|
||||
case "agent":
|
||||
return partB.type === "agent" && partA.name === partB.name
|
||||
case "image":
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import type { Config, OpencodeClient, Path, Project, ProviderAuthResponse } from "@opencode-ai/sdk/v2/client"
|
||||
import type {
|
||||
Config,
|
||||
McpResource,
|
||||
OpencodeClient,
|
||||
Path,
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
} from "@opencode-ai/sdk/v2/client"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { type Accessor, batch, createMemo, getOwner, onCleanup, onMount, untrack } from "solid-js"
|
||||
@@ -15,6 +22,7 @@ import {
|
||||
loadPathQuery,
|
||||
loadProjectsQuery,
|
||||
loadProvidersQuery,
|
||||
loadReferencesQuery,
|
||||
} from "./global-sync/bootstrap"
|
||||
import { createChildStoreManager } from "./global-sync/child-store"
|
||||
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
||||
@@ -56,6 +64,13 @@ export const loadMcpQuery = (scope: ServerScope, directory: string, sdk: Opencod
|
||||
queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}),
|
||||
})
|
||||
|
||||
export const loadMcpResourcesQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
||||
queryOptions<Record<string, McpResource>>({
|
||||
queryKey: [scope, directory, "mcpResources"] as const,
|
||||
queryFn: () => sdk.experimental.resource.list().then((r) => r.data ?? {}),
|
||||
placeholderData: {},
|
||||
})
|
||||
|
||||
export const loadLspQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
||||
queryOptions({
|
||||
queryKey: [scope, directory, "lsp"] as const,
|
||||
@@ -75,7 +90,9 @@ function makeQueryOptionsApi(
|
||||
path: (directory: PathKey | null) =>
|
||||
loadPathQuery(scope, directory, directory === null ? serverSDK() : sdkFor(directory)),
|
||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, sdkFor(directory)),
|
||||
references: (directory: PathKey) => loadReferencesQuery(scope, directory, sdkFor(directory)),
|
||||
mcp: (directory: PathKey) => loadMcpQuery(scope, directory, sdkFor(directory)),
|
||||
mcpResources: (directory: PathKey) => loadMcpResourcesQuery(scope, directory, sdkFor(directory)),
|
||||
lsp: (directory: PathKey) => loadLspQuery(scope, directory, sdkFor(directory)),
|
||||
sessions: (directory: PathKey) => ({ queryKey: [scope, directory, "loadSessions"] as const }),
|
||||
}
|
||||
@@ -396,6 +413,9 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
||||
loadLsp: () => {
|
||||
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
|
||||
},
|
||||
loadReferences: () => {
|
||||
void queryClient.fetchQuery(queryOptionsApi.references(key))
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -484,6 +504,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
||||
},
|
||||
refresh: async () => {
|
||||
await queryClient.refetchQueries(queryOptionsApi.mcp(key))
|
||||
await queryClient.refetchQueries(queryOptionsApi.mcpResources(key))
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
const fallback = server.key
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
{
|
||||
...Persist.global("tabs"),
|
||||
...Persist.window("tabs"),
|
||||
migrate: (value: unknown) => {
|
||||
if (!Array.isArray(value)) return value
|
||||
return value.map((tab) => {
|
||||
@@ -62,7 +62,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
},
|
||||
createStore<Tab[]>([]),
|
||||
)
|
||||
const [recent, setRecent, , recentReady] = persisted(Persist.global("tabs.recent"), createStore<RecentTab>({}))
|
||||
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
|
||||
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
@@ -138,12 +138,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
const next = { type: "session" as const, ...tab }
|
||||
const existing = store.find((item) => tabKey(item) === tabKey(next))
|
||||
if (existing) return existing
|
||||
setStore(
|
||||
produce((tabs) => {
|
||||
if (tabs.some((item) => tabKey(item) === tabKey(next))) return
|
||||
tabs.push(next)
|
||||
}),
|
||||
)
|
||||
void startTransition(() => {
|
||||
setStore(
|
||||
produce((tabs) => {
|
||||
if (tabs.some((item) => tabKey(item) === tabKey(next))) return
|
||||
tabs.push(next)
|
||||
}),
|
||||
)
|
||||
})
|
||||
return next
|
||||
},
|
||||
reorder(keys: string[]) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { DESKTOP_MENU } from "./desktop-menu"
|
||||
|
||||
describe("desktop menu", () => {
|
||||
test("exports logs through the desktop command registry", () => {
|
||||
const items = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).filter(
|
||||
(item) => item.type === "item" && item.label === "Export Logs...",
|
||||
)
|
||||
|
||||
expect(items).toHaveLength(2)
|
||||
expect(items.every((item) => item.type === "item" && item.command === "logs.export" && !item.action)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -388,6 +388,20 @@ export const dict = {
|
||||
"wsl.onboarding.chooseDistroFirst": "Choose a distro first.",
|
||||
"wsl.onboarding.loadFailed": "Failed to load WSL state.",
|
||||
"wsl.onboarding.loading": "Loading...",
|
||||
"wsl.onboarding.installedDistros": "Installed distros",
|
||||
"wsl.onboarding.checkAgain": "Check again",
|
||||
"wsl.onboarding.distroStatus.ready": "Ready",
|
||||
"wsl.onboarding.distroStatus.checking": "Checking...",
|
||||
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode not installed",
|
||||
"wsl.onboarding.distroStatus.missingTools": "Missing bash, curl",
|
||||
"wsl.onboarding.distroStatus.unsupported": "Unsupported · Use WSL 2",
|
||||
"wsl.onboarding.needAnotherDistro": "Need another distro?",
|
||||
"wsl.onboarding.needAnotherDistroHint": "Install a Linux distribution from the WSL catalog",
|
||||
"wsl.onboarding.wslNotInstalled.title": "WSL not installed",
|
||||
"wsl.onboarding.wslNotInstalled.description":
|
||||
"WSL (Windows Subsystem for Linux) is required before OpenCode can add a WSL server",
|
||||
"wsl.onboarding.wslUnavailable.title": "WSL unavailable",
|
||||
"wsl.onboarding.wslUnavailable.description": "OpenCode could not verify WSL on this machine.",
|
||||
"wsl.onboarding.installWsl": "Install WSL",
|
||||
"wsl.onboarding.windowsRestartRequired": "Restart Windows to finish installing WSL, then reopen OpenCode.",
|
||||
"wsl.onboarding.next": "Next",
|
||||
@@ -397,6 +411,7 @@ export const dict = {
|
||||
"wsl.onboarding.install": "Install",
|
||||
"wsl.onboarding.installing": "Installing...",
|
||||
"wsl.onboarding.installDistro": "Install distro",
|
||||
"wsl.onboarding.searchDistros": "Search distros",
|
||||
"wsl.onboarding.wsl2Required": "WSL 2 is required.",
|
||||
"wsl.onboarding.toolsRequired": "This distro needs bash and curl.",
|
||||
"wsl.onboarding.openTerminal": "Open terminal",
|
||||
@@ -604,7 +619,12 @@ export const dict = {
|
||||
"session.tab.session": "Session",
|
||||
"session.tab.review": "Review",
|
||||
"session.tab.context": "Context",
|
||||
"session.tab.unknown": "Unknown Session",
|
||||
"session.panel.reviewAndFiles": "Review and files",
|
||||
"session.error.notFound": "This session cannot be found",
|
||||
"session.error.notFound.description": "This tab points to a session that no longer exists on this server.",
|
||||
"session.error.notFound.closeTab": "Close Tab",
|
||||
"session.error.serverConnection": "Can't connect to this server",
|
||||
"session.review.filesChanged": "{{count}} Files Changed",
|
||||
"session.review.change.one": "Change",
|
||||
"session.review.change.other": "Changes",
|
||||
@@ -637,6 +657,10 @@ export const dict = {
|
||||
"session.todo.expand": "Expand",
|
||||
"session.todo.progress": "{{done}} of {{total}} todos completed",
|
||||
"session.question.progress": "{{current}} of {{total}} questions",
|
||||
"session.question.minimize": "Minimize question",
|
||||
"session.question.restore": "Restore question",
|
||||
"session.question.pending.one": "{{count}} pending question",
|
||||
"session.question.pending.other": "{{count}} pending questions",
|
||||
"session.followupDock.summary.one": "{{count}} queued message",
|
||||
"session.followupDock.summary.other": "{{count}} queued messages",
|
||||
"session.followupDock.sendNow": "Send now",
|
||||
|
||||
@@ -108,6 +108,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
.home-session-group-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: var(--v2-background-bg-base);
|
||||
}
|
||||
|
||||
.home-session-group-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--v2-background-bg-base) 0%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 92.0456%, transparent) 7.93%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 84.9947%, transparent) 14.14%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 78.6813%, transparent) 19%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 72.9394%, transparent) 22.85%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 67.6028%, transparent) 26.05%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 62.5055%, transparent) 28.95%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 57.4815%, transparent) 31.91%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 52.3647%, transparent) 35.27%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 46.989%, transparent) 39.4%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 41.1884%, transparent) 44.65%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 34.7969%, transparent) 51.36%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 27.6484%, transparent) 59.9%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 19.5767%, transparent) 70.62%,
|
||||
color-mix(in srgb, var(--v2-background-bg-base) 10.416%, transparent) 83.87%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
[data-slot="titlebar-update-loader"] {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
@@ -132,4 +171,122 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes home-projects-fade-top {
|
||||
from {
|
||||
visibility: hidden;
|
||||
}
|
||||
to {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes home-projects-fade-bottom {
|
||||
from {
|
||||
visibility: visible;
|
||||
}
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"] {
|
||||
timeline-scope: --home-projects-scroll;
|
||||
}
|
||||
|
||||
[data-slot="model-selector-scroll"] {
|
||||
timeline-scope: --model-selector-scroll;
|
||||
}
|
||||
|
||||
[data-slot="manage-models-scroll"] {
|
||||
timeline-scope: --manage-models-scroll;
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"]::before,
|
||||
[data-slot="home-projects-scroll"]::after,
|
||||
[data-slot="model-selector-scroll"]::before,
|
||||
[data-slot="model-selector-scroll"]::after,
|
||||
[data-slot="manage-models-scroll"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
height: 16px;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"]::before {
|
||||
top: 0;
|
||||
background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
|
||||
}
|
||||
|
||||
[data-slot="model-selector-scroll"]::before {
|
||||
top: 0;
|
||||
background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent);
|
||||
}
|
||||
|
||||
[data-slot="manage-models-scroll"]::before {
|
||||
top: 0;
|
||||
background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"]::after {
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, var(--v2-background-bg-base), transparent);
|
||||
}
|
||||
|
||||
[data-slot="model-selector-scroll"]::after {
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent);
|
||||
}
|
||||
|
||||
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
|
||||
[data-slot="home-projects-scroll"] .scroll-view__viewport {
|
||||
scroll-timeline: --home-projects-scroll y;
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"]::before {
|
||||
animation: home-projects-fade-top linear both;
|
||||
animation-timeline: --home-projects-scroll;
|
||||
animation-range: 0 0.1px;
|
||||
}
|
||||
|
||||
[data-slot="home-projects-scroll"]::after {
|
||||
animation: home-projects-fade-bottom linear both;
|
||||
animation-timeline: --home-projects-scroll;
|
||||
animation-range: calc(100% - 1.1px) calc(100% - 1px);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (animation-timeline: --model-selector-scroll) and (timeline-scope: --model-selector-scroll) {
|
||||
[data-slot="model-selector-scroll"] .scroll-view__viewport {
|
||||
scroll-timeline: --model-selector-scroll y;
|
||||
}
|
||||
|
||||
[data-slot="model-selector-scroll"]::before {
|
||||
animation: home-projects-fade-top linear both;
|
||||
animation-timeline: --model-selector-scroll;
|
||||
animation-range: 0 0.1px;
|
||||
}
|
||||
|
||||
[data-slot="model-selector-scroll"]::after {
|
||||
animation: home-projects-fade-bottom linear both;
|
||||
animation-timeline: --model-selector-scroll;
|
||||
animation-range: calc(100% - 1.1px) calc(100% - 1px);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (animation-timeline: --manage-models-scroll) and (timeline-scope: --manage-models-scroll) {
|
||||
[data-slot="manage-models-scroll"] .settings-v2-panel {
|
||||
scroll-timeline: --manage-models-scroll y;
|
||||
}
|
||||
|
||||
[data-slot="manage-models-scroll"]::before {
|
||||
animation: home-projects-fade-top linear both;
|
||||
animation-timeline: --manage-models-scroll;
|
||||
animation-range: 0 0.1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,10 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans">
|
||||
<div
|
||||
class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center font-sans"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
|
||||
<Logo class="w-58.5 opacity-12 shrink-0" />
|
||||
<div class="flex flex-col items-center gap-2 text-center">
|
||||
|
||||
+238
-98
@@ -1,5 +1,6 @@
|
||||
import type { Session } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
type ComponentProps,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
@@ -68,6 +69,9 @@ import { archiveHomeSession } from "./home-session-archive"
|
||||
import { showToast } from "@/utils/toast"
|
||||
|
||||
const HOME_SESSION_LIMIT = 64
|
||||
const HOME_SESSION_HEADER_STICKY_TOP = 12
|
||||
const HOME_SESSION_HEADER_TEXT_HEIGHT = 16
|
||||
const HOME_SESSION_HEADER_FADE_DISTANCE = 16
|
||||
const SHOW_HOME_SESSION_ARCHIVE = false
|
||||
const HOME_ROW_LAYOUT =
|
||||
"flex min-w-0 w-full shrink-0 cursor-default items-center rounded-[6px] bg-transparent text-left transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out focus-visible:outline-none"
|
||||
@@ -133,6 +137,107 @@ function homeSessionSearchKey(record: HomeSessionRecord) {
|
||||
return `${pathKey(record.session.directory)}:${record.session.id}`
|
||||
}
|
||||
|
||||
function useHomeSessionHeaderOpacity(groups: () => HomeSessionGroup[]) {
|
||||
let viewport: HTMLDivElement | undefined
|
||||
let content: HTMLDivElement | undefined
|
||||
let positionFrame: number | undefined
|
||||
let resizeObserver: ResizeObserver | undefined
|
||||
const headerRefs = new Map<HomeSessionGroup["id"], HTMLDivElement>()
|
||||
const headerOffsets = new Map<HomeSessionGroup["id"], number>()
|
||||
const [state, setState] = createStore({
|
||||
titleOpacity: {} as Partial<Record<HomeSessionGroup["id"], number>>,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const items = groups()
|
||||
const ids = new Set(items.map((group) => group.id))
|
||||
headerRefs.forEach((_, id) => {
|
||||
if (!ids.has(id)) headerRefs.delete(id)
|
||||
})
|
||||
headerOffsets.forEach((_, id) => {
|
||||
if (!ids.has(id)) headerOffsets.delete(id)
|
||||
})
|
||||
if (items.length === 0) {
|
||||
content = undefined
|
||||
bindResizeObserver()
|
||||
}
|
||||
queuePositionUpdate()
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (positionFrame !== undefined) cancelAnimationFrame(positionFrame)
|
||||
resizeObserver?.disconnect()
|
||||
})
|
||||
|
||||
function setViewport(el: HTMLDivElement) {
|
||||
viewport = el
|
||||
bindResizeObserver()
|
||||
queuePositionUpdate()
|
||||
}
|
||||
|
||||
function setContentRef(el: HTMLDivElement) {
|
||||
content = el
|
||||
bindResizeObserver()
|
||||
queuePositionUpdate()
|
||||
}
|
||||
|
||||
function setHeaderRef(id: HomeSessionGroup["id"], el: HTMLDivElement) {
|
||||
headerRefs.set(id, el)
|
||||
queuePositionUpdate()
|
||||
}
|
||||
|
||||
function queuePositionUpdate() {
|
||||
if (typeof requestAnimationFrame === "undefined") {
|
||||
updatePositionCache()
|
||||
return
|
||||
}
|
||||
if (positionFrame !== undefined) return
|
||||
positionFrame = requestAnimationFrame(() => {
|
||||
positionFrame = undefined
|
||||
updatePositionCache()
|
||||
})
|
||||
}
|
||||
|
||||
function updatePositionCache() {
|
||||
if (!viewport) return
|
||||
groups().forEach((group) => {
|
||||
const el = headerRefs.get(group.id)
|
||||
if (!el) return
|
||||
headerOffsets.set(group.id, el.offsetTop)
|
||||
})
|
||||
update(viewport.scrollTop)
|
||||
}
|
||||
|
||||
function update(scrollTop: number) {
|
||||
const items = groups()
|
||||
items.forEach((group, index) => {
|
||||
const nextOffset = items
|
||||
.slice(index + 1)
|
||||
.map((item) => headerOffsets.get(item.id))
|
||||
.find((offset) => offset !== undefined)
|
||||
const fadeEnd = HOME_SESSION_HEADER_STICKY_TOP + HOME_SESSION_HEADER_TEXT_HEIGHT
|
||||
const nextTop = nextOffset === undefined ? undefined : nextOffset - scrollTop
|
||||
const opacity =
|
||||
nextTop === undefined ? 1 : Math.max(0, Math.min(1, (nextTop - fadeEnd) / HOME_SESSION_HEADER_FADE_DISTANCE))
|
||||
setState("titleOpacity", group.id, Math.round(opacity * 1000) / 1000)
|
||||
})
|
||||
}
|
||||
|
||||
function titleOpacity(id: HomeSessionGroup["id"]) {
|
||||
return state.titleOpacity[id] ?? 1
|
||||
}
|
||||
|
||||
function bindResizeObserver() {
|
||||
resizeObserver?.disconnect()
|
||||
if (typeof ResizeObserver === "undefined") return
|
||||
resizeObserver = new ResizeObserver(() => queuePositionUpdate())
|
||||
if (viewport) resizeObserver.observe(viewport)
|
||||
if (content) resizeObserver.observe(content)
|
||||
}
|
||||
|
||||
return { setViewport, setContentRef, setHeaderRef, update, titleOpacity }
|
||||
}
|
||||
|
||||
export function NewHome() {
|
||||
const sync = useServerSync()
|
||||
const layout = useLayout()
|
||||
@@ -223,6 +328,7 @@ export function NewHome() {
|
||||
})
|
||||
const searchOpen = createMemo(() => state.searchFocused && search().length > 0)
|
||||
const groups = createMemo(() => groupSessions(records(), language))
|
||||
const sessionHeaderOpacity = useHomeSessionHeaderOpacity(groups)
|
||||
const prefetched = new Set<string>()
|
||||
|
||||
createEffect(() => {
|
||||
@@ -435,7 +541,7 @@ export function NewHome() {
|
||||
/>
|
||||
|
||||
<section
|
||||
class="min-h-0 min-w-0 flex-1 flex flex-col pt-6 lg:pt-12"
|
||||
class="min-h-0 min-w-0 flex-1 flex flex-col pt-6 lg:pt-12 relative"
|
||||
aria-label={language.t("sidebar.project.recentSessions")}
|
||||
>
|
||||
<HomeSessionSearch
|
||||
@@ -456,7 +562,25 @@ export function NewHome() {
|
||||
onClose={closeSearch}
|
||||
onSelect={selectSearchSession}
|
||||
/>
|
||||
<ScrollView class="mt-3 -mr-3 min-h-0 flex-1">
|
||||
<ScrollView
|
||||
class="mt-3 -mr-3 min-h-0 flex-1 relative"
|
||||
viewportRef={sessionHeaderOpacity.setViewport}
|
||||
onScroll={(event) => sessionHeaderOpacity.update(event.currentTarget.scrollTop)}
|
||||
>
|
||||
<Show when={groups().length > 0 && newSessionProject()}>
|
||||
<div class="pointer-events-none absolute top-3 right-3 z-20 flex">
|
||||
<ButtonV2
|
||||
data-action="home-new-session"
|
||||
variant="ghost-muted"
|
||||
size="normal"
|
||||
icon="edit"
|
||||
class="pointer-events-auto h-7 px-2 [font-weight:530]"
|
||||
onClick={openNewSession}
|
||||
>
|
||||
{language.t("command.session.new")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
when={!sessionLoad.isLoading}
|
||||
fallback={
|
||||
@@ -469,15 +593,19 @@ export function NewHome() {
|
||||
when={groups().length > 0}
|
||||
fallback={<HomeSessionsEmpty onNewSession={newSessionProject() ? openNewSession : undefined} />}
|
||||
>
|
||||
<div class="flex flex-col gap-6 pt-3 pr-3 pb-16">
|
||||
<div ref={sessionHeaderOpacity.setContentRef} class="flex flex-col pt-3 pr-3 pb-16">
|
||||
<For each={groups()}>
|
||||
{(group, index) => (
|
||||
<div class="flex min-w-0 flex-col gap-4">
|
||||
<>
|
||||
<HomeSessionGroupHeader
|
||||
title={group.title}
|
||||
onNewSession={index() === 0 && newSessionProject() ? openNewSession : undefined}
|
||||
titleOpacity={sessionHeaderOpacity.titleOpacity(group.id)}
|
||||
ref={(el) => sessionHeaderOpacity.setHeaderRef(group.id, el)}
|
||||
elevated={index() === 0}
|
||||
/>
|
||||
<div class="flex min-w-0 flex-col gap-px">
|
||||
<div
|
||||
class={`flex min-w-0 flex-col gap-px pt-4 ${index() === groups().length - 1 ? "" : "mb-6"}`}
|
||||
>
|
||||
<For each={group.sessions}>
|
||||
{(record) => (
|
||||
<HomeSessionRow
|
||||
@@ -491,7 +619,7 @@ export function NewHome() {
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
@@ -540,10 +668,10 @@ function HomeProjectColumn(props: {
|
||||
|
||||
return (
|
||||
<aside
|
||||
class="mt-6 flex min-w-0 flex-col gap-4 lg:mt-14 lg:pt-[52px]"
|
||||
class="mt-6 flex min-h-0 min-w-0 flex-col gap-4 overflow-hidden lg:mt-14 lg:pt-[52px]"
|
||||
aria-label={props.language.t("home.projects")}
|
||||
>
|
||||
<div class="flex h-7 min-w-0 items-center justify-between pl-1.5">
|
||||
<div class="flex h-7 min-w-0 shrink-0 items-center justify-between pl-1.5 pr-3">
|
||||
<div class={HOME_SECTION_LABEL}>{props.language.t("home.projects")}</div>
|
||||
<Show when={global.servers.list().length === 1}>
|
||||
<TooltipV2 placement="bottom" value={props.language.t("home.project.add")}>
|
||||
@@ -560,42 +688,51 @@ function HomeProjectColumn(props: {
|
||||
</TooltipV2>
|
||||
</Show>
|
||||
</div>
|
||||
<Show
|
||||
when={global.servers.list().length > 1}
|
||||
fallback={<HomeProjectList {...props} server={global.servers.list()[0]!} />}
|
||||
>
|
||||
<For each={global.servers.list()}>
|
||||
{(item) => {
|
||||
const key = ServerConnection.key(item)
|
||||
const healthy = () => !!global.servers.health[key]?.healthy
|
||||
const serverCtx = global.ensureServerCtx(item)
|
||||
const collapsed = () => !!state().collapsed[key]
|
||||
return (
|
||||
<div class="flex max-h-[min(572px,calc(100vh_-_300px))] min-w-0 flex-col gap-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
<HomeServerRow
|
||||
server={item}
|
||||
selected={props.selected.server === key && !props.selected.directory}
|
||||
healthy={healthy()}
|
||||
collapsed={collapsed()}
|
||||
health={global.servers.health[key]}
|
||||
controller={controller}
|
||||
focusServer={props.focusServer}
|
||||
chooseProject={props.chooseProject}
|
||||
openEdit={(server) => dialog.show(() => <DialogServerV2 mode="edit" server={server} />)}
|
||||
toggleCollapsed={() => setState("collapsed", key, !state().collapsed[key])}
|
||||
language={props.language}
|
||||
/>
|
||||
<Show when={healthy() && !collapsed()}>
|
||||
<div class="mx-3 h-px bg-v2-border-border-base" />
|
||||
<HomeProjectList {...props} server={item} projects={serverCtx.projects.list()} />
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
<ScrollView data-slot="home-projects-scroll" class="min-h-0 min-w-0 shrink">
|
||||
<Show
|
||||
when={global.servers.list().length > 1}
|
||||
fallback={
|
||||
<div class="pr-3">
|
||||
<HomeProjectList {...props} server={global.servers.list()[0]!} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="flex min-w-0 flex-col gap-4 pr-3">
|
||||
<For each={global.servers.list()}>
|
||||
{(item) => {
|
||||
const key = ServerConnection.key(item)
|
||||
const healthy = () => !!global.servers.health[key]?.healthy
|
||||
const serverCtx = global.ensureServerCtx(item)
|
||||
const projects = () => serverCtx.projects.list()
|
||||
const hasProjects = () => projects().length > 0
|
||||
const collapsed = () => !!state().collapsed[key]
|
||||
return (
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<HomeServerRow
|
||||
server={item}
|
||||
selected={props.selected.server === key && !props.selected.directory}
|
||||
collapsed={collapsed()}
|
||||
health={global.servers.health[key]}
|
||||
controller={controller}
|
||||
focusServer={props.focusServer}
|
||||
chooseProject={props.chooseProject}
|
||||
openEdit={(server) => dialog.show(() => <DialogServerV2 mode="edit" server={server} />)}
|
||||
toggleCollapsed={() => setState("collapsed", key, !state().collapsed[key])}
|
||||
language={props.language}
|
||||
/>
|
||||
<Show when={healthy() && hasProjects() && !collapsed()}>
|
||||
<div class="mx-3 h-px bg-v2-border-border-base" />
|
||||
<HomeProjectList {...props} server={item} projects={projects()} />
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</ScrollView>
|
||||
<HomeUtilityNav
|
||||
class="mt-4 hidden lg:flex"
|
||||
class="mb-8 mt-4 hidden shrink-0 lg:flex"
|
||||
openSettings={props.openSettings}
|
||||
openHelp={props.openHelp}
|
||||
language={props.language}
|
||||
@@ -635,7 +772,6 @@ function HomeUtilityNav(props: {
|
||||
function HomeServerRow(props: {
|
||||
server: ServerConnection.Any
|
||||
selected: boolean
|
||||
healthy: boolean
|
||||
collapsed: boolean
|
||||
health: ServerHealth | undefined
|
||||
controller: ReturnType<typeof useServerManagementController>
|
||||
@@ -645,39 +781,46 @@ function HomeServerRow(props: {
|
||||
toggleCollapsed: () => void
|
||||
language: ReturnType<typeof useLanguage>
|
||||
}) {
|
||||
const global = useGlobal()
|
||||
const [state, setState] = createStore({ menuOpen: false })
|
||||
const healthy = () => !!props.health?.healthy
|
||||
const canToggle = () => healthy() && global.ensureServerCtx(props.server).projects.list().length > 0
|
||||
return (
|
||||
<div class="group/server relative flex h-7 min-w-0 items-center rounded-[6px]">
|
||||
<button
|
||||
type="button"
|
||||
class={`${HOME_PROJECT_NAV_ROW} pr-16 disabled:opacity-60`}
|
||||
data-selected={props.selected ? "" : undefined}
|
||||
disabled={!props.healthy}
|
||||
disabled={!healthy()}
|
||||
onClick={() => props.focusServer(props.server)}
|
||||
>
|
||||
<Show when={props.healthy}>
|
||||
<span
|
||||
data-action="home-server-collapse"
|
||||
class="inline-flex -ml-0.5 -mr-1.5 size-5 shrink-0 items-center justify-center rounded-[4px] text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover"
|
||||
aria-label={
|
||||
props.collapsed ? props.language.t("home.server.expand") : props.language.t("home.server.collapse")
|
||||
}
|
||||
aria-expanded={!props.collapsed}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
props.toggleCollapsed()
|
||||
}}
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
>
|
||||
<IconV2
|
||||
name="chevron-down"
|
||||
size="small"
|
||||
class="transition-transform duration-150 ease-in-out"
|
||||
style={{ transform: `rotate(${props.collapsed ? -90 : 0}deg)` }}
|
||||
/>
|
||||
</span>
|
||||
</Show>
|
||||
<span
|
||||
data-action="home-server-collapse"
|
||||
class="inline-flex -ml-0.5 -mr-1.5 size-5 shrink-0 items-center justify-center rounded-[4px] text-v2-icon-icon-muted"
|
||||
classList={{
|
||||
"hover:bg-v2-overlay-simple-overlay-hover": canToggle(),
|
||||
"cursor-default opacity-40": !canToggle(),
|
||||
}}
|
||||
aria-label={
|
||||
props.collapsed ? props.language.t("home.server.expand") : props.language.t("home.server.collapse")
|
||||
}
|
||||
aria-disabled={!canToggle()}
|
||||
aria-expanded={canToggle() ? !props.collapsed : undefined}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (!canToggle()) return
|
||||
props.toggleCollapsed()
|
||||
}}
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
>
|
||||
<IconV2
|
||||
name="chevron-down"
|
||||
size="small"
|
||||
class="transition-transform duration-150 ease-in-out"
|
||||
style={{ transform: `rotate(${props.collapsed ? -90 : 0}deg)` }}
|
||||
/>
|
||||
</span>
|
||||
<div class="flex size-4 shrink-0 items-center justify-center -mr-0.5">
|
||||
<ServerHealthIndicator health={props.health} />
|
||||
</div>
|
||||
@@ -854,6 +997,7 @@ function HomeSessionLeading(props: {
|
||||
session: Session
|
||||
server: ServerConnection.Key
|
||||
activeServer: boolean
|
||||
revealProjectOnHover: boolean
|
||||
}) {
|
||||
const tabs = useTabs()
|
||||
const hasOpenTab = createMemo(() => sessionHasOpenTab(tabs.store, props.server, props.session))
|
||||
@@ -862,8 +1006,8 @@ function HomeSessionLeading(props: {
|
||||
<Show when={hasOpenTab()}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="pointer-events-none absolute top-1/2 h-[7px] w-[3px] -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||
style={{ right: "calc(100% + 5px)" }}
|
||||
class="pointer-events-none absolute top-1/2 h-3 w-0.5 -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||
style={{ right: "calc(100% + 4px)" }}
|
||||
/>
|
||||
</Show>
|
||||
<SessionTabAvatar
|
||||
@@ -871,6 +1015,7 @@ function HomeSessionLeading(props: {
|
||||
directory={props.session.directory}
|
||||
sessionId={props.session.id}
|
||||
activeServer={props.activeServer}
|
||||
revealProjectOnHover={props.revealProjectOnHover}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -961,7 +1106,7 @@ function HomeSessionSearch(props: {
|
||||
|
||||
return (
|
||||
<div class="w-full">
|
||||
<div ref={root} data-component="home-session-search" class="relative z-10 w-full">
|
||||
<div ref={root} data-component="home-session-search" class="relative z-30 w-full">
|
||||
<Show when={props.open}>
|
||||
<div
|
||||
data-component="home-session-search-panel"
|
||||
@@ -1018,13 +1163,7 @@ function HomeSessionSearch(props: {
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<label
|
||||
class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out"
|
||||
classList={{
|
||||
"focus-within:shadow-[0_0_0_0.5px_var(--v2-border-border-focus),var(--v2-elevation-raised)]": !props.open,
|
||||
"shadow-[0_0_0_0.5px_var(--v2-border-border-focus)]": props.open,
|
||||
}}
|
||||
>
|
||||
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02">
|
||||
<IconV2 name="magnifying-glass" />
|
||||
<input
|
||||
ref={input}
|
||||
@@ -1110,6 +1249,7 @@ function HomeSessionSearchResultRow(props: {
|
||||
classList={{
|
||||
[HOME_SEARCH_RESULT_ROW]: true,
|
||||
"bg-v2-overlay-simple-overlay-hover": props.selected,
|
||||
group: !!showProjectName(),
|
||||
}}
|
||||
onMouseEnter={() => props.onHighlight()}
|
||||
onClick={() => props.onSelect(props.record.session)}
|
||||
@@ -1119,6 +1259,7 @@ function HomeSessionSearchResultRow(props: {
|
||||
session={props.record.session}
|
||||
server={props.server}
|
||||
activeServer={props.activeServer}
|
||||
revealProjectOnHover={!!showProjectName()}
|
||||
/>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-1.5">
|
||||
<span
|
||||
@@ -1134,25 +1275,20 @@ function HomeSessionSearchResultRow(props: {
|
||||
)
|
||||
}
|
||||
|
||||
function HomeSessionGroupHeader(props: { title: string; onNewSession?: () => void }) {
|
||||
const language = useLanguage()
|
||||
function HomeSessionGroupHeader(props: {
|
||||
title: string
|
||||
titleOpacity: number
|
||||
ref: ComponentProps<"div">["ref"]
|
||||
elevated?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div class="flex h-7 min-w-0 items-center justify-between pl-3">
|
||||
<div class={HOME_SECTION_LABEL}>{props.title}</div>
|
||||
<Show when={props.onNewSession}>
|
||||
{(onNewSession) => (
|
||||
<ButtonV2
|
||||
data-action="home-new-session"
|
||||
variant="ghost-muted"
|
||||
size="normal"
|
||||
icon="edit"
|
||||
class="h-7 px-2 [font-weight:530]"
|
||||
onClick={onNewSession()}
|
||||
>
|
||||
{language.t("command.session.new")}
|
||||
</ButtonV2>
|
||||
)}
|
||||
</Show>
|
||||
<div
|
||||
ref={props.ref}
|
||||
class={`pointer-events-none sticky top-3 flex h-7 min-w-0 items-center justify-between pl-3 bg-v2-background-bg-base ${props.elevated ? "home-session-group-header z-[5]" : "z-10"}`}
|
||||
>
|
||||
<div class={HOME_SECTION_LABEL} style={{ opacity: props.titleOpacity }}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1170,7 +1306,10 @@ function HomeSessionRow(props: {
|
||||
const showProjectName = () => props.showProjectName && props.record.projectName
|
||||
|
||||
return (
|
||||
<div class="group/session relative flex h-10 min-w-0 items-center rounded-[6px]">
|
||||
<div
|
||||
class="group/session relative flex h-10 min-w-0 items-center rounded-[6px]"
|
||||
classList={{ group: !!showProjectName() }}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-component="home-session-row"
|
||||
@@ -1182,6 +1321,7 @@ function HomeSessionRow(props: {
|
||||
session={props.record.session}
|
||||
server={props.server}
|
||||
activeServer={props.activeServer}
|
||||
revealProjectOnHover={!!showProjectName()}
|
||||
/>
|
||||
<span
|
||||
class={`min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-v2-text-text-base [font-weight:530] ${showProjectName() ? "max-w-[min(70%,480px)] flex-[0_1_auto]" : "flex-[1_1_auto]"}`}
|
||||
|
||||
@@ -943,18 +943,6 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
keybind: "mod+comma",
|
||||
onSelect: () => openSettings(),
|
||||
},
|
||||
...(platform.platform === "desktop" && platform.exportDebugLogs
|
||||
? [
|
||||
{
|
||||
id: "logs.export",
|
||||
title: "Export logs",
|
||||
category: language.t("command.category.settings"),
|
||||
onSelect: () => {
|
||||
void platform.exportDebugLogs?.()
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: "session.previous",
|
||||
title: language.t("command.session.previous"),
|
||||
|
||||
@@ -11,32 +11,28 @@ export function SessionTabAvatar(props: {
|
||||
directory: string
|
||||
sessionId: string
|
||||
activeServer: boolean
|
||||
revealProjectOnHover?: boolean
|
||||
}) {
|
||||
const directory = () => props.directory
|
||||
const sessionId = () => props.sessionId
|
||||
const state = useSessionTabAvatarState(directory, sessionId, () => props.activeServer)
|
||||
const projectAvatar = () => (
|
||||
<ProjectAvatar
|
||||
fallback={displayName(props.project ?? { worktree: props.directory })}
|
||||
src={getProjectAvatarSource(props.project?.id, props.project?.icon)}
|
||||
variant={getProjectAvatarVariant(props.project?.icon?.color)}
|
||||
unread={state.unread()}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Show
|
||||
when={state.loading()}
|
||||
fallback={
|
||||
<ProjectAvatar
|
||||
fallback={displayName(props.project ?? { worktree: props.directory })}
|
||||
src={getProjectAvatarSource(props.project?.id, props.project?.icon)}
|
||||
variant={getProjectAvatarVariant(props.project?.icon?.color)}
|
||||
unread={state.unread()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Show when={state.loading()} fallback={projectAvatar()}>
|
||||
<span class="relative block size-4 shrink-0">
|
||||
<SessionProgressIndicatorV2 class="absolute inset-0 group-hover:invisible" />
|
||||
<span class="invisible absolute inset-0 group-hover:visible">
|
||||
<ProjectAvatar
|
||||
fallback={displayName(props.project ?? { worktree: props.directory })}
|
||||
src={getProjectAvatarSource(props.project?.id, props.project?.icon)}
|
||||
variant={getProjectAvatarVariant(props.project?.icon?.color)}
|
||||
unread={state.unread()}
|
||||
/>
|
||||
</span>
|
||||
<SessionProgressIndicatorV2
|
||||
class={`absolute inset-0 ${props.revealProjectOnHover === false ? "" : "group-hover:invisible"}`}
|
||||
/>
|
||||
<Show when={props.revealProjectOnHover !== false}>
|
||||
<span class="invisible absolute inset-0 group-hover:visible">{projectAvatar()}</span>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
import {
|
||||
batch,
|
||||
ErrorBoundary,
|
||||
onCleanup,
|
||||
Show,
|
||||
Match,
|
||||
@@ -10,8 +11,10 @@ import {
|
||||
createMemo,
|
||||
createEffect,
|
||||
createComputed,
|
||||
createResource,
|
||||
on,
|
||||
onMount,
|
||||
type ParentProps,
|
||||
untrack,
|
||||
} from "solid-js"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
@@ -19,29 +22,37 @@ import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { FileProvider, selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||
import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
||||
import { useLocation, useNavigate, useSearchParams } from "@solidjs/router"
|
||||
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||
import { useComments } from "@/context/comments"
|
||||
import { ErrorPage } from "@/pages/error"
|
||||
import { CommentsProvider, useComments } from "@/context/comments"
|
||||
import { DirectoryDataProvider } from "@/pages/directory-layout"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { usePrompt } from "@/context/prompt"
|
||||
import { ModelsProvider } from "@/context/models"
|
||||
import { useNotification } from "@/context/notification"
|
||||
import { PermissionProvider } from "@/context/permission"
|
||||
import { PromptProvider, usePrompt } from "@/context/prompt"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
||||
import { useServerSDK } from "@/context/server-sdk"
|
||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useTerminal } from "@/context/terminal"
|
||||
import { useTabs } from "@/context/tabs"
|
||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||
@@ -73,8 +84,8 @@ import { Identifier } from "@/utils/id"
|
||||
import { diffs as list } from "@/utils/diffs"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { extractPromptFromParts } from "@/utils/prompt"
|
||||
import { formatServerError } from "@/utils/server-errors"
|
||||
import { legacySessionHref, requireServerKey, sessionHref } from "@/utils/session-route"
|
||||
import { formatServerError, isSessionNotFoundError } from "@/utils/server-errors"
|
||||
import { legacySessionHref, requireServerKey, selectSessionLineage, sessionHref } from "@/utils/session-route"
|
||||
import { useUsageExceededDialogs } from "./session/usage-exceeded-dialogs"
|
||||
import { createSessionOwnership } from "./session/session-ownership"
|
||||
|
||||
@@ -91,6 +102,15 @@ const sessionViewState = () => ({
|
||||
changes: "git" as ChangeMode,
|
||||
})
|
||||
|
||||
function isLocalSessionNotFoundError(error: unknown, sessionID: string) {
|
||||
return error instanceof Error && error.message === `Session not found: ${sessionID}`
|
||||
}
|
||||
|
||||
function isCurrentSessionNotFoundError(error: unknown, sessionID: string | undefined) {
|
||||
if (!sessionID) return false
|
||||
return isSessionNotFoundError(error, sessionID) || isLocalSessionNotFoundError(error, sessionID)
|
||||
}
|
||||
|
||||
async function runPromptRollbackMutation<T, R>(input: {
|
||||
capturePrompt: () => { current: () => T[]; set: (value: T[]) => void; reset: () => void }
|
||||
optimistic: (prompt: { set: (value: T[]) => void; reset: () => void }) => void
|
||||
@@ -114,6 +134,204 @@ async function runPromptRollbackMutation<T, R>(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export function SessionPage() {
|
||||
return (
|
||||
<SessionProviders>
|
||||
<Page />
|
||||
</SessionProviders>
|
||||
)
|
||||
}
|
||||
|
||||
export function TargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
return (
|
||||
<Show when={`${params.serverKey}\0${params.id}`} keyed>
|
||||
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)} padded>
|
||||
<ResolvedTargetSessionRoute />
|
||||
</SessionRouteErrorBoundary>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionRouteErrorBoundary(
|
||||
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
||||
) {
|
||||
const settings = useSettings()
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={(error) =>
|
||||
settings.general.newLayoutDesigns() ? (
|
||||
<SessionRouteFrame padded={props.padded}>
|
||||
<SessionPanelFrame newLayout raised={!!props.sessionID}>
|
||||
<SessionErrorFallback error={error} sessionID={props.sessionID} serverKey={props.serverKey} />
|
||||
</SessionPanelFrame>
|
||||
</SessionRouteFrame>
|
||||
) : (
|
||||
<ErrorPage error={error} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionErrorFallback(props: { error: unknown; sessionID?: string; serverKey?: ServerConnection.Key }) {
|
||||
const language = useLanguage()
|
||||
const server = useServer()
|
||||
const tabs = useTabs()
|
||||
const displayServer = createMemo(() => {
|
||||
const key = props.serverKey ?? server.key
|
||||
const conn = server.list.find((item) => ServerConnection.key(item) === key)
|
||||
return conn ? serverName(conn) : key
|
||||
})
|
||||
const closeTab = () => {
|
||||
if (!props.sessionID) return
|
||||
tabs.removeSessionTab({ server: props.serverKey ?? server.key, sessionId: props.sessionID })
|
||||
}
|
||||
if (isCurrentSessionNotFoundError(props.error, props.sessionID)) {
|
||||
return (
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<div class="h-full px-6 pb-42 -mt-4 flex flex-col items-center justify-center text-center gap-4">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="text-16-medium text-text max-w-md">{language.t("session.error.notFound")}</div>
|
||||
<div class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("session.error.notFound.description")}
|
||||
</div>
|
||||
</div>
|
||||
<Show when={props.sessionID}>
|
||||
{(sessionID) => (
|
||||
<div class="max-w-full flex flex-col items-center gap-1">
|
||||
<div class="max-w-full text-11-regular text-text-faint break-all">{displayServer()}</div>
|
||||
<code class="max-w-full rounded-[4px] px-1 py-0.5 font-mono text-xs font-medium leading-4 text-text-base break-all bg-[color-mix(in_oklch,var(--v2-text-text-base)_8%,transparent)]">
|
||||
{sessionID()}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<ButtonV2 variant="neutral" size="normal" icon="xmark-small" onClick={closeTab}>
|
||||
{language.t("session.error.notFound.closeTab")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <ErrorPage error={props.error} />
|
||||
}
|
||||
|
||||
function ResolvedTargetSessionRoute() {
|
||||
const params = useParams<{ serverKey: string; id: string }>()
|
||||
const settings = useSettings()
|
||||
const tabs = useTabs()
|
||||
const sync = useServerSync()
|
||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||
const cached = createMemo(() => sync().session.lineage.peek(params.id))
|
||||
const [resolved] = createResource(
|
||||
() => {
|
||||
if (cached()) return
|
||||
return { id: params.id, sync: sync() }
|
||||
},
|
||||
({ id, sync }) => sync.session.lineage.resolve(id),
|
||||
)
|
||||
const current = createMemo(() => selectSessionLineage(params.id, cached(), resolved()))
|
||||
const directory = createMemo(() => current()?.session.directory)
|
||||
const targetDirectory = () => directory()!
|
||||
|
||||
createEffect(() => {
|
||||
const session = current()
|
||||
if (!session) return
|
||||
tabs.addSessionTab({
|
||||
server: serverKey(),
|
||||
sessionId: session.root.id,
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<TargetServerScopedProviders directory={directory} sessionID={() => params.id}>
|
||||
<Show when={directory()}>
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
||||
>
|
||||
<SDKProvider directory={targetDirectory}>
|
||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||
<TargetSessionPage />
|
||||
</DirectoryDataProvider>
|
||||
</SDKProvider>
|
||||
</Show>
|
||||
</Show>
|
||||
</TargetServerScopedProviders>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetSessionPage() {
|
||||
const sdk = useSDK()
|
||||
const serverSDK = useServerSDK()
|
||||
return (
|
||||
<Show when={`${serverSDK().scope}\0${sdk().directory}`} keyed>
|
||||
<SessionPage />
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function TargetServerScopedProviders(
|
||||
props: ParentProps<{ directory?: () => string | undefined; sessionID?: () => string | undefined }>,
|
||||
) {
|
||||
return (
|
||||
<PermissionProvider directory={props.directory}>
|
||||
<MarkSessionNotificationsViewed sessionID={props.sessionID} />
|
||||
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
|
||||
</PermissionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) {
|
||||
const notification = useNotification()
|
||||
createEffect(() => {
|
||||
const sessionID = props.sessionID?.()
|
||||
if (!notification.ready() || !sessionID) return
|
||||
if (notification.session.unseenCount(sessionID) === 0) return
|
||||
notification.session.markViewed(sessionID)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
function SessionProviders(props: ParentProps) {
|
||||
return (
|
||||
<TerminalProvider>
|
||||
<FileProvider>
|
||||
<PromptProvider>
|
||||
<CommentsProvider>{props.children}</CommentsProvider>
|
||||
</PromptProvider>
|
||||
</FileProvider>
|
||||
</TerminalProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionRouteFrame(props: ParentProps<{ padded?: boolean }>) {
|
||||
return (
|
||||
<div class="relative size-full overflow-hidden flex flex-col" classList={{ "p-2": props.padded }}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionPanelFrame(props: ParentProps<{ newLayout: boolean; raised?: boolean }>) {
|
||||
return (
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 flex flex-col": true,
|
||||
"bg-v2-background-bg-base": props.newLayout,
|
||||
"bg-background-stronger": !props.newLayout,
|
||||
"rounded-[10px] overflow-hidden": props.newLayout,
|
||||
"shadow-[var(--v2-elevation-raised)]": props.newLayout && props.raised,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const serverSync = useServerSync()
|
||||
const layout = useLayout()
|
||||
@@ -1695,9 +1913,88 @@ export default function Page() {
|
||||
() => !isDesktop() && settings.general.newLayoutDesigns() && settings.general.mobileTitlebarPosition() === "bottom",
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="relative size-full overflow-hidden flex flex-col">
|
||||
const sessionErrorFallback = (error: unknown, reset: () => void) => {
|
||||
createEffect(on(sessionKey, reset, { defer: true }))
|
||||
return <SessionErrorFallback error={error} sessionID={params.id} />
|
||||
}
|
||||
|
||||
const sessionPanelContent = () => (
|
||||
<>
|
||||
{sessionSync() ?? ""}
|
||||
<Show when={!isDesktop() && !!params.id && settings.general.newLayoutDesigns() && !mobileTabsBottom()}>
|
||||
{mobileTabs(true)}
|
||||
</Show>
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<Switch>
|
||||
<Match when={params.id && mobileChanges()}>
|
||||
<div class="relative h-full overflow-hidden">
|
||||
{reviewContent({
|
||||
diffStyle: "unified",
|
||||
classes: {
|
||||
root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
|
||||
header: "px-4 !h-16 !pb-4",
|
||||
container: "px-4",
|
||||
},
|
||||
loadingClass: "px-4 py-4 text-text-weak",
|
||||
emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
|
||||
})}
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={params.id}>
|
||||
<Show when={messagesReady() ? params.id : undefined} keyed>
|
||||
{(_id) => (
|
||||
<MessageTimeline
|
||||
actions={actions}
|
||||
scroll={ui.scroll}
|
||||
onResumeScroll={resumeScroll}
|
||||
setScrollRef={setScrollRef}
|
||||
onScheduleScrollState={scheduleScrollState}
|
||||
onAutoScrollHandleScroll={autoScroll.handleScroll}
|
||||
onMarkScrollGesture={markScrollGesture}
|
||||
hasScrollGesture={hasScrollGesture}
|
||||
onUserScroll={markUserScroll}
|
||||
onHistoryScroll={onHistoryScroll}
|
||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||
shouldAnchorBottom={() =>
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
}
|
||||
centered={centered()}
|
||||
setContentRef={(el) => {
|
||||
content = el
|
||||
autoScroll.contentRef(el)
|
||||
|
||||
const root = scroller
|
||||
if (root) scheduleScrollState(root)
|
||||
}}
|
||||
userMessages={visibleUserMessages()}
|
||||
setHistoryAnchor={(handlers) => {
|
||||
captureHistoryAnchor = handlers.capture
|
||||
restoreHistoryAnchor = handlers.restore
|
||||
}}
|
||||
anchor={anchor}
|
||||
setRevealMessage={(fn) => {
|
||||
revealMessage = fn
|
||||
}}
|
||||
setScrollToEnd={(fn) => {
|
||||
scrollToEnd = fn
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<NewSessionView worktree={newSessionWorktree()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<Show when={(params.id || !newSessionDesign()) && !mobileChanges()}>{(_) => composerRegion()}</Show>
|
||||
<Show when={!!params.id && mobileTabsBottom()}>{mobileTabs(true, true)}</Show>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<SessionRouteFrame>
|
||||
<SessionHeader />
|
||||
<div
|
||||
class="flex-1 min-h-0 flex flex-col md:flex-row"
|
||||
@@ -1717,85 +2014,13 @@ export default function Page() {
|
||||
width: sessionPanelWidth(),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"flex-1 min-h-0 flex flex-col": true,
|
||||
"bg-v2-background-bg-base": settings.general.newLayoutDesigns(),
|
||||
"bg-background-stronger": !settings.general.newLayoutDesigns(),
|
||||
"rounded-[10px] overflow-hidden": settings.general.newLayoutDesigns(),
|
||||
"shadow-[var(--v2-elevation-raised)]": settings.general.newLayoutDesigns() && !!params.id,
|
||||
}}
|
||||
>
|
||||
<Show when={!isDesktop() && !!params.id && settings.general.newLayoutDesigns() && !mobileTabsBottom()}>
|
||||
{mobileTabs(true)}
|
||||
</Show>
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
<Switch>
|
||||
<Match when={params.id && mobileChanges()}>
|
||||
<div class="relative h-full overflow-hidden">
|
||||
{reviewContent({
|
||||
diffStyle: "unified",
|
||||
classes: {
|
||||
root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
|
||||
header: "px-4 !h-16 !pb-4",
|
||||
container: "px-4",
|
||||
},
|
||||
loadingClass: "px-4 py-4 text-text-weak",
|
||||
emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
|
||||
})}
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={params.id}>
|
||||
<Show when={messagesReady() ? params.id : undefined} keyed>
|
||||
{(_id) => (
|
||||
<MessageTimeline
|
||||
actions={actions}
|
||||
scroll={ui.scroll}
|
||||
onResumeScroll={resumeScroll}
|
||||
setScrollRef={setScrollRef}
|
||||
onScheduleScrollState={scheduleScrollState}
|
||||
onAutoScrollHandleScroll={autoScroll.handleScroll}
|
||||
onMarkScrollGesture={markScrollGesture}
|
||||
hasScrollGesture={hasScrollGesture}
|
||||
onUserScroll={markUserScroll}
|
||||
onHistoryScroll={onHistoryScroll}
|
||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||
shouldAnchorBottom={() =>
|
||||
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||
}
|
||||
centered={centered()}
|
||||
setContentRef={(el) => {
|
||||
content = el
|
||||
autoScroll.contentRef(el)
|
||||
|
||||
const root = scroller
|
||||
if (root) scheduleScrollState(root)
|
||||
}}
|
||||
userMessages={visibleUserMessages()}
|
||||
setHistoryAnchor={(handlers) => {
|
||||
captureHistoryAnchor = handlers.capture
|
||||
restoreHistoryAnchor = handlers.restore
|
||||
}}
|
||||
anchor={anchor}
|
||||
setRevealMessage={(fn) => {
|
||||
revealMessage = fn
|
||||
}}
|
||||
setScrollToEnd={(fn) => {
|
||||
scrollToEnd = fn
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<NewSessionView worktree={newSessionWorktree()} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<Show when={(params.id || !newSessionDesign()) && !mobileChanges()}>{(_) => composerRegion()}</Show>
|
||||
<Show when={!!params.id && mobileTabsBottom()}>{mobileTabs(true, true)}</Show>
|
||||
</div>
|
||||
<SessionPanelFrame newLayout={settings.general.newLayoutDesigns()} raised={!!params.id}>
|
||||
{settings.general.newLayoutDesigns() ? (
|
||||
<ErrorBoundary fallback={sessionErrorFallback}>{sessionPanelContent()}</ErrorBoundary>
|
||||
) : (
|
||||
sessionPanelContent()
|
||||
)}
|
||||
</SessionPanelFrame>
|
||||
|
||||
<Show when={desktopReviewOpen()}>
|
||||
<div onPointerDown={() => size.start()}>
|
||||
@@ -1832,6 +2057,6 @@ export default function Page() {
|
||||
</div>
|
||||
|
||||
<TerminalPanel />
|
||||
</div>
|
||||
</SessionRouteFrame>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export function SessionComposerRegion(props: {
|
||||
</Show>
|
||||
<div
|
||||
classList={{
|
||||
"relative z-30": true,
|
||||
"relative z-[70]": true,
|
||||
}}
|
||||
style={{
|
||||
"margin-top": `${-controller.lift()}px`,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { For, Show, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
||||
import { For, Show, createEffect, createMemo, onCleanup, onMount, type Component } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useMutation } from "@tanstack/solid-query"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { useSpring } from "@opencode-ai/ui/motion-spring"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import { useLanguage } from "@/context/language"
|
||||
@@ -77,9 +78,12 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
customOn: cached?.customOn ?? ([] as boolean[]),
|
||||
editing: false,
|
||||
focus: 0,
|
||||
minimized: false,
|
||||
optionsHeight: 180,
|
||||
})
|
||||
|
||||
let root: HTMLDivElement | undefined
|
||||
let optionsRef: HTMLDivElement | undefined
|
||||
let customRef: HTMLButtonElement | undefined
|
||||
let optsRef: HTMLButtonElement[] = []
|
||||
let replied = false
|
||||
@@ -96,11 +100,13 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
const n = Math.min(store.tab + 1, total())
|
||||
return language.t("session.question.progress", { current: n, total: total() })
|
||||
})
|
||||
|
||||
const customLabel = () => language.t("ui.messagePart.option.typeOwnAnswer")
|
||||
const customPlaceholder = () => language.t("ui.question.custom.placeholder")
|
||||
|
||||
const last = createMemo(() => store.tab >= total() - 1)
|
||||
const collapse = useSpring(() => (store.minimized ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
|
||||
const hidden = createMemo(() => Math.max(0, Math.min(1, collapse())))
|
||||
const optionsOff = createMemo(() => hidden() > 0.98)
|
||||
|
||||
const customUpdate = (value: string, selected: boolean = on()) => {
|
||||
const prev = input().trim()
|
||||
@@ -192,6 +198,14 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
focus(pickFocus())
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const el = optionsRef
|
||||
if (!el) return
|
||||
const update = () => setStore("optionsHeight", (height) => Math.max(height, el.scrollHeight))
|
||||
update()
|
||||
createResizeObserver(el, update)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (focusFrame !== undefined) cancelAnimationFrame(focusFrame)
|
||||
if (replied) return
|
||||
@@ -405,7 +419,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
const tab = store.tab + 1
|
||||
setStore("tab", tab)
|
||||
setStore("editing", false)
|
||||
focus(pickFocus(tab))
|
||||
if (!store.minimized) focus(pickFocus(tab))
|
||||
}
|
||||
|
||||
const back = () => {
|
||||
@@ -414,163 +428,212 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
const tab = store.tab - 1
|
||||
setStore("tab", tab)
|
||||
setStore("editing", false)
|
||||
focus(pickFocus(tab))
|
||||
if (!store.minimized) focus(pickFocus(tab))
|
||||
}
|
||||
|
||||
const jump = (tab: number) => {
|
||||
if (sending()) return
|
||||
setStore("tab", tab)
|
||||
setStore("editing", false)
|
||||
focus(pickFocus(tab))
|
||||
if (!store.minimized) focus(pickFocus(tab))
|
||||
}
|
||||
|
||||
const minimize = () => {
|
||||
if (sending()) return
|
||||
setStore("editing", false)
|
||||
setStore("minimized", true)
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
if (sending()) return
|
||||
setStore("minimized", false)
|
||||
focus(pickFocus())
|
||||
}
|
||||
|
||||
return (
|
||||
<DockPrompt
|
||||
kind="question"
|
||||
ref={(el) => (root = el)}
|
||||
onKeyDown={nav}
|
||||
header={
|
||||
<>
|
||||
<div data-slot="question-header-title">{summary()}</div>
|
||||
<Show when={total() > 1}>
|
||||
<div data-slot="question-progress">
|
||||
<For each={questions()}>
|
||||
{(_, i) => (
|
||||
<button
|
||||
type="button"
|
||||
data-slot="question-progress-segment"
|
||||
data-active={i() === store.tab}
|
||||
data-answered={answered(i())}
|
||||
disabled={sending()}
|
||||
onClick={() => jump(i())}
|
||||
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<div data-component="session-question-dock">
|
||||
<DockPrompt
|
||||
kind="question"
|
||||
ref={(el) => (root = el)}
|
||||
onKeyDown={nav}
|
||||
header={
|
||||
<>
|
||||
<div data-slot="question-header-title">{summary()}</div>
|
||||
<div data-slot="question-header-actions">
|
||||
<Show when={total() > 1}>
|
||||
<div data-slot="question-progress">
|
||||
<For each={questions()}>
|
||||
{(_, i) => (
|
||||
<button
|
||||
type="button"
|
||||
data-slot="question-progress-segment"
|
||||
data-active={i() === store.tab}
|
||||
data-answered={answered(i())}
|
||||
disabled={sending()}
|
||||
onClick={() => jump(i())}
|
||||
aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
data-component="icon-button"
|
||||
data-icon="chevron-down"
|
||||
data-size="normal"
|
||||
data-variant="ghost"
|
||||
disabled={sending()}
|
||||
style={{ transform: `rotate(${hidden() * 180}deg)` }}
|
||||
onClick={store.minimized ? restore : minimize}
|
||||
aria-label={language.t(store.minimized ? "session.question.restore" : "session.question.minimize")}
|
||||
>
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" size="large" disabled={sending()} onClick={reject} aria-keyshortcuts="Escape">
|
||||
{language.t("ui.common.dismiss")}
|
||||
</Button>
|
||||
<div data-slot="question-footer-actions">
|
||||
<Show when={store.tab > 0}>
|
||||
<Button variant="secondary" size="large" disabled={sending()} onClick={back}>
|
||||
{language.t("ui.common.back")}
|
||||
</Button>
|
||||
</Show>
|
||||
<Button
|
||||
variant={last() ? "primary" : "secondary"}
|
||||
size="large"
|
||||
disabled={sending()}
|
||||
onClick={next}
|
||||
aria-keyshortcuts="Meta+Enter Control+Enter"
|
||||
>
|
||||
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="ghost" size="large" disabled={sending()} onClick={reject} aria-keyshortcuts="Escape">
|
||||
{language.t("ui.common.dismiss")}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div data-slot="question-text" class="overflow-auto">
|
||||
{question()?.question}
|
||||
</div>
|
||||
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
|
||||
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
|
||||
</Show>
|
||||
<div data-slot="question-options">
|
||||
<For each={options()}>
|
||||
{(opt, i) => (
|
||||
<Option
|
||||
multi={multi()}
|
||||
picked={picked(opt.label)}
|
||||
label={opt.label}
|
||||
description={opt.description}
|
||||
disabled={sending()}
|
||||
ref={(el) => (optsRef[i()] = el)}
|
||||
onFocus={() => setStore("focus", i())}
|
||||
onClick={() => selectOption(i())}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
<div data-slot="question-footer-actions">
|
||||
<Show when={store.tab > 0}>
|
||||
<Button variant="secondary" size="large" disabled={sending()} onClick={back}>
|
||||
{language.t("ui.common.back")}
|
||||
</Button>
|
||||
</Show>
|
||||
<Button
|
||||
variant={last() ? "primary" : "secondary"}
|
||||
size="large"
|
||||
disabled={sending()}
|
||||
onClick={next}
|
||||
aria-keyshortcuts="Meta+Enter Control+Enter"
|
||||
>
|
||||
{last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div
|
||||
data-slot="question-text"
|
||||
style={{
|
||||
display: store.minimized ? "-webkit-box" : undefined,
|
||||
"-webkit-line-clamp": store.minimized ? "3" : undefined,
|
||||
"-webkit-box-orient": store.minimized ? "vertical" : undefined,
|
||||
overflow: store.minimized ? "hidden" : undefined,
|
||||
}}
|
||||
>
|
||||
{question()?.question}
|
||||
</div>
|
||||
<Show when={!store.minimized}>
|
||||
<Show when={multi()} fallback={<div data-slot="question-hint">{language.t("ui.question.singleHint")}</div>}>
|
||||
<div data-slot="question-hint">{language.t("ui.question.multiHint")}</div>
|
||||
</Show>
|
||||
</Show>
|
||||
<div
|
||||
ref={(el) => (optionsRef = el)}
|
||||
data-slot="question-options"
|
||||
aria-hidden={store.minimized || optionsOff() ? "true" : undefined}
|
||||
classList={{ "pointer-events-none": hidden() > 0.1 }}
|
||||
style={{
|
||||
"max-height": `${Math.max(0, store.optionsHeight * (1 - hidden()))}px`,
|
||||
opacity: `${Math.max(0, Math.min(1, 1 - hidden()))}`,
|
||||
visibility: optionsOff() ? "hidden" : "visible",
|
||||
}}
|
||||
>
|
||||
<For each={options()}>
|
||||
{(opt, i) => (
|
||||
<Option
|
||||
multi={multi()}
|
||||
picked={picked(opt.label)}
|
||||
label={opt.label}
|
||||
description={opt.description}
|
||||
disabled={sending()}
|
||||
ref={(el) => (optsRef[i()] = el)}
|
||||
onFocus={() => setStore("focus", i())}
|
||||
onClick={() => selectOption(i())}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<Show
|
||||
when={store.editing}
|
||||
fallback={
|
||||
<button
|
||||
type="button"
|
||||
ref={customRef}
|
||||
<Show
|
||||
when={store.editing}
|
||||
fallback={
|
||||
<button
|
||||
type="button"
|
||||
ref={customRef}
|
||||
data-slot="question-option"
|
||||
data-custom="true"
|
||||
data-picked={on()}
|
||||
role={multi() ? "checkbox" : "radio"}
|
||||
aria-checked={on()}
|
||||
disabled={sending()}
|
||||
onFocus={() => setStore("focus", options().length)}
|
||||
onClick={customOpen}
|
||||
>
|
||||
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
||||
<span data-slot="question-option-main">
|
||||
<span data-slot="option-label">{customLabel()}</span>
|
||||
<span data-slot="option-description">{input() || customPlaceholder()}</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form
|
||||
data-slot="question-option"
|
||||
data-custom="true"
|
||||
data-picked={on()}
|
||||
role={multi() ? "checkbox" : "radio"}
|
||||
aria-checked={on()}
|
||||
disabled={sending()}
|
||||
onFocus={() => setStore("focus", options().length)}
|
||||
onClick={customOpen}
|
||||
onMouseDown={(e) => {
|
||||
if (sending()) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
if (e.target instanceof HTMLTextAreaElement) return
|
||||
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
|
||||
if (input instanceof HTMLTextAreaElement) input.focus()
|
||||
}}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
commitCustom()
|
||||
}}
|
||||
>
|
||||
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
||||
<span data-slot="question-option-main">
|
||||
<span data-slot="option-label">{customLabel()}</span>
|
||||
<span data-slot="option-description">{input() || customPlaceholder()}</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<form
|
||||
data-slot="question-option"
|
||||
data-custom="true"
|
||||
data-picked={on()}
|
||||
role={multi() ? "checkbox" : "radio"}
|
||||
aria-checked={on()}
|
||||
onMouseDown={(e) => {
|
||||
if (sending()) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
if (e.target instanceof HTMLTextAreaElement) return
|
||||
const input = e.currentTarget.querySelector('[data-slot="question-custom-input"]')
|
||||
if (input instanceof HTMLTextAreaElement) input.focus()
|
||||
}}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
commitCustom()
|
||||
}}
|
||||
>
|
||||
<Mark multi={multi()} picked={on()} onClick={toggleCustomMark} />
|
||||
<span data-slot="question-option-main">
|
||||
<span data-slot="option-label">{customLabel()}</span>
|
||||
<textarea
|
||||
ref={focusCustom}
|
||||
data-slot="question-custom-input"
|
||||
placeholder={customPlaceholder()}
|
||||
value={input()}
|
||||
rows={1}
|
||||
disabled={sending()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
<textarea
|
||||
ref={focusCustom}
|
||||
data-slot="question-custom-input"
|
||||
placeholder={customPlaceholder()}
|
||||
value={input()}
|
||||
rows={1}
|
||||
disabled={sending()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
setStore("editing", false)
|
||||
focus(options().length)
|
||||
return
|
||||
}
|
||||
if ((e.metaKey || e.ctrlKey) && !e.altKey) return
|
||||
if (e.key !== "Enter" || e.shiftKey) return
|
||||
e.preventDefault()
|
||||
setStore("editing", false)
|
||||
focus(options().length)
|
||||
return
|
||||
}
|
||||
if ((e.metaKey || e.ctrlKey) && !e.altKey) return
|
||||
if (e.key !== "Enter" || e.shiftKey) return
|
||||
e.preventDefault()
|
||||
commitCustom()
|
||||
}}
|
||||
onInput={(e) => {
|
||||
customUpdate(e.currentTarget.value)
|
||||
resizeInput(e.currentTarget)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</form>
|
||||
</Show>
|
||||
</div>
|
||||
</DockPrompt>
|
||||
commitCustom()
|
||||
}}
|
||||
onInput={(e) => {
|
||||
customUpdate(e.currentTarget.value)
|
||||
resizeInput(e.currentTarget)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</form>
|
||||
</Show>
|
||||
</div>
|
||||
</DockPrompt>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,9 +31,14 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { DialogFooter, DialogHeader, DialogTitleGroup, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { SessionRetry } from "@opencode-ai/session-ui/session-retry"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||
@@ -671,6 +676,34 @@ export function MessageTimeline(props: {
|
||||
if (!shareEnabled()) return
|
||||
unshareMutation.mutate(id)
|
||||
}
|
||||
const copyShareUrl = () => {
|
||||
const url = shareUrl()
|
||||
if (!url) return
|
||||
void navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() =>
|
||||
showToast({
|
||||
variant: "success",
|
||||
icon: "circle-check",
|
||||
title: language.t("session.share.copy.copied"),
|
||||
description: url,
|
||||
}),
|
||||
)
|
||||
.catch((err: unknown) =>
|
||||
showToast({
|
||||
title: language.t("common.requestFailed"),
|
||||
description: errorMessage(err),
|
||||
}),
|
||||
)
|
||||
}
|
||||
const selectShareUrlText: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {
|
||||
const selection = window.getSelection()
|
||||
if (!selection) return
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(event.currentTarget)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
@@ -703,8 +736,9 @@ export function MessageTimeline(props: {
|
||||
if (!sessionID() || parentID()) return
|
||||
setTitle({ editing: true, draft: titleLabel() ?? "" })
|
||||
requestAnimationFrame(() => {
|
||||
titleRef?.focus()
|
||||
titleRef?.select()
|
||||
if (!titleRef) return
|
||||
titleRef.focus()
|
||||
titleRef.select()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -856,6 +890,26 @@ export function MessageTimeline(props: {
|
||||
dialog.close()
|
||||
}
|
||||
|
||||
if (settings.general.newLayoutDesigns())
|
||||
return (
|
||||
<DialogV2 fit>
|
||||
<DialogHeader hideClose>
|
||||
<DialogTitleGroup
|
||||
title={language.t("session.delete.title")}
|
||||
description={language.t("session.delete.confirm", { name: name() })}
|
||||
/>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="ghost" onClick={() => dialog.close()}>
|
||||
{language.t("common.cancel")}
|
||||
</ButtonV2>
|
||||
<ButtonV2 variant="danger" onClick={handleDelete}>
|
||||
{language.t("session.delete.button")}
|
||||
</ButtonV2>
|
||||
</DialogFooter>
|
||||
</DialogV2>
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog title={language.t("session.delete.title")} fit>
|
||||
<div class="flex flex-col gap-4 pl-6 pr-2.5 pb-3">
|
||||
@@ -960,6 +1014,7 @@ export function MessageTimeline(props: {
|
||||
message={message()}
|
||||
showAssistantCopyPartID={assistantCopyPartID(row().userMessageID)}
|
||||
turnDurationMs={turnDurationMs(row().userMessageID)}
|
||||
useV2Actions={settings.general.newLayoutDesigns()}
|
||||
defaultOpen={defaultOpen()}
|
||||
toolOpen={toolOpen[part().id] ?? defaultOpen()}
|
||||
onToolOpenChange={(open) => setToolOpen(part().id, open)}
|
||||
@@ -1067,6 +1122,7 @@ export function MessageTimeline(props: {
|
||||
message={message()}
|
||||
parts={getMsgParts(userMessageRow().userMessageID)}
|
||||
actions={props.actions}
|
||||
useV2Actions={settings.general.newLayoutDesigns()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1302,26 +1358,31 @@ export function MessageTimeline(props: {
|
||||
"w-full": true,
|
||||
"pb-4": true,
|
||||
"pr-3": true,
|
||||
"pl-4": settings.general.newLayoutDesigns(),
|
||||
"pl-2": settings.general.newLayoutDesigns(),
|
||||
"pl-2 md:pl-4": !settings.general.newLayoutDesigns(),
|
||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
|
||||
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered && !settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
>
|
||||
<div class="h-12 w-full flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-1 min-w-0 flex-1 pr-3">
|
||||
<div class="flex items-center min-w-0 grow-1">
|
||||
<div
|
||||
classList={{
|
||||
"flex items-center gap-1 min-w-0 flex-1": true,
|
||||
"pr-3": !settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center min-w-0 flex-1 w-full">
|
||||
<Show when={parentID()}>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="session-title-parent"
|
||||
class="min-w-0 max-w-[40%] truncate text-14-medium text-text-weak transition-colors hover:text-text-base"
|
||||
class="min-w-0 max-w-[40%] truncate px-2 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-faint transition-colors hover:text-v2-text-text-muted"
|
||||
onClick={navigateParent}
|
||||
>
|
||||
{parentTitle()}
|
||||
</button>
|
||||
<span
|
||||
data-slot="session-title-separator"
|
||||
class="px-2 text-14-medium text-text-weak"
|
||||
class="-translate-y-[0.5px] px-1 text-[11px] font-medium text-v2-text-text-faint"
|
||||
aria-hidden="true"
|
||||
>
|
||||
/
|
||||
@@ -1333,8 +1394,13 @@ export function MessageTimeline(props: {
|
||||
fallback={
|
||||
<h1
|
||||
data-slot="session-title-child"
|
||||
class="text-14-medium text-text-strong truncate grow-1 min-w-0"
|
||||
onDblClick={openTitleEditor}
|
||||
classList={{
|
||||
"truncate text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base": true,
|
||||
"w-fit rounded-[6px] px-2 py-1 hover:bg-v2-overlay-simple-overlay-hover":
|
||||
settings.general.newLayoutDesigns(),
|
||||
"grow-1 min-w-0": !settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
onClick={openTitleEditor}
|
||||
>
|
||||
{childTitle()}
|
||||
</h1>
|
||||
@@ -1347,8 +1413,17 @@ export function MessageTimeline(props: {
|
||||
data-slot="session-title-child"
|
||||
value={title.draft}
|
||||
disabled={titleMutation.isPending}
|
||||
class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px] pl-1 -ml-1"
|
||||
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
||||
classList={{
|
||||
"block text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base": true,
|
||||
"w-full flex-1 grow-1 min-w-0 pl-1 -ml-1 rounded-[6px]": !settings.general.newLayoutDesigns(),
|
||||
"field-sizing-content self-start rounded-[6px] px-2 py-1 ":
|
||||
settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
style={{
|
||||
"--inline-input-shadow": settings.general.newLayoutDesigns()
|
||||
? "none"
|
||||
: "var(--shadow-xs-border-select)",
|
||||
}}
|
||||
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation()
|
||||
@@ -1370,88 +1445,170 @@ export function MessageTimeline(props: {
|
||||
</div>
|
||||
<Show when={sessionID()} keyed>
|
||||
{(id) => (
|
||||
<div class="shrink-0 flex items-center gap-3">
|
||||
<SessionContextUsage placement="bottom" />
|
||||
<div
|
||||
classList={{
|
||||
"shrink-0 flex items-center": true,
|
||||
"gap-2": settings.general.newLayoutDesigns(),
|
||||
"gap-3": !settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
>
|
||||
<SessionContextUsage
|
||||
placement="bottom"
|
||||
buttonAppearance={settings.general.newLayoutDesigns() ? "v2" : "default"}
|
||||
/>
|
||||
<Show when={!parentID()}>
|
||||
<DropdownMenu
|
||||
gutter={4}
|
||||
placement="bottom-end"
|
||||
open={title.menuOpen}
|
||||
onOpenChange={(open) => {
|
||||
setTitle("menuOpen", open)
|
||||
if (open) return
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
icon="dot-grid"
|
||||
variant="ghost"
|
||||
class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
||||
classList={{
|
||||
"bg-surface-base-active": share.open || title.pendingShare,
|
||||
}}
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
aria-expanded={title.menuOpen || share.open || title.pendingShare}
|
||||
ref={(el: HTMLButtonElement) => {
|
||||
more = el
|
||||
}}
|
||||
/>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
style={{ "min-width": "104px" }}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (title.pendingRename) {
|
||||
event.preventDefault()
|
||||
setTitle("pendingRename", false)
|
||||
openTitleEditor()
|
||||
return
|
||||
}
|
||||
if (title.pendingShare) {
|
||||
event.preventDefault()
|
||||
requestAnimationFrame(() => {
|
||||
setShare({ open: true, dismiss: null })
|
||||
setTitle("pendingShare", false)
|
||||
})
|
||||
}
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={
|
||||
<DropdownMenu
|
||||
gutter={4}
|
||||
placement="bottom-end"
|
||||
open={title.menuOpen}
|
||||
onOpenChange={(open) => {
|
||||
setTitle("menuOpen", open)
|
||||
if (open) return
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setTitle("pendingRename", true)
|
||||
setTitle("menuOpen", false)
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
icon="dot-grid"
|
||||
variant="ghost"
|
||||
class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
||||
classList={{
|
||||
"bg-surface-base-active": share.open || title.pendingShare,
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
<Show when={shareEnabled()}>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setTitle({ pendingShare: true, menuOpen: false })
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
aria-expanded={title.menuOpen || share.open || title.pendingShare}
|
||||
ref={(el: HTMLButtonElement) => {
|
||||
more = el
|
||||
}}
|
||||
/>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
style={{ "min-width": "104px" }}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (title.pendingRename) {
|
||||
event.preventDefault()
|
||||
setTitle("pendingRename", false)
|
||||
openTitleEditor()
|
||||
return
|
||||
}
|
||||
if (title.pendingShare) {
|
||||
event.preventDefault()
|
||||
requestAnimationFrame(() => {
|
||||
setShare({ open: true, dismiss: null })
|
||||
setTitle("pendingShare", false)
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>
|
||||
{language.t("session.share.action.share")}
|
||||
</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</Show>
|
||||
<DropdownMenu.Item onSelect={() => void archiveSession(id)}>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.archive")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => dialog.show(() => <DialogDeleteSession sessionID={id} />)}
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setTitle("pendingRename", true)
|
||||
setTitle("menuOpen", false)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
<Show when={shareEnabled()}>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setTitle({ pendingShare: true, menuOpen: false })
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>
|
||||
{language.t("session.share.action.share")}
|
||||
</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</Show>
|
||||
<DropdownMenu.Item onSelect={() => void archiveSession(id)}>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.archive")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => dialog.show(() => <DialogDeleteSession sessionID={id} />)}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
}
|
||||
>
|
||||
<MenuV2
|
||||
gutter={6}
|
||||
placement="bottom-end"
|
||||
open={title.menuOpen}
|
||||
onOpenChange={(open) => {
|
||||
setTitle("menuOpen", open)
|
||||
if (open) return
|
||||
}}
|
||||
>
|
||||
<MenuV2.Trigger
|
||||
as={IconButtonV2}
|
||||
icon={<IconV2 name="outline-dots" />}
|
||||
variant="ghost-muted"
|
||||
size="large"
|
||||
state={share.open || title.pendingShare ? "pressed" : undefined}
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
aria-expanded={title.menuOpen || share.open || title.pendingShare}
|
||||
ref={(el: HTMLButtonElement) => {
|
||||
more = el
|
||||
}}
|
||||
/>
|
||||
<MenuV2.Portal>
|
||||
<MenuV2.Content
|
||||
style={{ width: "120px", "min-width": "120px" }}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (title.pendingRename) {
|
||||
event.preventDefault()
|
||||
setTitle("pendingRename", false)
|
||||
openTitleEditor()
|
||||
return
|
||||
}
|
||||
if (title.pendingShare) {
|
||||
event.preventDefault()
|
||||
requestAnimationFrame(() => {
|
||||
setShare({ open: true, dismiss: null })
|
||||
setTitle("pendingShare", false)
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
<MenuV2.Item
|
||||
onSelect={() => {
|
||||
setTitle("pendingRename", true)
|
||||
setTitle("menuOpen", false)
|
||||
}}
|
||||
>
|
||||
{language.t("common.rename")}
|
||||
</MenuV2.Item>
|
||||
<Show when={shareEnabled()}>
|
||||
<MenuV2.Item
|
||||
onSelect={() => {
|
||||
setTitle({ pendingShare: true, menuOpen: false })
|
||||
}}
|
||||
>
|
||||
{language.t("session.share.action.share")}...
|
||||
</MenuV2.Item>
|
||||
</Show>
|
||||
<MenuV2.Item onSelect={() => void archiveSession(id)}>
|
||||
{language.t("common.archive")}
|
||||
</MenuV2.Item>
|
||||
<MenuV2.Separator />
|
||||
<MenuV2.Item onSelect={() => dialog.show(() => <DialogDeleteSession sessionID={id} />)}>
|
||||
{language.t("common.delete")}...
|
||||
</MenuV2.Item>
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
</Show>
|
||||
|
||||
<KobaltePopover
|
||||
open={share.open}
|
||||
anchorRef={() => more}
|
||||
placement="bottom-end"
|
||||
gutter={4}
|
||||
gutter={settings.general.newLayoutDesigns() ? 6 : 4}
|
||||
modal={false}
|
||||
onOpenChange={(open) => {
|
||||
if (open) setShare("dismiss", null)
|
||||
@@ -1461,6 +1618,10 @@ export function MessageTimeline(props: {
|
||||
<KobaltePopover.Portal>
|
||||
<KobaltePopover.Content
|
||||
data-component="popover-content"
|
||||
classList={{
|
||||
"flex w-80 max-w-none flex-col items-start gap-3 rounded-[10px] border-0 bg-v2-background-bg-layer-01 p-3 shadow-[var(--v2-elevation-floating)]":
|
||||
settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
style={{ "min-width": "320px" }}
|
||||
onEscapeKeyDown={(event) => {
|
||||
setShare({ dismiss: "escape", open: false })
|
||||
@@ -1478,24 +1639,90 @@ export function MessageTimeline(props: {
|
||||
setShare("dismiss", null)
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col p-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-13-medium text-text-strong">
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={
|
||||
<div class="flex flex-col p-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-13-medium text-text-strong">
|
||||
{language.t("session.share.popover.title")}
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak">
|
||||
{shareUrl()
|
||||
? language.t("session.share.popover.description.shared")
|
||||
: language.t("session.share.popover.description.unshared")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-col gap-2">
|
||||
<Show
|
||||
when={shareUrl()}
|
||||
fallback={
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-full"
|
||||
onClick={shareSession}
|
||||
disabled={shareMutation.isPending}
|
||||
>
|
||||
{shareMutation.isPending
|
||||
? language.t("session.share.action.publishing")
|
||||
: language.t("session.share.action.publish")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<TextField
|
||||
value={shareUrl() ?? ""}
|
||||
readOnly
|
||||
copyable
|
||||
copyKind="link"
|
||||
tabIndex={-1}
|
||||
class="w-full"
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
size="large"
|
||||
variant="secondary"
|
||||
class="w-full shadow-none border border-border-weak-base"
|
||||
onClick={unshareSession}
|
||||
disabled={unshareMutation.isPending}
|
||||
>
|
||||
{unshareMutation.isPending
|
||||
? language.t("session.share.action.unpublishing")
|
||||
: language.t("session.share.action.unpublish")}
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-full"
|
||||
onClick={viewShare}
|
||||
disabled={unshareMutation.isPending}
|
||||
>
|
||||
{language.t("session.share.action.view")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="flex w-full flex-col gap-1.5 px-0.5 pt-0.5">
|
||||
<div class="select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base [font-variation-settings:'slnt'_0]">
|
||||
{language.t("session.share.popover.title")}
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak">
|
||||
<div class="select-none text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-variation-settings:'slnt'_0]">
|
||||
{shareUrl()
|
||||
? language.t("session.share.popover.description.shared")
|
||||
: language.t("session.share.popover.description.unshared")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-col gap-2">
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<Show
|
||||
when={shareUrl()}
|
||||
fallback={
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
<ButtonV2
|
||||
variant="contrast"
|
||||
class="w-full"
|
||||
onClick={shareSession}
|
||||
disabled={shareMutation.isPending}
|
||||
@@ -1503,48 +1730,57 @@ export function MessageTimeline(props: {
|
||||
{shareMutation.isPending
|
||||
? language.t("session.share.action.publishing")
|
||||
: language.t("session.share.action.publish")}
|
||||
</Button>
|
||||
</ButtonV2>
|
||||
}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<TextField
|
||||
value={shareUrl() ?? ""}
|
||||
readOnly
|
||||
copyable
|
||||
copyKind="link"
|
||||
tabIndex={-1}
|
||||
class="w-full"
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
size="large"
|
||||
variant="secondary"
|
||||
class={
|
||||
settings.general.newLayoutDesigns()
|
||||
? "w-full shadow-none border-[0.5px] border-border-weak-base"
|
||||
: "w-full shadow-none border border-border-weak-base"
|
||||
}
|
||||
<div
|
||||
class="flex h-8 w-full items-center gap-1.5 rounded-[6px] py-1 pl-2.5 pr-1.5 shadow-[var(--v2-elevation-button-neutral)]"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(180deg, var(--v2-alpha-light-2) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-button-neutral)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="min-w-0 flex-1 truncate select-text cursor-text text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-variation-settings:'slnt'_0]"
|
||||
onClick={selectShareUrlText}
|
||||
>
|
||||
{shareUrl()}
|
||||
</div>
|
||||
<IconButtonV2
|
||||
type="button"
|
||||
size="small"
|
||||
variant="ghost-muted"
|
||||
icon={<IconV2 name="outline-copy" />}
|
||||
aria-label={language.t("session.share.copy.copyLink")}
|
||||
onClick={copyShareUrl}
|
||||
/>
|
||||
<IconButtonV2
|
||||
type="button"
|
||||
size="small"
|
||||
variant="ghost-muted"
|
||||
icon={<IconV2 name="outline-square-arrow" />}
|
||||
aria-label={language.t("session.share.action.view")}
|
||||
onClick={viewShare}
|
||||
disabled={unshareMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<ButtonV2
|
||||
variant="outline"
|
||||
class="w-full"
|
||||
onClick={unshareSession}
|
||||
disabled={unshareMutation.isPending}
|
||||
>
|
||||
{unshareMutation.isPending
|
||||
? language.t("session.share.action.unpublishing")
|
||||
: language.t("session.share.action.unpublish")}
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
variant="primary"
|
||||
class="w-full"
|
||||
onClick={viewShare}
|
||||
disabled={unshareMutation.isPending}
|
||||
>
|
||||
{language.t("session.share.action.view")}
|
||||
</Button>
|
||||
</ButtonV2>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</KobaltePopover.Content>
|
||||
</KobaltePopover.Portal>
|
||||
</KobaltePopover>
|
||||
|
||||
@@ -16,6 +16,7 @@ type PersistedWithReady<T> = [
|
||||
|
||||
type PersistTarget = {
|
||||
storage?: string
|
||||
scope?: "window"
|
||||
legacyStorageNames?: string[]
|
||||
key: string
|
||||
legacy?: string[]
|
||||
@@ -24,6 +25,7 @@ type PersistTarget = {
|
||||
|
||||
const LEGACY_STORAGE = "default.dat"
|
||||
const GLOBAL_STORAGE = "opencode.global.dat"
|
||||
const WINDOW_STORAGE = "opencode.window"
|
||||
const LOCAL_PREFIX = "opencode."
|
||||
const fallback = new Map<string, boolean>()
|
||||
|
||||
@@ -347,6 +349,11 @@ function draftStorage(draftID: string) {
|
||||
return `opencode.draft.${head}.${sum}.dat`
|
||||
}
|
||||
|
||||
function windowStorage(windowID: string) {
|
||||
const safe = (windowID || "browser").replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||
return `${WINDOW_STORAGE}.${safe}.dat`
|
||||
}
|
||||
|
||||
function legacyWorkspaceStorage(dir: string) {
|
||||
const storage = workspaceStorage(pathKey(dir))
|
||||
const result = new Set<string>()
|
||||
@@ -467,6 +474,8 @@ export const PersistTesting = {
|
||||
localStorageWithPrefix,
|
||||
migrateLegacy,
|
||||
normalize,
|
||||
resolveTarget,
|
||||
windowStorage,
|
||||
workspaceStorage,
|
||||
}
|
||||
|
||||
@@ -474,6 +483,9 @@ export const Persist = {
|
||||
global(key: string, legacy?: string[]): PersistTarget {
|
||||
return { storage: GLOBAL_STORAGE, key, legacy }
|
||||
},
|
||||
window(key: string, legacy?: string[]): PersistTarget {
|
||||
return { scope: "window", key, legacy }
|
||||
},
|
||||
draft(draftID: string, key: string, legacy?: string[]): PersistTarget {
|
||||
return { storage: draftStorage(draftID), key: `draft:${key}`, legacy }
|
||||
},
|
||||
@@ -503,6 +515,16 @@ export const Persist = {
|
||||
},
|
||||
}
|
||||
|
||||
function resolveTarget(target: PersistTarget, platform: Platform): PersistTarget {
|
||||
if (target.scope !== "window") return target
|
||||
if (platform.platform === "desktop" && !platform.windowID) return { ...target, storage: GLOBAL_STORAGE }
|
||||
const windowID = platform.platform === "desktop" ? (platform.windowID ?? "browser") : "browser"
|
||||
return {
|
||||
...target,
|
||||
storage: windowStorage(windowID),
|
||||
}
|
||||
}
|
||||
|
||||
export function removePersisted(
|
||||
target: { storage?: string; legacyStorageNames?: string[]; key: string },
|
||||
platform?: Platform,
|
||||
@@ -533,7 +555,7 @@ export function persisted<T>(
|
||||
store: [Store<T>, SetStoreFunction<T>],
|
||||
): PersistedWithReady<T> {
|
||||
const platform = usePlatform()
|
||||
const config: PersistTarget = typeof target === "string" ? { key: target } : target
|
||||
const config = resolveTarget(typeof target === "string" ? { key: target } : target, platform)
|
||||
|
||||
const defaults = snapshot(store[0])
|
||||
const legacy = config.legacy ?? []
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useMutation } from "@tanstack/solid-query"
|
||||
import { createEffect } from "solid-js"
|
||||
import type { Accessor } from "solid-js"
|
||||
import {
|
||||
addServerProbePlan,
|
||||
createProbeFailureGate,
|
||||
runAddableProbePlan,
|
||||
type AddServerProbePlan,
|
||||
type WslAddServerView,
|
||||
} from "./settings-model"
|
||||
import type { WslInstalledDistro, WslServersPlatform, WslServersState } from "./types"
|
||||
|
||||
export function useWslAddServerProbes(input: {
|
||||
state: Accessor<WslServersState | undefined>
|
||||
api: WslServersPlatform
|
||||
view: Accessor<WslAddServerView>
|
||||
adding: Accessor<boolean>
|
||||
busy: Accessor<boolean>
|
||||
selectedDistro: Accessor<string | null>
|
||||
addableInstalledDistros: Accessor<WslInstalledDistro[]>
|
||||
onError: (error: unknown) => void
|
||||
}) {
|
||||
const gate = createProbeFailureGate()
|
||||
const probe = useMutation(() => ({
|
||||
mutationFn: async (command: AddServerProbePlan) => {
|
||||
if (command.kind === "addable") {
|
||||
await runAddableProbePlan({ plan: command.plan, api: input.api })
|
||||
return
|
||||
}
|
||||
if (command.plan.action === "probeRuntime") await input.api.probeRuntime()
|
||||
if (command.plan.action === "refreshDistros") await input.api.refreshDistros()
|
||||
},
|
||||
onError: input.onError,
|
||||
onSettled: (_result, error, command) => {
|
||||
if (command) gate.settle(command.key, error)
|
||||
},
|
||||
}))
|
||||
|
||||
createEffect(() => {
|
||||
if (probe.isPending) return
|
||||
const command = addServerProbePlan({
|
||||
state: input.state(),
|
||||
view: input.view(),
|
||||
adding: input.adding(),
|
||||
busy: input.busy(),
|
||||
selectedDistro: input.selectedDistro(),
|
||||
addableInstalledDistros: input.addableInstalledDistros(),
|
||||
})
|
||||
if (!command || !gate.accepts(command.key)) return
|
||||
probe.mutate(command)
|
||||
})
|
||||
|
||||
return {
|
||||
probingAddable: () => probe.isPending && probe.variables?.kind === "addable",
|
||||
resetProbeFailure: () => gate.reset(),
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { createEffect, createMemo, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
||||
import { LoaderV2 } from "@opencode-ai/ui/v2/loader-v2"
|
||||
import { RadioGroupV2, RadioItemV2 } from "@opencode-ai/ui/v2/radio-v2"
|
||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useWslAddServerProbes } from "./add-server-probes"
|
||||
import { useWslServers } from "./context"
|
||||
import { enterWslOpencodeStep } from "./settings-model"
|
||||
import { addServerViewModel, type AddServerText } from "./settings-model"
|
||||
import "./dialog-add-wsl-server.css"
|
||||
|
||||
type WslServerStep = "wsl" | "distro" | "opencode"
|
||||
function isWslRuntimeMissing(error: string | null | undefined) {
|
||||
if (!error) return true
|
||||
return /WSL is not installed|not been installed|wsl(?:\.exe)? --install/i.test(error)
|
||||
}
|
||||
|
||||
const STEPS: WslServerStep[] = ["wsl", "distro", "opencode"]
|
||||
|
||||
function isHiddenDistro(name: string) {
|
||||
return /^docker-desktop(?:-data)?$/i.test(name)
|
||||
function translate(language: ReturnType<typeof useLanguage>, value: AddServerText) {
|
||||
if (value.params) return language.t(value.key, value.params)
|
||||
return language.t(value.key)
|
||||
}
|
||||
|
||||
interface DialogWslServerProps {
|
||||
@@ -22,210 +30,272 @@ interface DialogWslServerProps {
|
||||
}
|
||||
|
||||
export function DialogAddWslServer(props: DialogWslServerProps = {}) {
|
||||
const language = useLanguage()
|
||||
const controller = useWslAddServerController(props)
|
||||
const model = controller.model
|
||||
const primaryButton = () => model().primaryButton
|
||||
const primaryButtonStyle = () => {
|
||||
const width = primaryButton().width
|
||||
if (!width) return undefined
|
||||
return { width }
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={!controller.wslServers.isPending && !controller.wslServers.isError}
|
||||
fallback={
|
||||
<Dialog fit class="settings-v2-wsl-dialog">
|
||||
<Show
|
||||
when={!controller.wslServers.isError}
|
||||
fallback={<div class="settings-v2-wsl-loading">{controller.loadError()}</div>}
|
||||
>
|
||||
<div class="settings-v2-wsl-loading">
|
||||
<LoaderV2 />
|
||||
</div>
|
||||
</Show>
|
||||
</Dialog>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={model().runtimeState === "ready"}
|
||||
fallback={
|
||||
<Show
|
||||
when={model().runtimeState === "checking" || model().runtimeState === "loading"}
|
||||
fallback={
|
||||
<DialogWslSetup
|
||||
state={model().runtimeState}
|
||||
error={controller.runtimeError()}
|
||||
installable={isWslRuntimeMissing(controller.runtimeError())}
|
||||
busy={model().busy}
|
||||
onInstall={controller.installWsl}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Dialog fit class="settings-v2-wsl-dialog">
|
||||
<div class="settings-v2-wsl-loading">
|
||||
<LoaderV2 />
|
||||
</div>
|
||||
</Dialog>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<Dialog fit class="settings-v2-wsl-dialog">
|
||||
<DialogHeader hideClose={true}>
|
||||
<DialogTitle>
|
||||
{controller.view() === "main" ? language.t("wsl.server.add") : language.t("wsl.onboarding.installDistro")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DividerV2 />
|
||||
<Show
|
||||
when={controller.view() === "main"}
|
||||
fallback={
|
||||
<>
|
||||
<DialogBody class="settings-v2-wsl-dialog-body settings-v2-wsl-catalog-picker">
|
||||
<TextInputV2
|
||||
class="settings-v2-wsl-catalog-search"
|
||||
appearance="large"
|
||||
placeholder={language.t("wsl.onboarding.searchDistros")}
|
||||
value={controller.catalogSearch()}
|
||||
disabled={model().busy}
|
||||
onInput={(event) => controller.setCatalogSearch(event.currentTarget.value)}
|
||||
/>
|
||||
<div class="settings-v2-wsl-catalog-list">
|
||||
<RadioGroupV2
|
||||
hideLabel
|
||||
class="settings-v2-wsl-distro-group"
|
||||
label={language.t("wsl.onboarding.installDistro")}
|
||||
value={model().catalogTarget ?? undefined}
|
||||
onChange={controller.setCatalogTarget}
|
||||
disabled={model().busy}
|
||||
>
|
||||
<For each={model().filteredInstallableDistros}>
|
||||
{(item) => (
|
||||
<RadioItemV2
|
||||
class="settings-v2-wsl-distro-row settings-v2-wsl-catalog-row"
|
||||
value={item.name}
|
||||
disabled={model().busy}
|
||||
label={<span class="settings-v2-wsl-distro-label">{item.label}</span>}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</RadioGroupV2>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="neutral" disabled={model().busy} onClick={controller.closeCatalog}>
|
||||
{language.t("common.cancel")}
|
||||
</ButtonV2>
|
||||
<ButtonV2
|
||||
variant={model().installingCatalogDistro ? "loading" : "contrast"}
|
||||
disabled={!model().installingCatalogDistro && (model().busy || !model().catalogTarget)}
|
||||
style={{ width: "99px" }}
|
||||
onClick={controller.installCatalogDistro}
|
||||
>
|
||||
<Show when={model().installingCatalogDistro} fallback={language.t("wsl.onboarding.installDistro")}>
|
||||
<LoaderV2 />
|
||||
</Show>
|
||||
</ButtonV2>
|
||||
</DialogFooter>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<DialogBody class="settings-v2-wsl-dialog-body">
|
||||
<div class="settings-v2-wsl-section-header">
|
||||
<span class="settings-v2-wsl-section-title">{language.t("wsl.onboarding.installedDistros")}</span>
|
||||
<ButtonV2
|
||||
variant="ghost-muted"
|
||||
size="small"
|
||||
disabled={model().busy}
|
||||
onClick={controller.refreshDistros}
|
||||
>
|
||||
{language.t("wsl.onboarding.checkAgain")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={model().addableInstalledDistros.length > 0}
|
||||
fallback={
|
||||
<div class="settings-v2-wsl-distro-list">
|
||||
<div class="settings-v2-wsl-distro-empty">
|
||||
{model().visibleInstalledDistros.length
|
||||
? language.t("wsl.onboarding.allDistrosAdded")
|
||||
: language.t("wsl.onboarding.noDistros")}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="settings-v2-wsl-distro-list">
|
||||
<RadioGroupV2
|
||||
hideLabel
|
||||
class="settings-v2-wsl-distro-group"
|
||||
label={language.t("wsl.onboarding.installedDistros")}
|
||||
value={model().selectedDistro ?? undefined}
|
||||
onChange={controller.setSelectedDistro}
|
||||
disabled={model().busy}
|
||||
>
|
||||
<For each={model().addableInstalledDistros}>
|
||||
{(item) => {
|
||||
const status = () => model().distroStatuses[item.name] ?? null
|
||||
return (
|
||||
<RadioItemV2
|
||||
class={`settings-v2-wsl-distro-row${item.version === 1 ? " settings-v2-wsl-distro-row--unsupported" : ""}`}
|
||||
value={item.name}
|
||||
disabled={item.version === 1 || model().busy}
|
||||
label={<span class="settings-v2-wsl-distro-label">{item.name}</span>}
|
||||
description={
|
||||
<Show when={status()}>
|
||||
{(value) => (
|
||||
<span class="settings-v2-wsl-distro-status" data-tone={value().tone}>
|
||||
{translate(language, value().label)}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</RadioGroupV2>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={model().installableDistros.length > 0}>
|
||||
<button
|
||||
type="button"
|
||||
class="settings-v2-wsl-catalog-card"
|
||||
disabled={model().busy}
|
||||
onClick={controller.openCatalog}
|
||||
>
|
||||
<span class="settings-v2-wsl-catalog-icon" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13.5564 10.4443V13.5554H4.22309C3.24087 13.5554 2.44531 13.5554 2.44531 13.5554V10.4443M11.112 5.99989L8.00087 9.111L4.88976 5.99989M8.00087 9.111L8.00087 2.44434"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="settings-v2-wsl-catalog-copy">
|
||||
<span class="settings-v2-wsl-catalog-title">{language.t("wsl.onboarding.needAnotherDistro")}</span>
|
||||
<span class="settings-v2-wsl-catalog-description">
|
||||
{language.t("wsl.onboarding.needAnotherDistroHint")}
|
||||
</span>
|
||||
</span>
|
||||
<span class="settings-v2-wsl-catalog-chevron" aria-hidden="true">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12L10 8L6 4" stroke="currentColor" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</Show>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<ButtonV2 variant="neutral" disabled={controller.adding()} onClick={controller.close}>
|
||||
{language.t("common.cancel")}
|
||||
</ButtonV2>
|
||||
<ButtonV2
|
||||
variant={primaryButton().loading ? "loading" : primaryButton().variant}
|
||||
disabled={!primaryButton().loading && primaryButton().disabled}
|
||||
style={primaryButtonStyle()}
|
||||
onClick={controller.runPrimary}
|
||||
>
|
||||
<Show when={primaryButton().loading} fallback={translate(language, primaryButton().label)}>
|
||||
<LoaderV2 />
|
||||
</Show>
|
||||
</ButtonV2>
|
||||
</DialogFooter>
|
||||
</Show>
|
||||
</Dialog>
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
function useWslAddServerController(props: DialogWslServerProps) {
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
const dialog = useDialog()
|
||||
const wslServers = useWslServers()
|
||||
const api = platform.wslServers!
|
||||
const [store, setStore] = createStore({
|
||||
step: undefined as WslServerStep | undefined,
|
||||
view: "main" as "main" | "catalog",
|
||||
selectedDistro: null as string | null,
|
||||
installTarget: undefined as string | undefined,
|
||||
catalogSearch: "",
|
||||
catalogTarget: null as string | null,
|
||||
adding: false,
|
||||
})
|
||||
const current = () => wslServers.data
|
||||
let disposed = false
|
||||
onCleanup(() => {
|
||||
disposed = true
|
||||
const viewModel = (probingAddable: boolean) =>
|
||||
addServerViewModel({
|
||||
state: current(),
|
||||
view: store.view,
|
||||
selectedDistro: store.selectedDistro,
|
||||
catalogSearch: store.catalogSearch,
|
||||
catalogTarget: store.catalogTarget,
|
||||
adding: store.adding,
|
||||
probingAddable,
|
||||
})
|
||||
const baseModel = createMemo(() => viewModel(false))
|
||||
const probes = useWslAddServerProbes({
|
||||
state: current,
|
||||
api,
|
||||
view: () => store.view,
|
||||
adding: () => store.adding,
|
||||
busy: () => baseModel().busy,
|
||||
selectedDistro: () => baseModel().selectedDistro,
|
||||
addableInstalledDistros: () => baseModel().addableInstalledDistros,
|
||||
onError: (error) => requestError(language, error),
|
||||
})
|
||||
const busy = createMemo(() => !!current()?.job || store.adding)
|
||||
const visibleInstalledDistros = createMemo(() =>
|
||||
(current()?.installed ?? []).filter((item) => !isHiddenDistro(item.name)),
|
||||
)
|
||||
const visibleOnlineDistros = createMemo(() => (current()?.online ?? []).filter((item) => !isHiddenDistro(item.name)))
|
||||
const defaultInstalledDistro = createMemo(() => visibleInstalledDistros().find((item) => item.isDefault) ?? null)
|
||||
const existingServerDistros = createMemo(() => new Set((current()?.servers ?? []).map((item) => item.config.distro)))
|
||||
const addableInstalledDistros = createMemo(() => {
|
||||
return visibleInstalledDistros().filter((item) => !existingServerDistros().has(item.name))
|
||||
})
|
||||
const selectedDistro = createMemo(() => {
|
||||
if (store.selectedDistro && addableInstalledDistros().some((item) => item.name === store.selectedDistro)) {
|
||||
return store.selectedDistro
|
||||
}
|
||||
const distro = defaultInstalledDistro()
|
||||
if (distro && !existingServerDistros().has(distro.name)) return distro.name
|
||||
return null
|
||||
})
|
||||
const selectedProbe = createMemo(() => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return null
|
||||
return current()?.distroProbes[distro] ?? null
|
||||
})
|
||||
const selectedInstalled = createMemo(() => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return null
|
||||
return (current()?.installed ?? []).find((item) => item.name === distro) ?? null
|
||||
})
|
||||
const opencodeCheck = createMemo(() => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return null
|
||||
return current()?.opencodeChecks[distro] ?? null
|
||||
})
|
||||
const wslReady = createMemo(() => !!current()?.runtime?.available && !current()?.pendingRestart)
|
||||
const distroReady = createMemo(() => {
|
||||
const probe = selectedProbe()
|
||||
if (!probe || !selectedDistro()) return false
|
||||
if (selectedInstalled()?.version === 1) return false
|
||||
return probe.canExecute && probe.hasBash && probe.hasCurl
|
||||
})
|
||||
const opencodeReady = createMemo(() => {
|
||||
const check = opencodeCheck()
|
||||
return !!check?.resolvedPath && !check.error
|
||||
})
|
||||
const distroWarningProbe = createMemo(() => {
|
||||
const probe = selectedProbe()
|
||||
if (!probe) return null
|
||||
if (distroReady()) return null
|
||||
return probe
|
||||
})
|
||||
const distroUnavailableMessage = createMemo(() => {
|
||||
const probe = distroWarningProbe()
|
||||
const distro = selectedDistro()
|
||||
if (!probe || probe.canExecute || !distro) return null
|
||||
if (!selectedInstalled()) return language.t("wsl.onboarding.distroNotInstalled", { distro })
|
||||
return language.t("wsl.onboarding.openDistroOnce", { distro })
|
||||
})
|
||||
const distroMissingTools = createMemo(() => {
|
||||
const probe = distroWarningProbe()
|
||||
if (!probe?.canExecute) return null
|
||||
if (probe.hasBash && probe.hasCurl) return null
|
||||
return probe
|
||||
})
|
||||
const installableDistros = createMemo(() => {
|
||||
const online = visibleOnlineDistros()
|
||||
const installed = new Set(visibleInstalledDistros().map((item) => item.name))
|
||||
const hasVersionedUbuntu = online.some((item) => /^Ubuntu-\d/.test(item.name))
|
||||
return online
|
||||
.filter((item) => !installed.has(item.name))
|
||||
.filter((item) => !(item.name === "Ubuntu" && hasVersionedUbuntu))
|
||||
})
|
||||
const installTarget = createMemo(
|
||||
() => installableDistros().find((item) => item.name === store.installTarget) ?? installableDistros()[0] ?? null,
|
||||
)
|
||||
const installingDistro = createMemo(() => current()?.job?.kind === "install-distro")
|
||||
const installingOpencode = createMemo(() => {
|
||||
const job = current()?.job
|
||||
return job?.kind === "install-opencode" && job.distro === selectedDistro()
|
||||
})
|
||||
const allReady = createMemo(() => wslReady() && distroReady() && opencodeReady())
|
||||
const addDisabled = createMemo(() => {
|
||||
const job = current()?.job
|
||||
if (!job) return store.adding
|
||||
return store.adding || job.kind !== "probe-opencode"
|
||||
})
|
||||
const recommendedStep = createMemo<WslServerStep>(() => {
|
||||
if (!wslReady()) return "wsl"
|
||||
if (!distroReady()) return "distro"
|
||||
return "opencode"
|
||||
})
|
||||
// activeStep falls back to recommendedStep when the user hasn't picked one.
|
||||
// Once the user clicks a step tab we respect their choice rather than snapping
|
||||
// them back when a probe result updates recommendedStep.
|
||||
const activeStep = createMemo(() => store.step ?? recommendedStep())
|
||||
const model = createMemo(() => viewModel(probes.probingAddable()))
|
||||
|
||||
const autoProbe = createMemo(() => {
|
||||
const state = current()
|
||||
if (!state || busy()) return null
|
||||
if (state.pendingRestart) return null
|
||||
if (!state.runtime) return { key: "runtime", run: () => api.probeRuntime() }
|
||||
if (!wslReady()) return null
|
||||
if (!state.installed.length && !state.online.length) {
|
||||
return { key: "distros", run: () => api.refreshDistros() }
|
||||
}
|
||||
const distro = selectedDistro()
|
||||
if (distro && !state.distroProbes[distro]) {
|
||||
return { key: `probe-distro:${distro}`, run: () => api.probeDistro(distro) }
|
||||
}
|
||||
if (!distro || !distroReady()) return null
|
||||
if (!state.opencodeChecks[distro]) {
|
||||
return { key: `probe-opencode:${distro}`, run: () => api.probeOpencode(distro) }
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
let lastAutoProbe: string | null = null
|
||||
createEffect(() => {
|
||||
const probe = autoProbe()
|
||||
if (!probe || probe.key === lastAutoProbe) return
|
||||
const key = probe.key
|
||||
lastAutoProbe = key
|
||||
void (async () => {
|
||||
try {
|
||||
await probe.run()
|
||||
} catch (err) {
|
||||
if (disposed) return
|
||||
// Allow the same probe to run again when reactive inputs next change
|
||||
// (e.g. user reselects a distro). Without this the user would be stuck
|
||||
// on a transient wsl.exe failure until they pick a different distro.
|
||||
if (lastAutoProbe === key) lastAutoProbe = null
|
||||
requestError(language, err)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
const wslMessage = createMemo(() => {
|
||||
const state = current()
|
||||
if (!state || state.job?.kind === "runtime") return language.t("wsl.onboarding.checkingRuntime")
|
||||
if (state.pendingRestart) return language.t("wsl.onboarding.restartRequired")
|
||||
if (state.runtime?.available) return state.runtime.version ?? language.t("wsl.onboarding.ready")
|
||||
return state.runtime?.error ?? language.t("wsl.onboarding.required")
|
||||
})
|
||||
|
||||
const distroMessage = createMemo(() => {
|
||||
const state = current()
|
||||
if (!state) return language.t("wsl.onboarding.checkingDistros")
|
||||
const distro = selectedDistro()
|
||||
if (state.job?.kind === "install-distro")
|
||||
return language.t("wsl.onboarding.installingDistro", { distro: state.job.distro })
|
||||
if (state.job?.kind === "probe-distro")
|
||||
return language.t("wsl.onboarding.checkingDistro", { distro: state.job.distro })
|
||||
if (state.job?.kind === "distros") return language.t("wsl.onboarding.listingDistros")
|
||||
if (distroUnavailableMessage()) return distroUnavailableMessage()!
|
||||
if (selectedProbe() && distroReady())
|
||||
return language.t("wsl.onboarding.distroReady", { distro: selectedProbe()!.name })
|
||||
if (distro) return language.t("wsl.onboarding.finishingDistro", { distro })
|
||||
return language.t("wsl.onboarding.pickDistro")
|
||||
})
|
||||
|
||||
const opencodeMessage = createMemo(() => {
|
||||
const state = current()
|
||||
if (!state) return language.t("wsl.onboarding.checkingOpencode")
|
||||
const distro = selectedDistro()
|
||||
if (state.job?.kind === "install-opencode") {
|
||||
return distro
|
||||
? language.t("wsl.onboarding.updatingOpencodeIn", { distro })
|
||||
: language.t("wsl.onboarding.updatingOpencode")
|
||||
}
|
||||
if (state.job?.kind === "probe-opencode") {
|
||||
return distro
|
||||
? language.t("wsl.onboarding.checkingOpencodeIn", { distro })
|
||||
: language.t("wsl.onboarding.checkingOpencode")
|
||||
}
|
||||
if (opencodeCheck()?.error) return opencodeCheck()!.error
|
||||
if (opencodeCheck()?.matchesDesktop === false) {
|
||||
return distro
|
||||
? language.t("wsl.onboarding.updateOpencodeIn", { distro })
|
||||
: language.t("wsl.onboarding.updateOpencode")
|
||||
}
|
||||
if (opencodeReady()) {
|
||||
return distro
|
||||
? language.t("wsl.onboarding.opencodeReadyIn", { distro })
|
||||
: language.t("wsl.onboarding.opencodeReady")
|
||||
}
|
||||
return distro
|
||||
? language.t("wsl.onboarding.installOpencodeIn", { distro })
|
||||
: language.t("wsl.onboarding.chooseDistroFirst")
|
||||
})
|
||||
const openCatalog = () => {
|
||||
const first = model().installableDistros[0]
|
||||
setStore({
|
||||
view: "catalog",
|
||||
catalogSearch: "",
|
||||
catalogTarget: first?.name ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
const run = async (action: () => Promise<unknown>) => {
|
||||
try {
|
||||
@@ -235,26 +305,43 @@ export function DialogAddWslServer(props: DialogWslServerProps = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const runSelectedDistro = (action: (distro: string) => Promise<unknown>) => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return
|
||||
void run(() => action(distro))
|
||||
const refreshDistros = () => {
|
||||
void run(async () => {
|
||||
probes.resetProbeFailure()
|
||||
await api.refreshDistros()
|
||||
})
|
||||
}
|
||||
|
||||
const selectDistro = (name: string) => {
|
||||
setStore("selectedDistro", name)
|
||||
setStore("step", undefined)
|
||||
const installDistro = (name: string) => {
|
||||
void run(async () => {
|
||||
probes.resetProbeFailure()
|
||||
await api.installDistro(name)
|
||||
setStore("view", "main")
|
||||
})
|
||||
}
|
||||
|
||||
const openOpencodeStep = () => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return
|
||||
void run(() => enterWslOpencodeStep(distro, api.probeOpencode, (step) => setStore("step", step)))
|
||||
const installCatalogDistro = () => {
|
||||
if (model().installingCatalogDistro) return
|
||||
const name = model().catalogTarget
|
||||
if (!name) return
|
||||
installDistro(name)
|
||||
}
|
||||
|
||||
const finish = async () => {
|
||||
const distro = selectedDistro()
|
||||
if (!distro) return
|
||||
const closeCatalog = () => {
|
||||
probes.resetProbeFailure()
|
||||
setStore({ view: "main", catalogSearch: "", catalogTarget: null })
|
||||
}
|
||||
|
||||
const runPrimary = async () => {
|
||||
const button = model().primaryButton
|
||||
if (button.loading) return
|
||||
const distro = model().selectedDistro
|
||||
const action = button.action
|
||||
if (!distro || !action) return
|
||||
if (action === "install-opencode") {
|
||||
await run(() => api.installOpencode(distro))
|
||||
return
|
||||
}
|
||||
setStore("adding", true)
|
||||
try {
|
||||
await api.addServer(distro)
|
||||
@@ -270,346 +357,99 @@ export function DialogAddWslServer(props: DialogWslServerProps = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const steps = createMemo(() => {
|
||||
const active = activeStep()
|
||||
const activeIndex = STEPS.indexOf(active)
|
||||
const recommendedIndex = STEPS.indexOf(recommendedStep())
|
||||
return STEPS.map((step) => {
|
||||
const index = STEPS.indexOf(step)
|
||||
return {
|
||||
step,
|
||||
title:
|
||||
step === "wsl"
|
||||
? language.t("wsl.server.label")
|
||||
: step === "distro"
|
||||
? language.t("wsl.onboarding.step.distro")
|
||||
: language.t("wsl.onboarding.step.opencode"),
|
||||
state:
|
||||
active === step
|
||||
? "current"
|
||||
: step === "wsl"
|
||||
? wslReady()
|
||||
? "done"
|
||||
: "warning"
|
||||
: step === "distro"
|
||||
? distroReady()
|
||||
? "done"
|
||||
: index > activeIndex
|
||||
? "locked"
|
||||
: "warning"
|
||||
: opencodeCheck()?.matchesDesktop === false
|
||||
? "warning"
|
||||
: opencodeReady()
|
||||
? "done"
|
||||
: index > activeIndex
|
||||
? "locked"
|
||||
: "warning",
|
||||
locked: index > recommendedIndex,
|
||||
}
|
||||
})
|
||||
})
|
||||
const loadError = createMemo(() => {
|
||||
const loadError = () => {
|
||||
const error = wslServers.error
|
||||
if (!error) return language.t("wsl.onboarding.loadFailed")
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
wslServers,
|
||||
model,
|
||||
loadError,
|
||||
runtimeError: () => current()?.runtime?.error ?? null,
|
||||
view: () => store.view,
|
||||
catalogSearch: () => store.catalogSearch,
|
||||
adding: () => store.adding,
|
||||
setCatalogSearch: (value: string) => setStore("catalogSearch", value),
|
||||
setCatalogTarget: (value: string) => setStore("catalogTarget", value),
|
||||
setSelectedDistro: (value: string) => setStore("selectedDistro", value),
|
||||
openCatalog,
|
||||
closeCatalog,
|
||||
refreshDistros,
|
||||
installCatalogDistro,
|
||||
installWsl: () => void run(() => api.installWsl()),
|
||||
runPrimary: () => void runPrimary(),
|
||||
close: () => dialog.close(),
|
||||
}
|
||||
}
|
||||
|
||||
function DialogWslSetup(props: {
|
||||
state: string
|
||||
error: string | null
|
||||
installable: boolean
|
||||
busy: boolean
|
||||
onInstall: () => void
|
||||
}) {
|
||||
const language = useLanguage()
|
||||
const dialog = useDialog()
|
||||
const title = () =>
|
||||
props.state === "pendingRestart"
|
||||
? language.t("wsl.onboarding.restartRequired")
|
||||
: props.installable
|
||||
? language.t("wsl.onboarding.wslNotInstalled.title")
|
||||
: language.t("wsl.onboarding.wslUnavailable.title")
|
||||
const description = () => {
|
||||
if (props.state === "pendingRestart") return language.t("wsl.onboarding.windowsRestartRequired")
|
||||
if (!props.installable) return language.t("wsl.onboarding.wslUnavailable.description")
|
||||
return language.t("wsl.onboarding.wslNotInstalled.description")
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="px-5 pb-5 flex flex-col gap-4">
|
||||
<Show
|
||||
when={!wslServers.isPending}
|
||||
fallback={<div class="px-1 py-6 text-14-regular text-text-weak">{language.t("wsl.onboarding.loading")}</div>}
|
||||
>
|
||||
<Show
|
||||
when={!wslServers.isError}
|
||||
fallback={<div class="px-1 py-6 text-14-regular text-text-weak">{loadError()}</div>}
|
||||
>
|
||||
<div class="flex gap-2 pb-1">
|
||||
<For each={steps()}>
|
||||
{(item) => (
|
||||
<button
|
||||
type="button"
|
||||
class="basis-0 flex-1 min-w-0 rounded-md border px-3 py-2 text-left transition-colors"
|
||||
classList={{
|
||||
"border-border-strong-base bg-surface-base-hover": item.state === "current",
|
||||
"border-icon-success-base/40 bg-surface-base": item.state === "done",
|
||||
"border-border-weak-base bg-background-base opacity-60": item.state === "locked",
|
||||
"border-icon-warning-base/40 bg-surface-base": item.state === "warning",
|
||||
}}
|
||||
disabled={item.locked}
|
||||
onClick={() => setStore("step", item.step)}
|
||||
>
|
||||
<div class="text-13-medium text-text-strong">{item.title}</div>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<Switch>
|
||||
<Match when={activeStep() === "wsl"}>
|
||||
<div class="rounded-md bg-surface-base p-4 flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="text-14-medium text-text-strong">{language.t("wsl.server.label")}</div>
|
||||
<Show when={current()?.runtime && !wslReady() && !current()?.pendingRestart}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="large"
|
||||
disabled={busy()}
|
||||
onClick={() => void run(() => api.installWsl())}
|
||||
>
|
||||
{language.t("wsl.onboarding.installWsl")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak whitespace-pre-wrap break-words">{wslMessage()}</div>
|
||||
<Show when={current()?.pendingRestart}>
|
||||
<div class="rounded-md border border-border-weak-base px-3 py-3">
|
||||
<div class="text-12-regular text-text-warning-base">
|
||||
{language.t("wsl.onboarding.windowsRestartRequired")}
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="flex items-center justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="large"
|
||||
disabled={busy() || !wslReady()}
|
||||
onClick={() => setStore("step", "distro")}
|
||||
>
|
||||
{language.t("wsl.onboarding.next")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
|
||||
<Match when={activeStep() === "distro"}>
|
||||
<div class="rounded-md bg-surface-base p-4 flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="text-14-medium text-text-strong">{language.t("wsl.onboarding.step.distro")}</div>
|
||||
<Show when={selectedDistro()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
disabled={busy()}
|
||||
onClick={() => runSelectedDistro((distro) => api.probeDistro(distro))}
|
||||
>
|
||||
{language.t("wsl.onboarding.refresh")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak whitespace-pre-wrap break-words">{distroMessage()}</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Show
|
||||
when={addableInstalledDistros().length > 0}
|
||||
fallback={
|
||||
<div class="text-12-regular text-text-weak">
|
||||
{visibleInstalledDistros().length
|
||||
? language.t("wsl.onboarding.allDistrosAdded")
|
||||
: current()?.runtime?.available
|
||||
? language.t("wsl.onboarding.noDistros")
|
||||
: language.t("wsl.onboarding.checkingDistros")}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={addableInstalledDistros()}>
|
||||
{(item) => (
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-border-weak-base px-3 py-2 text-left transition-colors"
|
||||
classList={{ "bg-surface-raised-base": selectedDistro() === item.name }}
|
||||
onClick={() => selectDistro(item.name)}
|
||||
>
|
||||
<div class="text-13-medium text-text-strong">{item.name}</div>
|
||||
<Show when={item.isDefault}>
|
||||
<div class="text-12-regular text-text-weak">{language.t("common.default")}</div>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={installableDistros().length > 0}>
|
||||
<div class="rounded-md border border-border-weak-base p-2 flex flex-col gap-2">
|
||||
<div class="px-1 flex items-center justify-between gap-3">
|
||||
<div class="text-12-medium text-text-weak">{language.t("wsl.onboarding.install")}</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<Show when={installingDistro()}>
|
||||
<Spinner class="h-4 w-4 text-icon-info-base shrink-0" />
|
||||
</Show>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={busy() || !installTarget()}
|
||||
onClick={() => void run(() => api.installDistro(installTarget()!.name))}
|
||||
>
|
||||
{installingDistro()
|
||||
? language.t("wsl.onboarding.installing")
|
||||
: language.t("wsl.onboarding.install")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="radiogroup"
|
||||
aria-label={language.t("wsl.onboarding.installDistro")}
|
||||
class="max-h-52 overflow-y-auto rounded-md bg-background-base"
|
||||
>
|
||||
<For each={installableDistros()}>
|
||||
{(item) => {
|
||||
const selected = () => installTarget()?.name === item.name
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={selected()}
|
||||
disabled={busy()}
|
||||
class="w-full px-3 py-2 flex items-center gap-3 text-left border-b border-border-weak-base last:border-b-0 transition-colors"
|
||||
classList={{
|
||||
"bg-surface-raised-base": selected(),
|
||||
"hover:bg-surface-base": !selected(),
|
||||
}}
|
||||
onClick={() => setStore("installTarget", item.name)}
|
||||
>
|
||||
<div
|
||||
class="mt-0.5 h-4 w-4 rounded-full border border-border-strong-base flex items-center justify-center shrink-0"
|
||||
classList={{ "border-text-strong": selected() }}
|
||||
>
|
||||
<div class="h-2 w-2 rounded-full bg-text-strong" classList={{ hidden: !selected() }} />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 text-13-medium text-text-strong truncate">{item.label}</div>
|
||||
</button>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={selectedInstalled()?.version === 1 || distroUnavailableMessage() || distroMissingTools()}>
|
||||
<div class="rounded-md border border-border-weak-base px-3 py-3 flex flex-col gap-1">
|
||||
<Show when={selectedInstalled()?.version === 1}>
|
||||
<div class="text-12-regular text-text-warning-base">
|
||||
{language.t("wsl.onboarding.wsl2Required")}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={distroUnavailableMessage()}>
|
||||
{(message) => <div class="text-12-regular text-text-warning-base">{message()}</div>}
|
||||
</Show>
|
||||
<Show when={distroMissingTools()}>
|
||||
<div class="text-12-regular text-text-warning-base">
|
||||
{language.t("wsl.onboarding.toolsRequired")}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="large"
|
||||
disabled={busy() || !selectedInstalled()}
|
||||
onClick={() => runSelectedDistro((distro) => api.openTerminal(distro))}
|
||||
>
|
||||
{language.t("wsl.onboarding.openTerminal")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="large"
|
||||
disabled={busy() || !selectedDistro()}
|
||||
onClick={() => runSelectedDistro((distro) => api.probeDistro(distro))}
|
||||
>
|
||||
{language.t("wsl.onboarding.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="large"
|
||||
disabled={busy() || !selectedDistro() || !distroReady()}
|
||||
onClick={openOpencodeStep}
|
||||
>
|
||||
{language.t("wsl.onboarding.next")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
|
||||
<Match when={activeStep() === "opencode"}>
|
||||
<div class="rounded-md bg-surface-base p-4 flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="text-14-medium text-text-strong">{language.t("wsl.onboarding.step.opencode")}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Show when={selectedDistro()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="large"
|
||||
disabled={busy()}
|
||||
onClick={() => runSelectedDistro((distro) => api.probeOpencode(distro))}
|
||||
>
|
||||
{language.t("wsl.onboarding.refresh")}
|
||||
</Button>
|
||||
</Show>
|
||||
<Show when={!opencodeReady() || opencodeCheck()?.matchesDesktop === false}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="large"
|
||||
disabled={busy()}
|
||||
onClick={() => runSelectedDistro((distro) => api.installOpencode(distro))}
|
||||
>
|
||||
<Show when={installingOpencode()}>
|
||||
<Spinner class="size-4 shrink-0" />
|
||||
</Show>
|
||||
{opencodeCheck()?.resolvedPath
|
||||
? language.t("wsl.onboarding.updateOpencode")
|
||||
: language.t("wsl.onboarding.installOpencode")}
|
||||
</Button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak whitespace-pre-wrap break-words">{opencodeMessage()}</div>
|
||||
<Show when={opencodeCheck()?.matchesDesktop === false ? opencodeCheck() : null}>
|
||||
{(check) => (
|
||||
<div class="rounded-md border border-border-weak-base px-3 py-3 flex flex-col gap-1">
|
||||
<div class="text-12-regular text-text-weak">
|
||||
{language.t("wsl.onboarding.path", {
|
||||
path: check().resolvedPath ?? language.t("wsl.onboarding.notFound"),
|
||||
})}
|
||||
</div>
|
||||
<div class="text-12-regular text-text-weak">
|
||||
{language.t("wsl.onboarding.version", {
|
||||
version: check().version ?? language.t("wsl.onboarding.unknown"),
|
||||
})}
|
||||
<Show when={check().expectedVersion}>
|
||||
{(expected) => (
|
||||
<span>{` · ${language.t("wsl.onboarding.desktopVersion", { version: expected() })}`}</span>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<div class="text-12-regular text-text-warning-base">
|
||||
{language.t("wsl.onboarding.versionMismatch")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
<Show when={activeStep() === "opencode" && allReady() && selectedDistro()}>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" size="large" disabled={store.adding} onClick={() => dialog.close()}>
|
||||
{language.t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="primary" size="large" disabled={addDisabled()} onClick={() => void finish()}>
|
||||
{store.adding ? language.t("wsl.onboarding.adding") : language.t("wsl.server.add")}
|
||||
</Button>
|
||||
</div>
|
||||
<Dialog fit class="settings-v2-wsl-not-installed-dialog">
|
||||
<div class="settings-v2-wsl-not-installed-content">
|
||||
<div class="settings-v2-wsl-not-installed-message">
|
||||
<svg
|
||||
class="settings-v2-wsl-not-installed-icon"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g clip-path="url(#settings-v2-wsl-warning-clip)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 -0.00244141L23.6926 20.2498H0.308594L12 -0.00244141ZM12.7954 6.32932C12.5844 6.11834 12.2982 5.99982 11.9999 5.99982C11.7015 5.99982 11.4154 6.11834 11.2044 6.32932C10.9934 6.5403 10.8749 6.82645 10.8749 7.12482V11.6248C10.8749 11.9232 10.9934 12.2093 11.2044 12.4203C11.4154 12.6313 11.7015 12.7498 11.9999 12.7498C12.2982 12.7498 12.5844 12.6313 12.7954 12.4203C13.0064 12.2093 13.1249 11.9232 13.1249 11.6248V7.12482C13.1249 6.82645 13.0064 6.5403 12.7954 6.32932ZM13.0605 17.5605C12.7792 17.8418 12.3977 17.9998 11.9999 17.9998C11.6021 17.9998 11.2205 17.8418 10.9392 17.5605C10.6579 17.2792 10.4999 16.8976 10.4999 16.4998C10.4999 16.102 10.6579 15.7205 10.9392 15.4392C11.2205 15.1579 11.6021 14.9998 11.9999 14.9998C12.3977 14.9998 12.7792 15.1579 13.0605 15.4392C13.3418 15.7205 13.4999 16.102 13.4999 16.4998C13.4999 16.8976 13.3418 17.2792 13.0605 17.5605Z"
|
||||
fill="#DBDBDB"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="settings-v2-wsl-warning-clip">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<h2 class="settings-v2-wsl-not-installed-title">{title()}</h2>
|
||||
<p class="settings-v2-wsl-not-installed-description">{description()}</p>
|
||||
<Show when={!props.installable && props.error}>
|
||||
<p class="settings-v2-wsl-unavailable-error">{props.error}</p>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={props.state === "unavailable" && props.installable}>
|
||||
<ButtonV2 variant="neutral" disabled={props.busy} onClick={props.onInstall}>
|
||||
{language.t("wsl.onboarding.installWsl")}
|
||||
</ButtonV2>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={props.state !== "unavailable"}>
|
||||
<ButtonV2 variant="neutral" onClick={() => dialog.close()}>
|
||||
{language.t("common.close")}
|
||||
</ButtonV2>
|
||||
</Show>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
[data-component="dialog-v2"].settings-v2-wsl-dialog [data-slot="dialog-container"] {
|
||||
width: 480px;
|
||||
max-width: calc(100vw - 32px);
|
||||
height: auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-dialog [data-slot="dialog-content"] {
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-dialog [data-slot="dialog-header"] {
|
||||
align-items: center;
|
||||
padding: 24px 16px 0;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-dialog [data-slot="dialog-body"] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 16px 1px;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-dialog [data-slot="dialog-footer"] {
|
||||
padding: 0 16px 24px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-not-installed-dialog [data-slot="dialog-container"] {
|
||||
width: 480px;
|
||||
max-width: calc(100vw - 32px);
|
||||
min-height: 264px;
|
||||
height: auto;
|
||||
background: var(--v2-background-bg-layer-01);
|
||||
box-shadow: var(--v2-elevation-overlay);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-not-installed-dialog [data-slot="dialog-content"] {
|
||||
align-items: stretch;
|
||||
min-height: 264px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-component="dialog-v2"].settings-v2-wsl-not-installed-dialog [data-slot="dialog-body"] {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-not-installed-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 56px 0;
|
||||
gap: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-not-installed-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-not-installed-icon {
|
||||
flex: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-not-installed-title {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
font-style: normal;
|
||||
font-weight: 530;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-not-installed-description {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 0;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-unavailable-error {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 4px 0 0;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-faint);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
gap: 12px;
|
||||
height: 24px;
|
||||
padding: 0 0 0 9px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-section-title {
|
||||
flex: none;
|
||||
user-select: none;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.04px;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
max-height: 220px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
background: var(--v2-background-bg-base);
|
||||
box-shadow: var(--v2-elevation-raised);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-group [data-slot="radio-v2-items"] {
|
||||
width: 100%;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 62px;
|
||||
padding: 14px 14px 14px 16px;
|
||||
border-bottom: 0.5px solid var(--v2-border-border-base);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row [data-slot="radio-v2-item-label"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row [data-slot="radio-v2-item-label"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -14px -14px -14px -44px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row [data-slot="radio-v2-item-text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-label {
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.04px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-status {
|
||||
flex: none;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0.05px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-variation-settings: "slnt" 0;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-status[data-tone="success"] {
|
||||
color: var(--v2-state-fg-success);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-status[data-tone="warning"] {
|
||||
color: var(--v2-state-fg-warning);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-status[data-tone="muted"] {
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-row:where([data-disabled]) .settings-v2-wsl-distro-status {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-distro-empty {
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1.4;
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-card {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
padding: 14px 14px 16px 16px;
|
||||
border: 0.5px solid var(--v2-border-border-base);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-card:focus-visible {
|
||||
outline: 2px solid var(--v2-border-border-focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-card:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-icon {
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--v2-icon-icon-base);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-title {
|
||||
user-select: none;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.04px;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-base);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-description {
|
||||
user-select: none;
|
||||
font-style: normal;
|
||||
font-weight: 440;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0.05px;
|
||||
font-variation-settings: "slnt" 0;
|
||||
color: var(--v2-text-text-muted);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-chevron {
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--v2-icon-icon-muted);
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
max-height: 220px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-strong);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-picker {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-catalog-row {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.settings-v2-wsl-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
font-size: 13px;
|
||||
font-weight: 440;
|
||||
line-height: 1;
|
||||
color: var(--v2-text-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,5 +1,31 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { enterWslOpencodeStep, wslOpencodeAction, wslRuntimeRetryable } from "./settings-model"
|
||||
import {
|
||||
addableProbePlan,
|
||||
addServerProbePlan,
|
||||
addServerViewModel,
|
||||
autoProbePlan,
|
||||
createProbeFailureGate,
|
||||
runAddableProbePlan,
|
||||
wslOpencodeAction,
|
||||
wslRuntimeRetryable,
|
||||
} from "./settings-model"
|
||||
import type { WslServersState } from "./types"
|
||||
|
||||
const readyWslState = readyState()
|
||||
|
||||
function readyState(input: Partial<WslServersState> = {}): WslServersState {
|
||||
return {
|
||||
runtime: { available: true, version: "2.4.13.0", error: null },
|
||||
installed: [],
|
||||
online: [],
|
||||
distroProbes: {},
|
||||
opencodeChecks: {},
|
||||
pendingRestart: false,
|
||||
servers: [],
|
||||
job: null,
|
||||
...input,
|
||||
}
|
||||
}
|
||||
|
||||
describe("WSL server settings presentation", () => {
|
||||
test("retries only settled unsuccessful runtimes", () => {
|
||||
@@ -45,13 +71,161 @@ describe("WSL server settings presentation", () => {
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test("probes the selected distro before entering the OpenCode step", async () => {
|
||||
const calls: string[] = []
|
||||
await enterWslOpencodeStep(
|
||||
"Debian",
|
||||
async (distro) => calls.push(distro),
|
||||
(step) => calls.push(step),
|
||||
)
|
||||
expect(calls).toEqual(["Debian", "opencode"])
|
||||
test("plans addable distro probes with the selected distro first", () => {
|
||||
const plan = addableProbePlan({
|
||||
state: readyWslState,
|
||||
view: "main",
|
||||
adding: false,
|
||||
selectedDistro: "Ubuntu",
|
||||
addableInstalledDistros: [
|
||||
{ name: "Debian", version: 2, isDefault: true },
|
||||
{ name: "Ubuntu", version: 2, isDefault: false },
|
||||
],
|
||||
})
|
||||
|
||||
expect(plan?.key).toBe("distro:Ubuntu|distro:Debian")
|
||||
expect(plan?.distros).toEqual(["Ubuntu", "Debian"])
|
||||
})
|
||||
|
||||
test("plans bootstrap probes for missing runtime and initial distro lists", () => {
|
||||
expect(autoProbePlan({ state: undefined, busy: false })).toBeUndefined()
|
||||
expect(autoProbePlan({ state: { ...readyWslState, runtime: null }, busy: false })).toEqual({
|
||||
key: "runtime",
|
||||
action: "probeRuntime",
|
||||
})
|
||||
expect(autoProbePlan({ state: readyWslState, busy: false })).toEqual({
|
||||
key: "distros",
|
||||
action: "refreshDistros",
|
||||
})
|
||||
})
|
||||
|
||||
test("uses one command plan for bootstrap before addable distro probing", () => {
|
||||
expect(
|
||||
addServerProbePlan({
|
||||
state: { ...readyWslState, runtime: null },
|
||||
view: "main",
|
||||
adding: false,
|
||||
busy: false,
|
||||
selectedDistro: null,
|
||||
addableInstalledDistros: [{ name: "Debian", version: 2, isDefault: true }],
|
||||
}),
|
||||
).toEqual({ kind: "auto", key: "auto:runtime", plan: { key: "runtime", action: "probeRuntime" } })
|
||||
|
||||
expect(
|
||||
addServerProbePlan({
|
||||
state: {
|
||||
...readyWslState,
|
||||
installed: [{ name: "Debian", version: 2, isDefault: true }],
|
||||
online: [{ name: "Ubuntu", label: "Ubuntu" }],
|
||||
},
|
||||
view: "main",
|
||||
adding: false,
|
||||
busy: false,
|
||||
selectedDistro: "Debian",
|
||||
addableInstalledDistros: [{ name: "Debian", version: 2, isDefault: true }],
|
||||
}),
|
||||
).toEqual({ kind: "addable", key: "addable:distro:Debian", plan: { key: "distro:Debian", distros: ["Debian"] } })
|
||||
})
|
||||
|
||||
test("does not accept the same failed probe command until reset", () => {
|
||||
const gate = createProbeFailureGate()
|
||||
|
||||
expect(gate.accepts("addable:distro:Debian")).toBe(true)
|
||||
gate.settle("addable:distro:Debian", new Error("wsl failed"))
|
||||
expect(gate.accepts("addable:distro:Debian")).toBe(false)
|
||||
expect(gate.accepts("addable:distro:Ubuntu")).toBe(true)
|
||||
gate.reset()
|
||||
expect(gate.accepts("addable:distro:Debian")).toBe(true)
|
||||
})
|
||||
|
||||
test("keeps default distro selection stable while probes resolve", () => {
|
||||
const model = addServerViewModel({
|
||||
state: {
|
||||
...readyWslState,
|
||||
installed: [
|
||||
{ name: "Debian", version: 2, isDefault: true },
|
||||
{ name: "Ubuntu", version: 2, isDefault: false },
|
||||
],
|
||||
online: [{ name: "Alpine", label: "Alpine Linux" }],
|
||||
distroProbes: {
|
||||
Ubuntu: { name: "Ubuntu", canExecute: true, hasBash: true, hasCurl: true, error: null },
|
||||
},
|
||||
},
|
||||
view: "main",
|
||||
selectedDistro: null,
|
||||
catalogSearch: "",
|
||||
catalogTarget: null,
|
||||
adding: false,
|
||||
probingAddable: false,
|
||||
})
|
||||
|
||||
expect(model.selectedDistro).toBe("Debian")
|
||||
})
|
||||
|
||||
test("keeps the dialog busy across serial addable probe job gaps", () => {
|
||||
const model = addServerViewModel({
|
||||
state: readyState({
|
||||
installed: [{ name: "Debian", version: 2, isDefault: true }],
|
||||
online: [{ name: "Ubuntu", label: "Ubuntu" }],
|
||||
}),
|
||||
view: "main",
|
||||
selectedDistro: null,
|
||||
catalogSearch: "",
|
||||
catalogTarget: null,
|
||||
adding: false,
|
||||
probingAddable: true,
|
||||
})
|
||||
|
||||
expect(model.busy).toBe(true)
|
||||
})
|
||||
|
||||
test("does not report ready when OpenCode is present but cannot run", () => {
|
||||
const model = addServerViewModel({
|
||||
state: {
|
||||
...readyWslState,
|
||||
installed: [{ name: "Debian", version: 2, isDefault: true }],
|
||||
online: [{ name: "Ubuntu", label: "Ubuntu" }],
|
||||
distroProbes: {
|
||||
Debian: { name: "Debian", canExecute: true, hasBash: true, hasCurl: true, error: null },
|
||||
},
|
||||
opencodeChecks: {
|
||||
Debian: {
|
||||
distro: "Debian",
|
||||
resolvedPath: "/home/me/.opencode/bin/opencode",
|
||||
version: null,
|
||||
expectedVersion: "1.2.3",
|
||||
matchesDesktop: null,
|
||||
error: "opencode is installed but could not run",
|
||||
},
|
||||
},
|
||||
},
|
||||
view: "main",
|
||||
selectedDistro: null,
|
||||
catalogSearch: "",
|
||||
catalogTarget: null,
|
||||
adding: false,
|
||||
probingAddable: false,
|
||||
})
|
||||
|
||||
expect(model.distroStatuses.Debian).toEqual({
|
||||
label: { key: "wsl.onboarding.installOpencode" },
|
||||
tone: "warning",
|
||||
})
|
||||
expect(model.primaryButton.action).toBe("install-opencode")
|
||||
})
|
||||
|
||||
test("delegates addable probe plans to one batch command", async () => {
|
||||
const calls: string[][] = []
|
||||
|
||||
await runAddableProbePlan({
|
||||
plan: { key: "distro:Debian|distro:Ubuntu", distros: ["Debian", "Ubuntu"] },
|
||||
api: {
|
||||
probeAddable: async (distros) => {
|
||||
calls.push(distros)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(calls).toEqual([["Debian", "Ubuntu"]])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,341 @@
|
||||
import type { WslOpencodeCheck, WslServerRuntime } from "./types"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import type {
|
||||
WslInstalledDistro,
|
||||
WslOnlineDistro,
|
||||
WslOpencodeCheck,
|
||||
WslServersPlatform,
|
||||
WslServerRuntime,
|
||||
WslServersState,
|
||||
} from "./types"
|
||||
|
||||
export type AddServerText = {
|
||||
key: string
|
||||
params?: Record<string, string>
|
||||
}
|
||||
|
||||
export type DistroStatusTone = "success" | "warning" | "muted"
|
||||
|
||||
export type DistroStatus = {
|
||||
label: AddServerText
|
||||
tone: DistroStatusTone
|
||||
}
|
||||
|
||||
export type AddServerPrimaryButton = {
|
||||
variant: "neutral" | "contrast"
|
||||
label: AddServerText
|
||||
disabled: boolean
|
||||
action: "install-opencode" | "add" | null
|
||||
loading: boolean
|
||||
width: string | null
|
||||
}
|
||||
|
||||
export type AddServerRuntimeState = "loading" | "pendingRestart" | "checking" | "unavailable" | "ready"
|
||||
|
||||
export type AddableProbePlan = {
|
||||
key: string
|
||||
distros: string[]
|
||||
}
|
||||
|
||||
export type AutoProbePlan = { key: "runtime"; action: "probeRuntime" } | { key: "distros"; action: "refreshDistros" }
|
||||
|
||||
export type AddServerProbePlan =
|
||||
| { kind: "auto"; key: string; plan: AutoProbePlan }
|
||||
| { kind: "addable"; key: string; plan: AddableProbePlan }
|
||||
|
||||
export type WslAddServerView = "main" | "catalog"
|
||||
|
||||
function isHiddenDistro(name: string) {
|
||||
return /^docker-desktop(?:-data)?$/i.test(name)
|
||||
}
|
||||
|
||||
export const wslRuntimeRetryable = (runtime: WslServerRuntime) =>
|
||||
runtime.kind === "failed" || runtime.kind === "stopped"
|
||||
|
||||
export async function enterWslOpencodeStep(
|
||||
distro: string,
|
||||
probe: (distro: string) => Promise<unknown>,
|
||||
select: (step: "opencode") => void,
|
||||
) {
|
||||
await probe(distro)
|
||||
select("opencode")
|
||||
}
|
||||
|
||||
export function wslOpencodeAction(check?: WslOpencodeCheck) {
|
||||
if (!check) return
|
||||
if (!check.resolvedPath) return "Install OpenCode"
|
||||
if (check.matchesDesktop === false) return "Update OpenCode"
|
||||
}
|
||||
|
||||
export function wslDistroReady(state: WslServersState | undefined, name: string) {
|
||||
const installed = state?.installed.find((item) => item.name === name)
|
||||
const probe = state?.distroProbes[name]
|
||||
if (!probe || !installed) return false
|
||||
if (installed.version === 1) return false
|
||||
return probe.canExecute && probe.hasBash && probe.hasCurl
|
||||
}
|
||||
|
||||
export function addServerViewModel(input: {
|
||||
state: WslServersState | undefined
|
||||
view: WslAddServerView
|
||||
selectedDistro: string | null
|
||||
catalogSearch: string
|
||||
catalogTarget: string | null
|
||||
adding: boolean
|
||||
probingAddable: boolean
|
||||
}) {
|
||||
const state = input.state
|
||||
const visibleInstalledDistros = (state?.installed ?? []).filter((item) => !isHiddenDistro(item.name))
|
||||
const visibleOnlineDistros = (state?.online ?? []).filter((item) => !isHiddenDistro(item.name))
|
||||
const existingServerDistros = new Set((state?.servers ?? []).map((item) => item.config.distro))
|
||||
const addableInstalledDistros = visibleInstalledDistros.filter((item) => !existingServerDistros.has(item.name))
|
||||
const selectedDistro = addServerSelectedDistro(input.selectedDistro, visibleInstalledDistros, addableInstalledDistros)
|
||||
const opencodeCheck = selectedDistro ? (state?.opencodeChecks[selectedDistro] ?? null) : null
|
||||
const installableDistros = addServerInstallableDistros(visibleInstalledDistros, visibleOnlineDistros)
|
||||
const filteredInstallableDistros = addServerFilteredInstallableDistros(installableDistros, input.catalogSearch)
|
||||
const catalogTarget = addServerCatalogTarget(input.catalogTarget, filteredInstallableDistros)
|
||||
const busy = !!state?.job || input.adding || input.probingAddable
|
||||
|
||||
return {
|
||||
busy,
|
||||
runtimeState: addServerRuntimeState(state),
|
||||
visibleInstalledDistros,
|
||||
visibleOnlineDistros,
|
||||
addableInstalledDistros,
|
||||
selectedDistro,
|
||||
opencodeCheck,
|
||||
wslReady: !!state?.runtime?.available && !state?.pendingRestart,
|
||||
distroStatuses: Object.fromEntries(
|
||||
addableInstalledDistros.flatMap((item) => {
|
||||
const status = addServerDistroStatus({ state, name: item.name, probingAddable: input.probingAddable })
|
||||
if (!status) return []
|
||||
return [[item.name, status]]
|
||||
}),
|
||||
) as Record<string, DistroStatus | undefined>,
|
||||
primaryButton: addServerPrimaryButton({
|
||||
state,
|
||||
selectedDistro,
|
||||
opencodeCheck,
|
||||
adding: input.adding,
|
||||
probingAddable: input.probingAddable,
|
||||
}),
|
||||
installableDistros,
|
||||
filteredInstallableDistros,
|
||||
catalogTarget,
|
||||
installingCatalogDistro: state?.job?.kind === "install-distro",
|
||||
}
|
||||
}
|
||||
|
||||
function addServerSelectedDistro(
|
||||
selected: string | null,
|
||||
visibleInstalledDistros: WslInstalledDistro[],
|
||||
addableInstalledDistros: WslInstalledDistro[],
|
||||
) {
|
||||
if (selected && addableInstalledDistros.some((item) => item.name === selected && item.version !== 1)) return selected
|
||||
const defaultDistro = visibleInstalledDistros.find((item) => item.isDefault)
|
||||
if (
|
||||
defaultDistro &&
|
||||
defaultDistro.version !== 1 &&
|
||||
addableInstalledDistros.some((item) => item.name === defaultDistro.name)
|
||||
) {
|
||||
return defaultDistro.name
|
||||
}
|
||||
return addableInstalledDistros.find((item) => item.version !== 1)?.name ?? null
|
||||
}
|
||||
|
||||
function addServerRuntimeState(state: WslServersState | undefined): AddServerRuntimeState {
|
||||
if (!state) return "loading"
|
||||
if (state.pendingRestart) return "pendingRestart"
|
||||
if (!state.runtime) return "checking"
|
||||
if (!state.runtime.available) return "unavailable"
|
||||
return "ready"
|
||||
}
|
||||
|
||||
function addServerDistroStatus(input: {
|
||||
state: WslServersState | undefined
|
||||
name: string
|
||||
probingAddable: boolean
|
||||
}): DistroStatus | undefined {
|
||||
const installed = input.state?.installed.find((item) => item.name === input.name)
|
||||
if (installed?.version === 1) return { label: { key: "wsl.onboarding.distroStatus.unsupported" }, tone: "muted" }
|
||||
const job = input.state?.job
|
||||
const probe = input.state?.distroProbes[input.name]
|
||||
if (!probe) {
|
||||
if (input.probingAddable || (job?.kind === "probe-addable" && job.distros.includes(input.name))) {
|
||||
return checkingStatus()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!probe.canExecute) {
|
||||
if (!installed) {
|
||||
return { label: { key: "wsl.onboarding.distroNotInstalled", params: { distro: input.name } }, tone: "warning" }
|
||||
}
|
||||
return { label: { key: "wsl.onboarding.openDistroOnce", params: { distro: input.name } }, tone: "warning" }
|
||||
}
|
||||
if (!probe.hasBash || !probe.hasCurl) {
|
||||
return { label: { key: "wsl.onboarding.distroStatus.missingTools" }, tone: "warning" }
|
||||
}
|
||||
const check = input.state?.opencodeChecks[input.name]
|
||||
if (!check) {
|
||||
if (input.probingAddable || (job?.kind === "probe-addable" && job.distros.includes(input.name))) {
|
||||
return checkingStatus()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (check.matchesDesktop === false) return { label: { key: "wsl.onboarding.updateOpencode" }, tone: "warning" }
|
||||
if (!check.resolvedPath) return { label: { key: "wsl.onboarding.distroStatus.opencodeMissing" }, tone: "warning" }
|
||||
if (check.error) return { label: { key: "wsl.onboarding.installOpencode" }, tone: "warning" }
|
||||
return { label: { key: "wsl.onboarding.distroStatus.ready" }, tone: "success" }
|
||||
}
|
||||
|
||||
function checkingStatus(): DistroStatus {
|
||||
return { label: { key: "wsl.onboarding.distroStatus.checking" }, tone: "muted" }
|
||||
}
|
||||
|
||||
function addServerPrimaryButton(input: {
|
||||
state: WslServersState | undefined
|
||||
selectedDistro: string | null
|
||||
opencodeCheck: WslOpencodeCheck | null
|
||||
adding: boolean
|
||||
probingAddable: boolean
|
||||
}): AddServerPrimaryButton {
|
||||
const ready = !!input.selectedDistro && wslDistroReady(input.state, input.selectedDistro)
|
||||
const probingSelected = input.probingAddable && !addServerSelectedDistroSettled(input.state, input.selectedDistro)
|
||||
const probingOpencode =
|
||||
probingSelected ||
|
||||
(ready &&
|
||||
(!input.opencodeCheck ||
|
||||
(!!input.selectedDistro &&
|
||||
input.state?.job?.kind === "probe-addable" &&
|
||||
input.state.job.distros.includes(input.selectedDistro))))
|
||||
const installingOpencode =
|
||||
input.state?.job?.kind === "install-opencode" && input.state.job.distro === input.selectedDistro
|
||||
if (!ready || probingOpencode) {
|
||||
return {
|
||||
variant: "contrast",
|
||||
label: probingSelected ? { key: "wsl.onboarding.distroStatus.checking" } : { key: "wsl.server.add" },
|
||||
disabled: true,
|
||||
action: null,
|
||||
loading: probingSelected,
|
||||
width: null,
|
||||
}
|
||||
}
|
||||
if (!addServerOpencodeReady(input.opencodeCheck)) {
|
||||
const update = !!input.opencodeCheck?.resolvedPath && input.opencodeCheck.matchesDesktop === false
|
||||
return {
|
||||
variant: "neutral",
|
||||
label: installingOpencode
|
||||
? { key: "wsl.onboarding.updatingOpencode" }
|
||||
: update
|
||||
? { key: "wsl.onboarding.updateOpencode" }
|
||||
: { key: "wsl.onboarding.installOpencode" },
|
||||
disabled: !!input.state?.job || input.adding,
|
||||
action: "install-opencode",
|
||||
loading: installingOpencode,
|
||||
width: update ? "138px" : "129px",
|
||||
}
|
||||
}
|
||||
return {
|
||||
variant: "contrast",
|
||||
label: input.adding ? { key: "wsl.onboarding.adding" } : { key: "wsl.server.add" },
|
||||
disabled: input.adding || !!input.state?.job,
|
||||
action: "add",
|
||||
loading: input.adding,
|
||||
width: null,
|
||||
}
|
||||
}
|
||||
|
||||
function addServerOpencodeReady(check: WslOpencodeCheck | null) {
|
||||
return !!check?.resolvedPath && check.matchesDesktop !== false && !check.error
|
||||
}
|
||||
|
||||
function addServerSelectedDistroSettled(state: WslServersState | undefined, selectedDistro: string | null) {
|
||||
if (!selectedDistro) return false
|
||||
const installed = state?.installed.find((item) => item.name === selectedDistro)
|
||||
if (installed?.version === 1) return false
|
||||
if (!state?.distroProbes[selectedDistro]) return false
|
||||
if (!wslDistroReady(state, selectedDistro)) return true
|
||||
return !!state.opencodeChecks[selectedDistro]
|
||||
}
|
||||
|
||||
function addServerInstallableDistros(installedDistros: WslInstalledDistro[], onlineDistros: WslOnlineDistro[]) {
|
||||
const installed = new Set(installedDistros.map((item) => item.name))
|
||||
const hasVersionedUbuntu = onlineDistros.some((item) => /^Ubuntu-\d/.test(item.name))
|
||||
return onlineDistros
|
||||
.filter((item) => !installed.has(item.name))
|
||||
.filter((item) => item.name !== "Ubuntu" || !hasVersionedUbuntu)
|
||||
}
|
||||
|
||||
function addServerFilteredInstallableDistros(installableDistros: WslOnlineDistro[], search: string) {
|
||||
const query = search.trim()
|
||||
if (!query) return installableDistros
|
||||
return fuzzysort.go(query, installableDistros, { keys: ["label", "name"] }).map((item) => item.obj)
|
||||
}
|
||||
|
||||
function addServerCatalogTarget(target: string | null, distros: WslOnlineDistro[]) {
|
||||
if (target && distros.some((item) => item.name === target)) return target
|
||||
return distros[0]?.name ?? null
|
||||
}
|
||||
|
||||
export function addableProbePlan(input: {
|
||||
state: WslServersState | undefined
|
||||
view: WslAddServerView
|
||||
adding: boolean
|
||||
selectedDistro: string | null
|
||||
addableInstalledDistros: WslInstalledDistro[]
|
||||
}) {
|
||||
const state = input.state
|
||||
if (!state?.runtime?.available || state.pendingRestart || input.view !== "main" || input.adding) return
|
||||
if (state.job) return
|
||||
const ordered = input.selectedDistro
|
||||
? [
|
||||
...input.addableInstalledDistros.filter((item) => item.name === input.selectedDistro),
|
||||
...input.addableInstalledDistros.filter((item) => item.name !== input.selectedDistro),
|
||||
]
|
||||
: input.addableInstalledDistros
|
||||
const pending = ordered.flatMap((item) => {
|
||||
if (item.version === 1) return []
|
||||
if (!state.distroProbes[item.name]) return [`distro:${item.name}`]
|
||||
if (wslDistroReady(state, item.name) && !state.opencodeChecks[item.name]) return [`opencode:${item.name}`]
|
||||
return []
|
||||
})
|
||||
if (!pending.length) return
|
||||
return {
|
||||
key: pending.join("|"),
|
||||
distros: ordered.filter((item) => item.version !== 1).map((item) => item.name),
|
||||
} satisfies AddableProbePlan
|
||||
}
|
||||
|
||||
export function autoProbePlan(input: { state: WslServersState | undefined; busy: boolean }) {
|
||||
if (!input.state || input.busy || input.state.pendingRestart) return
|
||||
if (!input.state.runtime) return { key: "runtime", action: "probeRuntime" } satisfies AutoProbePlan
|
||||
if (!input.state.runtime.available) return
|
||||
if (input.state.installed.length || input.state.online.length) return
|
||||
return { key: "distros", action: "refreshDistros" } satisfies AutoProbePlan
|
||||
}
|
||||
|
||||
export function addServerProbePlan(input: {
|
||||
state: WslServersState | undefined
|
||||
view: WslAddServerView
|
||||
adding: boolean
|
||||
busy: boolean
|
||||
selectedDistro: string | null
|
||||
addableInstalledDistros: WslInstalledDistro[]
|
||||
}) {
|
||||
const auto = autoProbePlan({ state: input.state, busy: input.busy })
|
||||
if (auto) return { kind: "auto", key: `auto:${auto.key}`, plan: auto } satisfies AddServerProbePlan
|
||||
const addable = addableProbePlan(input)
|
||||
if (addable) return { kind: "addable", key: `addable:${addable.key}`, plan: addable } satisfies AddServerProbePlan
|
||||
}
|
||||
|
||||
export function createProbeFailureGate() {
|
||||
let failed: string | undefined
|
||||
return {
|
||||
accepts(key: string) {
|
||||
return key !== failed
|
||||
},
|
||||
settle(key: string, error?: unknown) {
|
||||
if (error) failed = key
|
||||
},
|
||||
reset() {
|
||||
failed = undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function runAddableProbePlan(input: {
|
||||
plan: AddableProbePlan
|
||||
api: Pick<WslServersPlatform, "probeAddable">
|
||||
}) {
|
||||
await input.api.probeAddable(input.plan.distros)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { Dialog, DialogBody, DialogHeader, DialogTitle } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||
@@ -30,17 +28,7 @@ export function AddServerMenu(props: { onAddServer: () => void }) {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const openAddWsl = () => {
|
||||
dialog.push(() => (
|
||||
<Dialog size="large" fit class="settings-v2-wsl-dialog">
|
||||
<DialogHeader hideClose={true}>
|
||||
<DialogTitle>{language.t("wsl.server.add")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DividerV2 />
|
||||
<DialogBody>
|
||||
<DialogAddWslServer />
|
||||
</DialogBody>
|
||||
</Dialog>
|
||||
))
|
||||
dialog.push(() => <DialogAddWslServer />)
|
||||
}
|
||||
return (
|
||||
<Show
|
||||
|
||||
@@ -53,8 +53,7 @@ export type WslJob =
|
||||
| { kind: "distros"; startedAt: number }
|
||||
| { kind: "install-wsl"; startedAt: number }
|
||||
| { kind: "install-distro"; distro: string; startedAt: number }
|
||||
| { kind: "probe-distro"; distro: string; startedAt: number }
|
||||
| { kind: "probe-opencode"; distro: string; startedAt: number }
|
||||
| { kind: "probe-addable"; distros: string[]; startedAt: number }
|
||||
| { kind: "install-opencode"; distro: string; startedAt: number }
|
||||
|
||||
export type WslServersState = {
|
||||
@@ -77,8 +76,7 @@ export type WslServersPlatform = {
|
||||
refreshDistros(): Promise<void>
|
||||
installWsl(): Promise<void>
|
||||
installDistro(name: string): Promise<void>
|
||||
probeDistro(name: string): Promise<void>
|
||||
probeOpencode(name: string): Promise<void>
|
||||
probeAddable(distros: string[]): Promise<void>
|
||||
installOpencode(name: string): Promise<void>
|
||||
openTerminal(name: string): Promise<void>
|
||||
addServer(distro: string): Promise<WslServerConfig>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```bash
|
||||
# From packages/cli: local V2 TUI
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev --standalone
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||
|
||||
# Released legacy TUI behavior reference
|
||||
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
|
||||
@@ -23,12 +23,12 @@ termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
|
||||
## Interactive debugging
|
||||
|
||||
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
|
||||
- Run commands from `packages/cli`. Use `bun dev --standalone` for most debugging so the TUI starts with a private V2 server instead of depending on the background service.
|
||||
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
|
||||
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
|
||||
- Use a dedicated session name and do not reuse or kill an unrelated session.
|
||||
|
||||
```bash
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev --standalone
|
||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
|
||||
termctrl show opencode-v2-dev
|
||||
```
|
||||
@@ -56,7 +56,7 @@ termctrl show opencode-v2-dev
|
||||
```
|
||||
|
||||
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
|
||||
- To exercise background-service behavior, omit `--standalone`. Service lifecycle commands are available through `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
||||
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
||||
- Always clean up the Terminal Control session when the check is complete:
|
||||
|
||||
```bash
|
||||
@@ -85,7 +85,7 @@ bun dev api <operationId> --param key=value
|
||||
|
||||
```bash
|
||||
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
|
||||
bun run --inspect=ws://localhost:6499/ src/index.ts --standalone
|
||||
bun run --inspect=ws://localhost:6499/ src/index.ts
|
||||
```
|
||||
|
||||
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
|
||||
@@ -14,6 +14,16 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
||||
Flag.withDescription("Run with a private server instead of the background service"),
|
||||
Flag.withDefault(false),
|
||||
),
|
||||
continue: Flag.boolean("continue").pipe(
|
||||
Flag.withAlias("c"),
|
||||
Flag.withDescription("Continue the last session"),
|
||||
Flag.withDefault(false),
|
||||
),
|
||||
session: Flag.string("session").pipe(
|
||||
Flag.withAlias("s"),
|
||||
Flag.withDescription("Session ID to continue"),
|
||||
Flag.optional,
|
||||
),
|
||||
},
|
||||
commands: [
|
||||
Spec.make("api", {
|
||||
@@ -36,6 +46,43 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
||||
description: "Debugging and troubleshooting tools",
|
||||
commands: [Spec.make("agents", { description: "List all agents" })],
|
||||
}),
|
||||
Spec.make("mcp", {
|
||||
description: "Manage MCP (Model Context Protocol) servers",
|
||||
commands: [
|
||||
Spec.make("list", { description: "List configured MCP servers and their status" }),
|
||||
Spec.make("add", {
|
||||
description: "Add an MCP server to your configuration",
|
||||
params: {
|
||||
name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")),
|
||||
command: Argument.string("command").pipe(
|
||||
Argument.withDescription("Command and arguments for a local server, passed after --"),
|
||||
Argument.variadic({ min: 0 }),
|
||||
),
|
||||
url: Flag.string("url").pipe(Flag.withDescription("URL for a remote MCP server"), Flag.optional),
|
||||
header: Flag.keyValuePair("header").pipe(
|
||||
Flag.withDescription("HTTP header for a remote server, as name=value"),
|
||||
Flag.optional,
|
||||
),
|
||||
env: Flag.keyValuePair("env").pipe(
|
||||
Flag.withDescription("Environment variable for a local server, as name=value"),
|
||||
Flag.optional,
|
||||
),
|
||||
global: Flag.boolean("global").pipe(
|
||||
Flag.withDescription("Write to the global config instead of the project config"),
|
||||
Flag.withDefault(false),
|
||||
),
|
||||
},
|
||||
}),
|
||||
Spec.make("auth", {
|
||||
description: "Authenticate with an OAuth-capable remote MCP server",
|
||||
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
|
||||
}),
|
||||
Spec.make("logout", {
|
||||
description: "Remove stored OAuth credentials for an MCP server",
|
||||
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Spec.make("migrate", { description: "Migrate v1 data to v2" }),
|
||||
Spec.make("service", {
|
||||
description: "Manage the background server",
|
||||
@@ -44,18 +91,26 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
||||
Spec.make("restart", { description: "Restart the background server" }),
|
||||
Spec.make("status", { description: "Show background server status" }),
|
||||
Spec.make("stop", { description: "Stop the background server" }),
|
||||
Spec.make("password", {
|
||||
description: "Get or set the server password",
|
||||
params: { value: Argument.string("value").pipe(Argument.optional) },
|
||||
Spec.make("get", {
|
||||
description: "Get service configuration",
|
||||
params: { key: Argument.string("key").pipe(Argument.optional) },
|
||||
}),
|
||||
Spec.make("set", {
|
||||
description: "Set service configuration",
|
||||
params: { key: Argument.string("key"), value: Argument.string("value") },
|
||||
}),
|
||||
Spec.make("unset", {
|
||||
description: "Unset service configuration",
|
||||
params: { key: Argument.string("key") },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Spec.make("serve", {
|
||||
description: "Start the v2 API server",
|
||||
params: {
|
||||
hostname: Flag.string("hostname").pipe(Flag.withDefault("127.0.0.1")),
|
||||
hostname: Flag.string("hostname").pipe(Flag.optional),
|
||||
port: Flag.integer("port").pipe(Flag.optional),
|
||||
register: Flag.boolean("register").pipe(Flag.withDefault(false)),
|
||||
service: Flag.boolean("service").pipe(Flag.withDefault(false)),
|
||||
stdio: Flag.boolean("stdio").pipe(Flag.withDefault(false)),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -10,12 +10,13 @@ export default Runtime.handler(Commands, (input) =>
|
||||
const directory = Option.getOrUndefined(input.directory)
|
||||
if (directory !== undefined) process.chdir(directory)
|
||||
const updater = yield* Updater.Service
|
||||
yield* updater.check()
|
||||
yield* updater.check().pipe(Effect.forkScoped)
|
||||
const daemon = yield* Daemon.Service
|
||||
const transport = yield* (input.standalone ? Standalone.transport() : daemon.transport())
|
||||
const { runTui } = yield* Effect.promise(() => import("../../tui"))
|
||||
yield* runTui(
|
||||
transport,
|
||||
{ continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
|
||||
input.standalone
|
||||
? undefined
|
||||
: async () => {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { EOL } from "node:os"
|
||||
import path from "node:path"
|
||||
import { Effect, Option } from "effect"
|
||||
import { applyEdits, modify } from "jsonc-parser"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.add,
|
||||
Effect.fn("cli.mcp.add")(function* (input) {
|
||||
const url = Option.getOrUndefined(input.url)
|
||||
const headers = Option.getOrUndefined(input.header)
|
||||
const environment = Option.getOrUndefined(input.env)
|
||||
// The CLI framework strands `--` operands on the root command, so read the local server command
|
||||
// straight from argv after `--`. This also lets the command carry its own flags (e.g. `npx -y`).
|
||||
const dash = process.argv.indexOf("--")
|
||||
const command = dash === -1 ? [...input.command] : process.argv.slice(dash + 1)
|
||||
|
||||
const hasCommand = command.length > 0
|
||||
if (url && hasCommand)
|
||||
return yield* Effect.fail(new Error("Provide either --url <url> or a command after --, not both"))
|
||||
if (!url && !hasCommand) return yield* Effect.fail(new Error("Provide either --url <url> or a command after --"))
|
||||
if (url && !URL.canParse(url)) return yield* Effect.fail(new Error(`Invalid URL: ${url}`))
|
||||
if (url && environment) return yield* Effect.fail(new Error("--env is only valid for local MCP servers"))
|
||||
if (hasCommand && headers) return yield* Effect.fail(new Error("--header is only valid for remote MCP servers"))
|
||||
|
||||
const server = url
|
||||
? { type: "remote" as const, url, ...(headers ? { headers } : {}) }
|
||||
: { type: "local" as const, command, ...(environment ? { environment } : {}) }
|
||||
|
||||
const configPath = yield* Effect.promise(() => resolveConfigPath(input.global ? Global.Path.config : process.cwd()))
|
||||
yield* Effect.promise(() => write(configPath, input.name, server))
|
||||
process.stdout.write(`MCP server "${input.name}" added to ${configPath}` + EOL)
|
||||
}),
|
||||
)
|
||||
|
||||
async function resolveConfigPath(directory: string) {
|
||||
const candidates = [
|
||||
path.join(directory, "opencode.json"),
|
||||
path.join(directory, "opencode.jsonc"),
|
||||
path.join(directory, ".opencode", "opencode.json"),
|
||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
]
|
||||
for (const candidate of candidates) {
|
||||
if (await Bun.file(candidate).exists()) return candidate
|
||||
}
|
||||
return candidates[0]
|
||||
}
|
||||
|
||||
async function write(configPath: string, name: string, server: unknown) {
|
||||
const file = Bun.file(configPath)
|
||||
const text = (await file.exists()) ? await file.text() : "{}"
|
||||
const edits = modify(text, ["mcp", "servers", name], server, {
|
||||
formattingOptions: { tabSize: 2, insertSpaces: true },
|
||||
})
|
||||
await Bun.write(configPath, applyEdits(text, edits))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { EOL } from "node:os"
|
||||
import { Effect } from "effect"
|
||||
import type { IntegrationAttemptStatus, IntegrationOAuthMethod, OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
import { resolveIntegration } from "./resolve"
|
||||
|
||||
const location = { directory: process.cwd() }
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.auth,
|
||||
Effect.fn("cli.mcp.auth")(function* (input) {
|
||||
const daemon = yield* Daemon.Service
|
||||
const client = yield* daemon.client()
|
||||
|
||||
const integration = yield* resolveIntegration(client, input.name, location)
|
||||
if (!integration)
|
||||
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
||||
const method = integration.methods.find(
|
||||
(candidate): candidate is IntegrationOAuthMethod => candidate.type === "oauth",
|
||||
)
|
||||
if (!method)
|
||||
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
||||
|
||||
const started = yield* Effect.promise(() =>
|
||||
client.v2.integration.connect.oauth({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
|
||||
)
|
||||
const attempt = started.data?.data
|
||||
if (!attempt) return yield* Effect.fail(new Error(started.error?.message ?? "Failed to start OAuth attempt"))
|
||||
if (attempt.mode === "code")
|
||||
return yield* Effect.fail(new Error("This server requires manual code entry, which the CLI does not support"))
|
||||
|
||||
process.stdout.write(attempt.instructions + EOL + attempt.url + EOL)
|
||||
|
||||
const result = yield* poll(client, attempt.attemptID)
|
||||
if (result.status === "complete") {
|
||||
process.stdout.write(`Authenticated with ${input.name}` + EOL)
|
||||
return
|
||||
}
|
||||
const reason = result.status === "failed" ? `: ${result.message}` : ""
|
||||
return yield* Effect.fail(new Error(`Authentication ${result.status}${reason}`))
|
||||
}),
|
||||
)
|
||||
|
||||
const poll = (
|
||||
client: OpencodeClient,
|
||||
attemptID: string,
|
||||
): Effect.Effect<Exclude<IntegrationAttemptStatus, { status: "pending" }>> =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* Effect.promise(() => client.v2.integration.attempt.status({ attemptID, location }))
|
||||
const status = response.data?.data
|
||||
if (!status || status.status === "pending") {
|
||||
yield* Effect.sleep("1 second")
|
||||
return yield* poll(client, attemptID)
|
||||
}
|
||||
return status
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import { EOL } from "node:os"
|
||||
import * as Effect from "effect/Effect"
|
||||
import type { McpServer } from "@opencode-ai/sdk/v2/client"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.list,
|
||||
Effect.fn("cli.mcp.list")(function* () {
|
||||
const daemon = yield* Daemon.Service
|
||||
const client = yield* daemon.client()
|
||||
const response = yield* Effect.promise(() => client.v2.mcp.list({ location: { directory: process.cwd() } }))
|
||||
const servers = (response.data?.data ?? []).toSorted((a, b) => a.name.localeCompare(b.name))
|
||||
if (servers.length === 0) {
|
||||
process.stdout.write("No MCP servers configured" + EOL)
|
||||
return
|
||||
}
|
||||
const width = Math.max(...servers.map((server) => server.name.length))
|
||||
const lines = servers.map(
|
||||
(server) => `${icon(server.status)} ${server.name.padEnd(width)} ${describe(server.status)}`,
|
||||
)
|
||||
process.stdout.write(lines.join(EOL) + EOL)
|
||||
}),
|
||||
)
|
||||
|
||||
function icon(status: McpServer["status"]) {
|
||||
switch (status.status) {
|
||||
case "connected":
|
||||
return "✓"
|
||||
case "needs_auth":
|
||||
return "⚠"
|
||||
case "failed":
|
||||
case "needs_client_registration":
|
||||
return "✗"
|
||||
default:
|
||||
return "○"
|
||||
}
|
||||
}
|
||||
|
||||
function describe(status: McpServer["status"]) {
|
||||
switch (status.status) {
|
||||
case "needs_auth":
|
||||
return "needs authentication"
|
||||
case "needs_client_registration":
|
||||
return `needs client registration: ${status.error}`
|
||||
case "failed":
|
||||
return `failed: ${status.error}`
|
||||
default:
|
||||
return status.status
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { EOL } from "node:os"
|
||||
import { Effect } from "effect"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
import { resolveIntegration } from "./resolve"
|
||||
|
||||
const location = { directory: process.cwd() }
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.logout,
|
||||
Effect.fn("cli.mcp.logout")(function* (input) {
|
||||
const daemon = yield* Daemon.Service
|
||||
const client = yield* daemon.client()
|
||||
|
||||
const integration = yield* resolveIntegration(client, input.name, location)
|
||||
if (!integration) {
|
||||
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
|
||||
return
|
||||
}
|
||||
|
||||
const credentials = integration.connections.filter((connection) => connection.type === "credential")
|
||||
if (credentials.length === 0) {
|
||||
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
|
||||
return
|
||||
}
|
||||
|
||||
yield* Effect.forEach(
|
||||
credentials,
|
||||
(connection) => Effect.promise(() => client.v2.credential.remove({ credentialID: connection.id, location })),
|
||||
{ discard: true },
|
||||
)
|
||||
process.stdout.write(`Removed OAuth credentials for ${input.name}` + EOL)
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Effect } from "effect"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
// Resolve through the MCP-owned integrationID rather than matching integration names: the shared
|
||||
// integration registry also holds provider/plugin integrations, whose names could collide with a server.
|
||||
// Fails when the server is unknown; returns undefined when the server has no integration (e.g. a local
|
||||
// or anonymous server), leaving that case for the caller to interpret.
|
||||
export const resolveIntegration = (client: OpencodeClient, name: string, location: { directory: string }) =>
|
||||
Effect.gen(function* () {
|
||||
const servers = yield* Effect.promise(() => client.v2.mcp.list({ location }))
|
||||
const server = (servers.data?.data ?? []).find((entry) => entry.name === name)
|
||||
if (!server) return yield* Effect.fail(new Error(`MCP server not found: ${name}`))
|
||||
const integrationID = server.integrationID
|
||||
if (!integrationID) return undefined
|
||||
const found = yield* Effect.promise(() => client.v2.integration.get({ integrationID, location }))
|
||||
return found.data?.data
|
||||
})
|
||||
@@ -1,7 +1,10 @@
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { Context, Layer, Option } from "effect"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Context, Layer, Option, Schedule } from "effect"
|
||||
import * as Effect from "effect/Effect"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { createServer } from "node:http"
|
||||
@@ -12,29 +15,41 @@ import { Commands } from "../commands"
|
||||
import { Runtime } from "../../framework/runtime"
|
||||
import { Daemon } from "../../services/daemon"
|
||||
import { Updater } from "../../services/updater"
|
||||
import { randomBytes } from "crypto"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.serve,
|
||||
Effect.fn("cli.serve")(function* (input) {
|
||||
if (input.service) yield* Effect.sync(() => process.chdir(Global.Path.home))
|
||||
return yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const daemon = yield* Daemon.Service
|
||||
const standalonePassword = process.env.OPENCODE_SERVER_PASSWORD
|
||||
if (input.stdio) delete process.env.OPENCODE_SERVER_PASSWORD
|
||||
const password = input.stdio ? standalonePassword : yield* daemon.password()
|
||||
const config = input.service ? yield* daemon.config() : {}
|
||||
const password = input.service
|
||||
? yield* daemon.password()
|
||||
: standalonePassword || randomBytes(32).toString("base64url")
|
||||
if (!password) return yield* Effect.fail(new Error("Missing server password"))
|
||||
const address = yield* listen(input.hostname, input.port, password)
|
||||
const hostname = Option.getOrUndefined(input.hostname) ?? config.hostname ?? "127.0.0.1"
|
||||
const port = Option.isSome(input.port)
|
||||
? input.port
|
||||
: config.port === undefined
|
||||
? Option.none<number>()
|
||||
: Option.some(config.port)
|
||||
const address = yield* listen(hostname, port, password)
|
||||
yield* Effect.tryPromise(() =>
|
||||
createOpencodeClient({
|
||||
baseUrl: HttpServer.formatAddress(address),
|
||||
headers: ServerAuth.headers({ password }),
|
||||
}).v2.location.get(undefined, { throwOnError: true }),
|
||||
}).v2.health.get({}),
|
||||
)
|
||||
if (input.register) yield* daemon.register(address)
|
||||
if (input.service) yield* daemon.register(address)
|
||||
const url = HttpServer.formatAddress(address)
|
||||
console.log(input.stdio ? JSON.stringify({ url }) : `server listening on ${url}`)
|
||||
if (!input.service && !input.stdio && !standalonePassword) console.log(`server password ${password}`)
|
||||
const updater = yield* Updater.Service
|
||||
yield* updater.check().pipe(Effect.forkScoped)
|
||||
yield* updater.check().pipe(Effect.schedule(Schedule.spaced("10 minutes")), Effect.forkScoped)
|
||||
return yield* (input.stdio ? waitForStdinClose() : Effect.never)
|
||||
}).pipe(Effect.annotateLogs({ role: "server" })),
|
||||
)
|
||||
@@ -70,8 +85,7 @@ function bind(hostname: string, port: number, password: string) {
|
||||
return Layer.build(
|
||||
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
|
||||
Layer.provideMerge(NodeHttpServer.layer(() => server, { port, host: hostname })),
|
||||
Layer.provide(Credential.defaultLayer),
|
||||
Layer.provide(PermissionSaved.defaultLayer),
|
||||
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))),
|
||||
),
|
||||
).pipe(
|
||||
Effect.tap(() => Effect.addFinalizer(() => Effect.sync(() => server.closeAllConnections()))),
|
||||
|
||||
+3
-5
@@ -6,11 +6,9 @@ import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.service.commands.password,
|
||||
Effect.fn("cli.service.password")(function* (input) {
|
||||
Commands.commands.service.commands.get,
|
||||
Effect.fn("cli.service.get")(function* (input) {
|
||||
const daemon = yield* Daemon.Service
|
||||
const value = Option.getOrUndefined(input.value)
|
||||
if (value !== undefined) yield* daemon.stop()
|
||||
process.stdout.write((yield* daemon.password(value)) + EOL)
|
||||
process.stdout.write((yield* daemon.get(Option.getOrUndefined(input.key))) + EOL)
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as Effect from "effect/Effect"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.service.commands.set,
|
||||
Effect.fn("cli.service.set")(function* (input) {
|
||||
yield* (yield* Daemon.Service).set(input.key, input.value)
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as Effect from "effect/Effect"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Daemon } from "../../../services/daemon"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.service.commands.unset,
|
||||
Effect.fn("cli.service.unset")(function* (input) {
|
||||
yield* (yield* Daemon.Service).unset(input.key)
|
||||
}),
|
||||
)
|
||||
@@ -11,6 +11,10 @@ import { Daemon } from "./services/daemon"
|
||||
import { Logging } from "@opencode-ai/core/observability/logging"
|
||||
import { Updater } from "./services/updater"
|
||||
import { InstallationChannel, InstallationVersion, InstallationLocal } from "@opencode-ai/core/installation/version"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
|
||||
const LoggingLayer = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe(
|
||||
Layer.provide(NodeFileSystem.layer),
|
||||
@@ -24,13 +28,21 @@ const Handlers = Runtime.handlers(Commands, {
|
||||
debug: {
|
||||
agents: () => import("./commands/handlers/debug/agents"),
|
||||
},
|
||||
mcp: {
|
||||
list: () => import("./commands/handlers/mcp/list"),
|
||||
add: () => import("./commands/handlers/mcp/add"),
|
||||
auth: () => import("./commands/handlers/mcp/auth"),
|
||||
logout: () => import("./commands/handlers/mcp/logout"),
|
||||
},
|
||||
migrate: () => import("./commands/handlers/migrate"),
|
||||
service: {
|
||||
start: () => import("./commands/handlers/service/start"),
|
||||
restart: () => import("./commands/handlers/service/restart"),
|
||||
status: () => import("./commands/handlers/service/status"),
|
||||
stop: () => import("./commands/handlers/service/stop"),
|
||||
password: () => import("./commands/handlers/service/password"),
|
||||
get: () => import("./commands/handlers/service/get"),
|
||||
set: () => import("./commands/handlers/service/set"),
|
||||
unset: () => import("./commands/handlers/service/unset"),
|
||||
},
|
||||
serve: () => import("./commands/handlers/serve"),
|
||||
})
|
||||
@@ -38,8 +50,9 @@ const Handlers = Runtime.handlers(Commands, {
|
||||
Effect.logInfo("cli starting", { version: InstallationVersion, channel: InstallationChannel, local: InstallationLocal }).pipe(
|
||||
Effect.flatMap(() => Runtime.run(Commands, Handlers, { version: InstallationVersion })),
|
||||
Effect.annotateLogs({ role: "cli" }),
|
||||
Effect.provide(Daemon.defaultLayer),
|
||||
Effect.provide(Updater.defaultLayer),
|
||||
Effect.provide(Daemon.layer),
|
||||
Effect.provide(Updater.layer),
|
||||
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node]))),
|
||||
Effect.provide(LoggingLayer),
|
||||
Effect.provide(NodeServices.layer),
|
||||
Effect.scoped,
|
||||
|
||||
@@ -15,6 +15,10 @@ export interface Interface {
|
||||
readonly status: () => Effect.Effect<string | undefined>
|
||||
readonly stop: () => Effect.Effect<void, unknown>
|
||||
readonly password: (value?: string) => Effect.Effect<string, unknown>
|
||||
readonly config: () => Effect.Effect<ServiceConfig, unknown>
|
||||
readonly get: (key?: string) => Effect.Effect<string, unknown>
|
||||
readonly set: (key: string, value: string) => Effect.Effect<void, unknown>
|
||||
readonly unset: (key: string) => Effect.Effect<void, unknown>
|
||||
readonly register: (address: HttpServer.Address) => Effect.Effect<void, unknown, Scope.Scope>
|
||||
}
|
||||
|
||||
@@ -28,9 +32,21 @@ const Registration = Schema.Struct({
|
||||
})
|
||||
type Registration = typeof Registration.Type
|
||||
|
||||
const Config = Schema.Struct({
|
||||
const ServiceConfig = Schema.Struct({
|
||||
hostname: Schema.optional(Schema.String),
|
||||
port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(65_535))),
|
||||
password: Schema.optional(Schema.String),
|
||||
autostart: Schema.optional(Schema.Boolean),
|
||||
})
|
||||
export type ServiceConfig = typeof ServiceConfig.Type
|
||||
|
||||
const serviceConfigKeys = ["hostname", "port", "password", "autostart"] as const
|
||||
type ServiceConfigKey = (typeof serviceConfigKeys)[number]
|
||||
|
||||
function serviceConfigKey(key: string): ServiceConfigKey {
|
||||
if (serviceConfigKeys.includes(key as ServiceConfigKey)) return key as ServiceConfigKey
|
||||
throw new Error(`Unknown service config key: ${key}`)
|
||||
}
|
||||
|
||||
function sameRegistration(left: Registration, right: Registration) {
|
||||
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid
|
||||
@@ -40,33 +56,117 @@ export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FileSystem.FileSystem
|
||||
const directory = Global.Path.state
|
||||
const file = path.join(directory, InstallationChannel === "local" ? "server-local.json" : "server.json")
|
||||
const configFile = path.join(Global.Path.config, "service.json")
|
||||
const legacyPasswordFile = path.join(directory, "password")
|
||||
const global = yield* Global.Service
|
||||
const directory = global.state
|
||||
const filename = InstallationChannel === "local" ? "service-local.json" : "service.json"
|
||||
const file = path.join(directory, filename)
|
||||
const configFile = path.join(global.config, filename)
|
||||
const decodeRegistration = Schema.decodeUnknownEffect(Schema.fromJsonString(Registration))
|
||||
const decodeConfig = Schema.decodeUnknownEffect(Schema.fromJsonString(Config))
|
||||
const decodeServiceConfig = Schema.decodeUnknownEffect(Schema.fromJsonString(ServiceConfig))
|
||||
|
||||
const config = Effect.fn("cli.daemon.config")(function* () {
|
||||
return yield* fs.readFileString(configFile).pipe(
|
||||
Effect.flatMap(decodeServiceConfig),
|
||||
Effect.catch(() => Effect.succeed({} as ServiceConfig)),
|
||||
)
|
||||
})
|
||||
|
||||
const writeConfig = Effect.fn("cli.daemon.writeConfig")(function* (value: ServiceConfig) {
|
||||
const temp = configFile + ".tmp"
|
||||
yield* fs.makeDirectory(path.dirname(configFile), { recursive: true })
|
||||
yield* fs.writeFileString(temp, JSON.stringify(value, null, 2) + "\n", { mode: 0o600 })
|
||||
yield* fs.rename(temp, configFile)
|
||||
})
|
||||
|
||||
const password = Effect.fn("cli.daemon.password")(function* (value?: string) {
|
||||
const config = yield* fs
|
||||
.readFileString(configFile)
|
||||
.pipe(Effect.flatMap(decodeConfig), Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (value === undefined && config?.password) return config.password
|
||||
|
||||
const legacy = yield* fs
|
||||
.readFileString(legacyPasswordFile)
|
||||
.pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
const next = value ?? legacy ?? randomBytes(32).toString("base64url")
|
||||
const existing = yield* config()
|
||||
if (value === undefined && existing.password) return existing.password
|
||||
const next = value ?? randomBytes(32).toString("base64url")
|
||||
|
||||
// Keep one private credential across server restarts so discovered clients
|
||||
// can reconnect without exposing a password flag or environment variable.
|
||||
const temp = configFile + ".tmp"
|
||||
yield* fs.writeFileString(temp, JSON.stringify({ password: next }, null, 2) + "\n", { mode: 0o600 })
|
||||
yield* fs.rename(temp, configFile)
|
||||
if (legacy) yield* fs.remove(legacyPasswordFile).pipe(Effect.ignore)
|
||||
yield* writeConfig({ ...existing, password: next })
|
||||
return next
|
||||
})
|
||||
|
||||
const get = Effect.fn("cli.daemon.get")(function* (key?: string) {
|
||||
if (key === undefined) {
|
||||
const { password: _password, ...safe } = yield* config()
|
||||
return JSON.stringify(safe, null, 2)
|
||||
}
|
||||
switch (serviceConfigKey(key)) {
|
||||
case "hostname": {
|
||||
return (yield* config()).hostname ?? ""
|
||||
}
|
||||
case "port": {
|
||||
const port = (yield* config()).port
|
||||
return port === undefined ? "" : String(port)
|
||||
}
|
||||
case "password": {
|
||||
return yield* password()
|
||||
}
|
||||
case "autostart": {
|
||||
const autostart = (yield* config()).autostart
|
||||
return autostart === undefined ? "" : String(autostart)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const set = Effect.fn("cli.daemon.set")(function* (key: string, value: string) {
|
||||
switch (serviceConfigKey(key)) {
|
||||
case "hostname": {
|
||||
yield* stop()
|
||||
yield* writeConfig({ ...(yield* config()), hostname: value })
|
||||
return
|
||||
}
|
||||
case "port": {
|
||||
const port = Number(value)
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error("Port must be between 1 and 65535")
|
||||
yield* stop()
|
||||
yield* writeConfig({ ...(yield* config()), port })
|
||||
return
|
||||
}
|
||||
case "password": {
|
||||
yield* stop()
|
||||
yield* password(value)
|
||||
return
|
||||
}
|
||||
case "autostart": {
|
||||
if (value !== "true" && value !== "false") throw new Error("Autostart must be true or false")
|
||||
yield* writeConfig({ ...(yield* config()), autostart: value === "true" })
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const unset = Effect.fn("cli.daemon.unset")(function* (key: string) {
|
||||
switch (serviceConfigKey(key)) {
|
||||
case "hostname": {
|
||||
yield* stop()
|
||||
const { hostname: _hostname, ...next } = yield* config()
|
||||
yield* writeConfig(next)
|
||||
return
|
||||
}
|
||||
case "port": {
|
||||
yield* stop()
|
||||
const { port: _port, ...next } = yield* config()
|
||||
yield* writeConfig(next)
|
||||
return
|
||||
}
|
||||
case "password": {
|
||||
yield* stop()
|
||||
const { password: _password, ...next } = yield* config()
|
||||
yield* writeConfig(next)
|
||||
return
|
||||
}
|
||||
case "autostart": {
|
||||
const { autostart: _autostart, ...next } = yield* config()
|
||||
yield* writeConfig(next)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const registration = Effect.fnUntraced(function* () {
|
||||
return yield* fs.readFileString(file).pipe(Effect.flatMap(decodeRegistration))
|
||||
})
|
||||
@@ -83,6 +183,16 @@ export const layer = Layer.effect(
|
||||
return yield* Effect.fail(new Error("Registered server is not healthy"))
|
||||
})
|
||||
|
||||
const remoteTransport = Effect.fn("cli.daemon.remoteTransport")(function* (input: ServiceConfig) {
|
||||
const url = serviceURL(input)
|
||||
const headers = ServerAuth.headers({ password: input.password })
|
||||
const response = yield* Effect.tryPromise(() =>
|
||||
createOpencodeClient({ baseUrl: url, headers }).v2.health.get({ signal: AbortSignal.timeout(2_000) }),
|
||||
)
|
||||
if (response.data?.healthy === true) return { url, headers }
|
||||
return yield* Effect.fail(new Error(`Server is not healthy: ${url}`))
|
||||
})
|
||||
|
||||
const compatible = Effect.fnUntraced(function* () {
|
||||
const info = yield* healthy()
|
||||
if (info.version === InstallationVersion) return info
|
||||
@@ -131,7 +241,7 @@ export const layer = Layer.effect(
|
||||
return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
|
||||
yield* Effect.try({
|
||||
try: () => {
|
||||
spawn(process.execPath, [...(entrypoint ? [entrypoint] : []), "serve", "--register"], {
|
||||
spawn(process.execPath, [...(entrypoint ? [entrypoint] : []), "serve", "--service"], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
}).unref()
|
||||
@@ -147,6 +257,8 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const transport = Effect.fn("cli.daemon.transport")(function* () {
|
||||
const current = yield* config()
|
||||
if (current.autostart === false) return yield* remoteTransport(current)
|
||||
return { url: yield* start(), headers: ServerAuth.headers({ password: yield* password() }) }
|
||||
})
|
||||
|
||||
@@ -197,10 +309,15 @@ export const layer = Layer.effect(
|
||||
)
|
||||
})
|
||||
|
||||
return Service.of({ client, transport, start, status, stop, password, register })
|
||||
return Service.of({ client, transport, start, status, stop, password, config, get, set, unset, register })
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
function serviceURL(config: ServiceConfig) {
|
||||
const hostname = config.hostname ?? "127.0.0.1"
|
||||
const result = new URL(`http://${hostname.includes(":") && !hostname.startsWith("[") ? `[${hostname}]` : hostname}`)
|
||||
result.port = String(config.port ?? 4096)
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
export * as Daemon from "./daemon"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ServerAuth } from "@opencode-ai/server/auth"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Effect, Schema, Stream } from "effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { randomBytes } from "node:crypto"
|
||||
@@ -35,7 +36,7 @@ export const transport = Effect.fn("cli.standalone.transport")(
|
||||
const ready = yield* Effect.tryPromise(() => decodeReady(output))
|
||||
return { url: ready.url, headers: ServerAuth.headers({ password }), pid: proc.pid }
|
||||
},
|
||||
Effect.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)),
|
||||
)
|
||||
|
||||
export * as Standalone from "./standalone"
|
||||
|
||||
@@ -153,6 +153,4 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AppProcess.defaultLayer), Layer.provide(Global.defaultLayer))
|
||||
|
||||
export * as Updater from "./updater"
|
||||
|
||||
+15
-12
@@ -1,37 +1,40 @@
|
||||
import { run } from "@opencode-ai/tui"
|
||||
import { TuiConfig } from "@opencode-ai/tui/config"
|
||||
import { Effect } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
|
||||
import { OpenCode } from "@opencode-ai/client"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
import type { Args } from "@opencode-ai/tui/context/args"
|
||||
|
||||
type Transport = { url: string; headers: RequestInit["headers"] }
|
||||
|
||||
export function runTui(transport: Transport, reload?: () => Promise<Transport>) {
|
||||
export function runTui(transport: Transport, args: Args, reload?: () => Promise<Transport>) {
|
||||
const config = TuiConfig.resolve({}, { terminalSuspend: false })
|
||||
let disposeSlots: (() => void) | undefined
|
||||
return Effect.gen(function* () {
|
||||
const options = { baseUrl: transport.url, headers: transport.headers }
|
||||
const client = createOpencodeClient(options)
|
||||
const directory = yield* Effect.tryPromise(() =>
|
||||
client.v2.fs.list({ location: { directory: process.cwd() } }, { throwOnError: true }),
|
||||
).pipe(
|
||||
Effect.map((response) => response.data.location.directory),
|
||||
const api = OpenCode.make(options)
|
||||
const directory = yield* Effect.tryPromise(() => api.file.list({ location: { directory: process.cwd() } })).pipe(
|
||||
Effect.map((response) => response.location.directory),
|
||||
Effect.catch(() =>
|
||||
Effect.tryPromise(() => client.v2.location.get(undefined, { throwOnError: true })).pipe(
|
||||
Effect.map((response) => response.data.directory),
|
||||
),
|
||||
Effect.tryPromise(() => api.location.get()).pipe(Effect.map((response) => response.directory)),
|
||||
),
|
||||
)
|
||||
return yield* run({
|
||||
client: createOpencodeClient({ ...options, directory }),
|
||||
api,
|
||||
reload: reload
|
||||
? async () => {
|
||||
const next = await reload()
|
||||
return createOpencodeClient({ baseUrl: next.url, headers: next.headers, directory })
|
||||
return {
|
||||
client: createOpencodeClient({ baseUrl: next.url, headers: next.headers, directory }),
|
||||
api: OpenCode.make({ baseUrl: next.url, headers: next.headers }),
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
args: {},
|
||||
args,
|
||||
config,
|
||||
pluginHost: {
|
||||
async start(input) {
|
||||
@@ -42,5 +45,5 @@ export function runTui(transport: Transport, reload?: () => Promise<Transport>)
|
||||
},
|
||||
},
|
||||
})
|
||||
}).pipe(Effect.provide(Global.defaultLayer))
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { expect, test } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { Daemon } from "../src/services/daemon"
|
||||
|
||||
test("local channel stores service config with the local service filename", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-daemon-"))
|
||||
try {
|
||||
await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const daemon = yield* Daemon.Service
|
||||
yield* daemon.set("autostart", "false")
|
||||
}).pipe(
|
||||
Effect.provide(Daemon.layer),
|
||||
Effect.provide(Global.layerWith({ config: path.join(root, "config"), state: path.join(root, "state") })),
|
||||
Effect.provide(NodeFileSystem.layer),
|
||||
),
|
||||
)
|
||||
expect(await Bun.file(path.join(root, "config", "service-local.json")).json()).toEqual({
|
||||
autostart: false,
|
||||
})
|
||||
expect(await Bun.file(path.join(root, "config", "service.json")).exists()).toBe(false)
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -1,16 +1,23 @@
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { compile, emitEffectImported, emitPromise, write } from "@opencode-ai/httpapi-codegen"
|
||||
import { ClientApi, endpointNames, groupNames, omitEndpoints } from "../src/contract"
|
||||
import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from "@opencode-ai/httpapi-codegen"
|
||||
import {
|
||||
ClientApi,
|
||||
effectOmitEndpoints,
|
||||
endpointNames,
|
||||
groupNames,
|
||||
promiseOmitEndpoints,
|
||||
} from "@opencode-ai/protocol/client"
|
||||
import { Effect } from "effect"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const contract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints })
|
||||
const promiseContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
|
||||
const effectContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: effectOmitEndpoints })
|
||||
|
||||
await Effect.runPromise(
|
||||
Effect.all(
|
||||
[
|
||||
write(
|
||||
emitPromise(contract, {
|
||||
emitPromise(promiseContract, {
|
||||
outputTypes: {
|
||||
"events.subscribe": {
|
||||
name: "OpenCodeEventEncoded",
|
||||
@@ -21,10 +28,14 @@ await Effect.runPromise(
|
||||
fileURLToPath(new URL("../src/generated", import.meta.url)),
|
||||
),
|
||||
write(
|
||||
emitEffectImported(contract, { module: "../contract", api: "ClientApi" }),
|
||||
emitEffectImported(effectContract, { module: "../contract", api: "ClientApi" }),
|
||||
fileURLToPath(new URL("../src/generated-effect", import.meta.url)),
|
||||
),
|
||||
write(
|
||||
emitEffectShape(effectContract, { module: "@opencode-ai/protocol/client", api: "ClientApi" }),
|
||||
fileURLToPath(new URL("../../plugin/src/v2/effect/generated", import.meta.url)),
|
||||
),
|
||||
],
|
||||
{ concurrency: 2, discard: true },
|
||||
{ concurrency: 3, discard: true },
|
||||
).pipe(Effect.provide(NodeFileSystem.layer)),
|
||||
)
|
||||
|
||||
@@ -1,53 +1,7 @@
|
||||
import { makeDefaultApi } from "@opencode-ai/protocol/api"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
|
||||
class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware>()(
|
||||
"@opencode-ai/client/LocationMiddleware",
|
||||
) {}
|
||||
|
||||
class SessionLocationMiddleware extends HttpApiMiddleware.Service<SessionLocationMiddleware>()(
|
||||
"@opencode-ai/client/SessionLocationMiddleware",
|
||||
{ error: [InvalidRequestError, SessionNotFoundError] },
|
||||
) {}
|
||||
|
||||
export const ClientApi = makeDefaultApi({
|
||||
locationMiddleware: LocationMiddleware,
|
||||
sessionLocationMiddleware: SessionLocationMiddleware,
|
||||
})
|
||||
|
||||
export const groupNames = {
|
||||
"server.health": "health",
|
||||
"server.location": "location",
|
||||
"server.agent": "agents",
|
||||
"server.session": "sessions",
|
||||
"server.message": "messages",
|
||||
"server.model": "models",
|
||||
"server.provider": "providers",
|
||||
"server.integration": "integrations",
|
||||
"server.credential": "credentials",
|
||||
"server.permission": "permissions",
|
||||
"server.fs": "files",
|
||||
"server.command": "commands",
|
||||
"server.skill": "skills",
|
||||
"server.event": "events",
|
||||
"server.pty": "ptys",
|
||||
"server.question": "questions",
|
||||
"server.reference": "references",
|
||||
"server.projectCopy": "projectCopies",
|
||||
} as const
|
||||
|
||||
export const endpointNames = {
|
||||
"session.messages": "list",
|
||||
"integration.connect.key": "connectKey",
|
||||
"integration.connect.oauth": "connectOauth",
|
||||
"integration.attempt.status": "attemptStatus",
|
||||
"integration.attempt.complete": "attemptComplete",
|
||||
"integration.attempt.cancel": "attemptCancel",
|
||||
"permission.request.list": "listRequests",
|
||||
"permission.saved.list": "listSaved",
|
||||
"permission.saved.remove": "removeSaved",
|
||||
"question.request.list": "listRequests",
|
||||
} as const
|
||||
|
||||
export const omitEndpoints = new Set(["fs.read", "pty.connect", "pty.connectToken"])
|
||||
export {
|
||||
ClientApi,
|
||||
effectOmitEndpoints,
|
||||
endpointNames,
|
||||
groupNames,
|
||||
promiseOmitEndpoints,
|
||||
} from "@opencode-ai/protocol/client"
|
||||
|
||||
@@ -4,6 +4,8 @@ export * from "./generated-effect/index"
|
||||
export { Agent } from "@opencode-ai/schema/agent"
|
||||
export { Command } from "@opencode-ai/schema/command"
|
||||
export { Credential } from "@opencode-ai/schema/credential"
|
||||
export { Event } from "@opencode-ai/schema/event"
|
||||
export { EventLog } from "@opencode-ai/schema/event-log"
|
||||
export { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
export { Integration } from "@opencode-ai/schema/integration"
|
||||
export { Location } from "@opencode-ai/schema/location"
|
||||
|
||||
@@ -32,18 +32,25 @@ const Endpoint2_0 = (raw: RawClient["server.agent"]) => (input?: Endpoint2_0Inpu
|
||||
|
||||
const adaptGroup2 = (raw: RawClient["server.agent"]) => ({ list: Endpoint2_0(raw) })
|
||||
|
||||
type Endpoint3_0Request = Parameters<RawClient["server.session"]["session.list"]>[0]
|
||||
type Endpoint3_0Input = {
|
||||
readonly workspace?: Endpoint3_0Request["query"]["workspace"]
|
||||
readonly limit?: Endpoint3_0Request["query"]["limit"]
|
||||
readonly order?: Endpoint3_0Request["query"]["order"]
|
||||
readonly search?: Endpoint3_0Request["query"]["search"]
|
||||
readonly directory?: Endpoint3_0Request["query"]["directory"]
|
||||
readonly project?: Endpoint3_0Request["query"]["project"]
|
||||
readonly subpath?: Endpoint3_0Request["query"]["subpath"]
|
||||
readonly cursor?: Endpoint3_0Request["query"]["cursor"]
|
||||
type Endpoint3_0Request = Parameters<RawClient["server.plugin"]["plugin.list"]>[0]
|
||||
type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] }
|
||||
const Endpoint3_0 = (raw: RawClient["server.plugin"]) => (input?: Endpoint3_0Input) =>
|
||||
raw["plugin.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup3 = (raw: RawClient["server.plugin"]) => ({ list: Endpoint3_0(raw) })
|
||||
|
||||
type Endpoint4_0Request = Parameters<RawClient["server.session"]["session.list"]>[0]
|
||||
type Endpoint4_0Input = {
|
||||
readonly workspace?: Endpoint4_0Request["query"]["workspace"]
|
||||
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
||||
readonly order?: Endpoint4_0Request["query"]["order"]
|
||||
readonly search?: Endpoint4_0Request["query"]["search"]
|
||||
readonly directory?: Endpoint4_0Request["query"]["directory"]
|
||||
readonly project?: Endpoint4_0Request["query"]["project"]
|
||||
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
|
||||
readonly cursor?: Endpoint4_0Request["query"]["cursor"]
|
||||
}
|
||||
const Endpoint3_0 = (raw: RawClient["server.session"]) => (input?: Endpoint3_0Input) =>
|
||||
const Endpoint4_0 = (raw: RawClient["server.session"]) => (input?: Endpoint4_0Input) =>
|
||||
raw["session.list"]({
|
||||
query: {
|
||||
workspace: input?.["workspace"],
|
||||
@@ -57,14 +64,14 @@ const Endpoint3_0 = (raw: RawClient["server.session"]) => (input?: Endpoint3_0In
|
||||
},
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_1Request = Parameters<RawClient["server.session"]["session.create"]>[0]
|
||||
type Endpoint3_1Input = {
|
||||
readonly id?: Endpoint3_1Request["payload"]["id"]
|
||||
readonly agent?: Endpoint3_1Request["payload"]["agent"]
|
||||
readonly model?: Endpoint3_1Request["payload"]["model"]
|
||||
readonly location?: Endpoint3_1Request["payload"]["location"]
|
||||
type Endpoint4_1Request = Parameters<RawClient["server.session"]["session.create"]>[0]
|
||||
type Endpoint4_1Input = {
|
||||
readonly id?: Endpoint4_1Request["payload"]["id"]
|
||||
readonly agent?: Endpoint4_1Request["payload"]["agent"]
|
||||
readonly model?: Endpoint4_1Request["payload"]["model"]
|
||||
readonly location?: Endpoint4_1Request["payload"]["location"]
|
||||
}
|
||||
const Endpoint3_1 = (raw: RawClient["server.session"]) => (input?: Endpoint3_1Input) =>
|
||||
const Endpoint4_1 = (raw: RawClient["server.session"]) => (input?: Endpoint4_1Input) =>
|
||||
raw["session.create"]({
|
||||
payload: { id: input?.["id"], agent: input?.["agent"], model: input?.["model"], location: input?.["location"] },
|
||||
}).pipe(
|
||||
@@ -72,59 +79,67 @@ const Endpoint3_1 = (raw: RawClient["server.session"]) => (input?: Endpoint3_1In
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
const Endpoint3_2 = (raw: RawClient["server.session"]) => () =>
|
||||
raw["session.active"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
const Endpoint4_2 = (raw: RawClient["server.session"]) => () =>
|
||||
raw["session.active"]({}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_3Request = Parameters<RawClient["server.session"]["session.get"]>[0]
|
||||
type Endpoint3_3Input = { readonly sessionID: Endpoint3_3Request["params"]["sessionID"] }
|
||||
const Endpoint3_3 = (raw: RawClient["server.session"]) => (input: Endpoint3_3Input) =>
|
||||
type Endpoint4_3Request = Parameters<RawClient["server.session"]["session.get"]>[0]
|
||||
type Endpoint4_3Input = { readonly sessionID: Endpoint4_3Request["params"]["sessionID"] }
|
||||
const Endpoint4_3 = (raw: RawClient["server.session"]) => (input: Endpoint4_3Input) =>
|
||||
raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint3_4Request = Parameters<RawClient["server.session"]["session.switchAgent"]>[0]
|
||||
type Endpoint3_4Input = {
|
||||
readonly sessionID: Endpoint3_4Request["params"]["sessionID"]
|
||||
readonly agent: Endpoint3_4Request["payload"]["agent"]
|
||||
type Endpoint4_4Request = Parameters<RawClient["server.session"]["session.fork"]>[0]
|
||||
type Endpoint4_4Input = {
|
||||
readonly sessionID: Endpoint4_4Request["params"]["sessionID"]
|
||||
readonly messageID?: Endpoint4_4Request["payload"]["messageID"]
|
||||
}
|
||||
const Endpoint3_4 = (raw: RawClient["server.session"]) => (input: Endpoint3_4Input) =>
|
||||
const Endpoint4_4 = (raw: RawClient["server.session"]) => (input: Endpoint4_4Input) =>
|
||||
raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint4_5Request = Parameters<RawClient["server.session"]["session.switchAgent"]>[0]
|
||||
type Endpoint4_5Input = {
|
||||
readonly sessionID: Endpoint4_5Request["params"]["sessionID"]
|
||||
readonly agent: Endpoint4_5Request["payload"]["agent"]
|
||||
}
|
||||
const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) =>
|
||||
raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint3_5Request = Parameters<RawClient["server.session"]["session.switchModel"]>[0]
|
||||
type Endpoint3_5Input = {
|
||||
readonly sessionID: Endpoint3_5Request["params"]["sessionID"]
|
||||
readonly model: Endpoint3_5Request["payload"]["model"]
|
||||
type Endpoint4_6Request = Parameters<RawClient["server.session"]["session.switchModel"]>[0]
|
||||
type Endpoint4_6Input = {
|
||||
readonly sessionID: Endpoint4_6Request["params"]["sessionID"]
|
||||
readonly model: Endpoint4_6Request["payload"]["model"]
|
||||
}
|
||||
const Endpoint3_5 = (raw: RawClient["server.session"]) => (input: Endpoint3_5Input) =>
|
||||
const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) =>
|
||||
raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint3_6Request = Parameters<RawClient["server.session"]["session.rename"]>[0]
|
||||
type Endpoint3_6Input = {
|
||||
readonly sessionID: Endpoint3_6Request["params"]["sessionID"]
|
||||
readonly title: Endpoint3_6Request["payload"]["title"]
|
||||
type Endpoint4_7Request = Parameters<RawClient["server.session"]["session.rename"]>[0]
|
||||
type Endpoint4_7Input = {
|
||||
readonly sessionID: Endpoint4_7Request["params"]["sessionID"]
|
||||
readonly title: Endpoint4_7Request["payload"]["title"]
|
||||
}
|
||||
const Endpoint3_6 = (raw: RawClient["server.session"]) => (input: Endpoint3_6Input) =>
|
||||
const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) =>
|
||||
raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint3_7Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
||||
type Endpoint3_7Input = {
|
||||
readonly sessionID: Endpoint3_7Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint3_7Request["payload"]["id"]
|
||||
readonly prompt: Endpoint3_7Request["payload"]["prompt"]
|
||||
readonly delivery?: Endpoint3_7Request["payload"]["delivery"]
|
||||
readonly resume?: Endpoint3_7Request["payload"]["resume"]
|
||||
type Endpoint4_8Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
|
||||
type Endpoint4_8Input = {
|
||||
readonly sessionID: Endpoint4_8Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint4_8Request["payload"]["id"]
|
||||
readonly prompt: Endpoint4_8Request["payload"]["prompt"]
|
||||
readonly delivery?: Endpoint4_8Request["payload"]["delivery"]
|
||||
readonly resume?: Endpoint4_8Request["payload"]["resume"]
|
||||
}
|
||||
const Endpoint3_7 = (raw: RawClient["server.session"]) => (input: Endpoint3_7Input) =>
|
||||
const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) =>
|
||||
raw["session.prompt"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
|
||||
@@ -133,23 +148,81 @@ const Endpoint3_7 = (raw: RawClient["server.session"]) => (input: Endpoint3_7Inp
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint3_8Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||
type Endpoint3_8Input = { readonly sessionID: Endpoint3_8Request["params"]["sessionID"] }
|
||||
const Endpoint3_8 = (raw: RawClient["server.session"]) => (input: Endpoint3_8Input) =>
|
||||
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.command"]>[0]
|
||||
type Endpoint4_9Input = {
|
||||
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint4_9Request["payload"]["id"]
|
||||
readonly command: Endpoint4_9Request["payload"]["command"]
|
||||
readonly arguments?: Endpoint4_9Request["payload"]["arguments"]
|
||||
readonly agent?: Endpoint4_9Request["payload"]["agent"]
|
||||
readonly model?: Endpoint4_9Request["payload"]["model"]
|
||||
readonly files?: Endpoint4_9Request["payload"]["files"]
|
||||
readonly agents?: Endpoint4_9Request["payload"]["agents"]
|
||||
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
|
||||
readonly resume?: Endpoint4_9Request["payload"]["resume"]
|
||||
}
|
||||
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
|
||||
raw["session.command"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: {
|
||||
id: input["id"],
|
||||
command: input["command"],
|
||||
arguments: input["arguments"],
|
||||
agent: input["agent"],
|
||||
model: input["model"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
},
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
|
||||
type Endpoint4_10Input = {
|
||||
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint4_10Request["payload"]["id"]
|
||||
readonly skill: Endpoint4_10Request["payload"]["skill"]
|
||||
readonly resume?: Endpoint4_10Request["payload"]["resume"]
|
||||
}
|
||||
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
|
||||
raw["session.skill"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
|
||||
type Endpoint4_11Input = {
|
||||
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
|
||||
readonly text: Endpoint4_11Request["payload"]["text"]
|
||||
readonly description?: Endpoint4_11Request["payload"]["description"]
|
||||
readonly metadata?: Endpoint4_11Request["payload"]["metadata"]
|
||||
}
|
||||
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
|
||||
raw["session.synthetic"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { text: input["text"], description: input["description"], metadata: input["metadata"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||
type Endpoint4_12Input = { readonly sessionID: Endpoint4_12Request["params"]["sessionID"] }
|
||||
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
|
||||
raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_9Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||
type Endpoint3_9Input = { readonly sessionID: Endpoint3_9Request["params"]["sessionID"] }
|
||||
const Endpoint3_9 = (raw: RawClient["server.session"]) => (input: Endpoint3_9Input) =>
|
||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||
type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
||||
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
|
||||
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_10Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||
type Endpoint3_10Input = {
|
||||
readonly sessionID: Endpoint3_10Request["params"]["sessionID"]
|
||||
readonly messageID: Endpoint3_10Request["payload"]["messageID"]
|
||||
readonly files?: Endpoint3_10Request["payload"]["files"]
|
||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
|
||||
type Endpoint4_14Input = {
|
||||
readonly sessionID: Endpoint4_14Request["params"]["sessionID"]
|
||||
readonly messageID: Endpoint4_14Request["payload"]["messageID"]
|
||||
readonly files?: Endpoint4_14Request["payload"]["files"]
|
||||
}
|
||||
const Endpoint3_10 = (raw: RawClient["server.session"]) => (input: Endpoint3_10Input) =>
|
||||
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
|
||||
raw["session.revert.stage"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { messageID: input["messageID"], files: input["files"] },
|
||||
@@ -158,271 +231,355 @@ const Endpoint3_10 = (raw: RawClient["server.session"]) => (input: Endpoint3_10I
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint3_11Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||
type Endpoint3_11Input = { readonly sessionID: Endpoint3_11Request["params"]["sessionID"] }
|
||||
const Endpoint3_11 = (raw: RawClient["server.session"]) => (input: Endpoint3_11Input) =>
|
||||
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
|
||||
type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] }
|
||||
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
|
||||
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_12Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||
type Endpoint3_12Input = { readonly sessionID: Endpoint3_12Request["params"]["sessionID"] }
|
||||
const Endpoint3_12 = (raw: RawClient["server.session"]) => (input: Endpoint3_12Input) =>
|
||||
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
|
||||
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
|
||||
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
|
||||
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_13Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||
type Endpoint3_13Input = { readonly sessionID: Endpoint3_13Request["params"]["sessionID"] }
|
||||
const Endpoint3_13 = (raw: RawClient["server.session"]) => (input: Endpoint3_13Input) =>
|
||||
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.context"]>[0]
|
||||
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
|
||||
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
|
||||
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint3_14Request = Parameters<RawClient["server.session"]["session.history"]>[0]
|
||||
type Endpoint3_14Input = {
|
||||
readonly sessionID: Endpoint3_14Request["params"]["sessionID"]
|
||||
readonly limit?: Endpoint3_14Request["query"]["limit"]
|
||||
readonly after?: Endpoint3_14Request["query"]["after"]
|
||||
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
|
||||
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
|
||||
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
|
||||
raw["session.context.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
|
||||
type Endpoint4_19Input = {
|
||||
readonly sessionID: Endpoint4_19Request["params"]["sessionID"]
|
||||
readonly key: Endpoint4_19Request["params"]["key"]
|
||||
readonly value: Endpoint4_19Request["payload"]["value"]
|
||||
}
|
||||
const Endpoint3_14 = (raw: RawClient["server.session"]) => (input: Endpoint3_14Input) =>
|
||||
raw["session.history"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
query: { limit: input["limit"], after: input["after"] },
|
||||
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
|
||||
raw["session.context.entry.put"]({
|
||||
params: { sessionID: input["sessionID"], key: input["key"] },
|
||||
payload: { value: input["value"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_15Request = Parameters<RawClient["server.session"]["session.events"]>[0]
|
||||
type Endpoint3_15Input = {
|
||||
readonly sessionID: Endpoint3_15Request["params"]["sessionID"]
|
||||
readonly after?: Endpoint3_15Request["query"]["after"]
|
||||
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
|
||||
type Endpoint4_20Input = {
|
||||
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
|
||||
readonly key: Endpoint4_20Request["params"]["key"]
|
||||
}
|
||||
const Endpoint3_15 = (raw: RawClient["server.session"]) => (input: Endpoint3_15Input) =>
|
||||
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
|
||||
raw["session.context.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.log"]>[0]
|
||||
type Endpoint4_21Input = {
|
||||
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
|
||||
readonly after?: Endpoint4_21Request["query"]["after"]
|
||||
readonly follow?: Endpoint4_21Request["query"]["follow"]
|
||||
}
|
||||
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
|
||||
Stream.unwrap(
|
||||
raw["session.events"]({ params: { sessionID: input["sessionID"] }, query: { after: input["after"] } }).pipe(
|
||||
raw["session.log"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
query: { after: input["after"], follow: input["follow"] },
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
|
||||
),
|
||||
)
|
||||
|
||||
type Endpoint3_16Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||
type Endpoint3_16Input = { readonly sessionID: Endpoint3_16Request["params"]["sessionID"] }
|
||||
const Endpoint3_16 = (raw: RawClient["server.session"]) => (input: Endpoint3_16Input) =>
|
||||
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
|
||||
type Endpoint4_22Input = { readonly sessionID: Endpoint4_22Request["params"]["sessionID"] }
|
||||
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
|
||||
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint3_17Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||
type Endpoint3_17Input = {
|
||||
readonly sessionID: Endpoint3_17Request["params"]["sessionID"]
|
||||
readonly messageID: Endpoint3_17Request["params"]["messageID"]
|
||||
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.background"]>[0]
|
||||
type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
|
||||
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
|
||||
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.message"]>[0]
|
||||
type Endpoint4_24Input = {
|
||||
readonly sessionID: Endpoint4_24Request["params"]["sessionID"]
|
||||
readonly messageID: Endpoint4_24Request["params"]["messageID"]
|
||||
}
|
||||
const Endpoint3_17 = (raw: RawClient["server.session"]) => (input: Endpoint3_17Input) =>
|
||||
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
|
||||
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
const adaptGroup3 = (raw: RawClient["server.session"]) => ({
|
||||
list: Endpoint3_0(raw),
|
||||
create: Endpoint3_1(raw),
|
||||
active: Endpoint3_2(raw),
|
||||
get: Endpoint3_3(raw),
|
||||
switchAgent: Endpoint3_4(raw),
|
||||
switchModel: Endpoint3_5(raw),
|
||||
rename: Endpoint3_6(raw),
|
||||
prompt: Endpoint3_7(raw),
|
||||
compact: Endpoint3_8(raw),
|
||||
wait: Endpoint3_9(raw),
|
||||
stage: Endpoint3_10(raw),
|
||||
clear: Endpoint3_11(raw),
|
||||
commit: Endpoint3_12(raw),
|
||||
context: Endpoint3_13(raw),
|
||||
history: Endpoint3_14(raw),
|
||||
events: Endpoint3_15(raw),
|
||||
interrupt: Endpoint3_16(raw),
|
||||
message: Endpoint3_17(raw),
|
||||
const adaptGroup4 = (raw: RawClient["server.session"]) => ({
|
||||
list: Endpoint4_0(raw),
|
||||
create: Endpoint4_1(raw),
|
||||
active: Endpoint4_2(raw),
|
||||
get: Endpoint4_3(raw),
|
||||
fork: Endpoint4_4(raw),
|
||||
switchAgent: Endpoint4_5(raw),
|
||||
switchModel: Endpoint4_6(raw),
|
||||
rename: Endpoint4_7(raw),
|
||||
prompt: Endpoint4_8(raw),
|
||||
command: Endpoint4_9(raw),
|
||||
skill: Endpoint4_10(raw),
|
||||
synthetic: Endpoint4_11(raw),
|
||||
compact: Endpoint4_12(raw),
|
||||
wait: Endpoint4_13(raw),
|
||||
revertStage: Endpoint4_14(raw),
|
||||
revertClear: Endpoint4_15(raw),
|
||||
revertCommit: Endpoint4_16(raw),
|
||||
context: Endpoint4_17(raw),
|
||||
listContextEntries: Endpoint4_18(raw),
|
||||
putContextEntry: Endpoint4_19(raw),
|
||||
removeContextEntry: Endpoint4_20(raw),
|
||||
log: Endpoint4_21(raw),
|
||||
interrupt: Endpoint4_22(raw),
|
||||
background: Endpoint4_23(raw),
|
||||
message: Endpoint4_24(raw),
|
||||
})
|
||||
|
||||
type Endpoint4_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
||||
type Endpoint4_0Input = {
|
||||
readonly sessionID: Endpoint4_0Request["params"]["sessionID"]
|
||||
readonly limit?: Endpoint4_0Request["query"]["limit"]
|
||||
readonly order?: Endpoint4_0Request["query"]["order"]
|
||||
readonly cursor?: Endpoint4_0Request["query"]["cursor"]
|
||||
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
|
||||
type Endpoint5_0Input = {
|
||||
readonly sessionID: Endpoint5_0Request["params"]["sessionID"]
|
||||
readonly limit?: Endpoint5_0Request["query"]["limit"]
|
||||
readonly order?: Endpoint5_0Request["query"]["order"]
|
||||
readonly cursor?: Endpoint5_0Request["query"]["cursor"]
|
||||
}
|
||||
const Endpoint4_0 = (raw: RawClient["server.message"]) => (input: Endpoint4_0Input) =>
|
||||
const Endpoint5_0 = (raw: RawClient["server.message"]) => (input: Endpoint5_0Input) =>
|
||||
raw["session.messages"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
query: { limit: input["limit"], order: input["order"], cursor: input["cursor"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup4 = (raw: RawClient["server.message"]) => ({ list: Endpoint4_0(raw) })
|
||||
const adaptGroup5 = (raw: RawClient["server.message"]) => ({ list: Endpoint5_0(raw) })
|
||||
|
||||
type Endpoint5_0Request = Parameters<RawClient["server.model"]["model.list"]>[0]
|
||||
type Endpoint5_0Input = { readonly location?: Endpoint5_0Request["query"]["location"] }
|
||||
const Endpoint5_0 = (raw: RawClient["server.model"]) => (input?: Endpoint5_0Input) =>
|
||||
type Endpoint6_0Request = Parameters<RawClient["server.model"]["model.list"]>[0]
|
||||
type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] }
|
||||
const Endpoint6_0 = (raw: RawClient["server.model"]) => (input?: Endpoint6_0Input) =>
|
||||
raw["model.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup5 = (raw: RawClient["server.model"]) => ({ list: Endpoint5_0(raw) })
|
||||
type Endpoint6_1Request = Parameters<RawClient["server.model"]["model.default"]>[0]
|
||||
type Endpoint6_1Input = { readonly location?: Endpoint6_1Request["query"]["location"] }
|
||||
const Endpoint6_1 = (raw: RawClient["server.model"]) => (input?: Endpoint6_1Input) =>
|
||||
raw["model.default"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint6_0Request = Parameters<RawClient["server.provider"]["provider.list"]>[0]
|
||||
type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] }
|
||||
const Endpoint6_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint6_0Input) =>
|
||||
const adaptGroup6 = (raw: RawClient["server.model"]) => ({ list: Endpoint6_0(raw), default: Endpoint6_1(raw) })
|
||||
|
||||
type Endpoint7_0Request = Parameters<RawClient["server.generate"]["generate.text"]>[0]
|
||||
type Endpoint7_0Input = {
|
||||
readonly location?: Endpoint7_0Request["query"]["location"]
|
||||
readonly prompt: Endpoint7_0Request["payload"]["prompt"]
|
||||
readonly model?: Endpoint7_0Request["payload"]["model"]
|
||||
}
|
||||
const Endpoint7_0 = (raw: RawClient["server.generate"]) => (input: Endpoint7_0Input) =>
|
||||
raw["generate.text"]({
|
||||
query: { location: input["location"] },
|
||||
payload: { prompt: input["prompt"], model: input["model"] },
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
const adaptGroup7 = (raw: RawClient["server.generate"]) => ({ text: Endpoint7_0(raw) })
|
||||
|
||||
type Endpoint8_0Request = Parameters<RawClient["server.provider"]["provider.list"]>[0]
|
||||
type Endpoint8_0Input = { readonly location?: Endpoint8_0Request["query"]["location"] }
|
||||
const Endpoint8_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint8_0Input) =>
|
||||
raw["provider.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint6_1Request = Parameters<RawClient["server.provider"]["provider.get"]>[0]
|
||||
type Endpoint6_1Input = {
|
||||
readonly providerID: Endpoint6_1Request["params"]["providerID"]
|
||||
readonly location?: Endpoint6_1Request["query"]["location"]
|
||||
type Endpoint8_1Request = Parameters<RawClient["server.provider"]["provider.get"]>[0]
|
||||
type Endpoint8_1Input = {
|
||||
readonly providerID: Endpoint8_1Request["params"]["providerID"]
|
||||
readonly location?: Endpoint8_1Request["query"]["location"]
|
||||
}
|
||||
const Endpoint6_1 = (raw: RawClient["server.provider"]) => (input: Endpoint6_1Input) =>
|
||||
const Endpoint8_1 = (raw: RawClient["server.provider"]) => (input: Endpoint8_1Input) =>
|
||||
raw["provider.get"]({ params: { providerID: input["providerID"] }, query: { location: input["location"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
const adaptGroup6 = (raw: RawClient["server.provider"]) => ({ list: Endpoint6_0(raw), get: Endpoint6_1(raw) })
|
||||
const adaptGroup8 = (raw: RawClient["server.provider"]) => ({ list: Endpoint8_0(raw), get: Endpoint8_1(raw) })
|
||||
|
||||
type Endpoint7_0Request = Parameters<RawClient["server.integration"]["integration.list"]>[0]
|
||||
type Endpoint7_0Input = { readonly location?: Endpoint7_0Request["query"]["location"] }
|
||||
const Endpoint7_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint7_0Input) =>
|
||||
type Endpoint9_0Request = Parameters<RawClient["server.integration"]["integration.list"]>[0]
|
||||
type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] }
|
||||
const Endpoint9_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint9_0Input) =>
|
||||
raw["integration.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_1Request = Parameters<RawClient["server.integration"]["integration.get"]>[0]
|
||||
type Endpoint7_1Input = {
|
||||
readonly integrationID: Endpoint7_1Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint7_1Request["query"]["location"]
|
||||
type Endpoint9_1Request = Parameters<RawClient["server.integration"]["integration.get"]>[0]
|
||||
type Endpoint9_1Input = {
|
||||
readonly integrationID: Endpoint9_1Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint9_1Request["query"]["location"]
|
||||
}
|
||||
const Endpoint7_1 = (raw: RawClient["server.integration"]) => (input: Endpoint7_1Input) =>
|
||||
const Endpoint9_1 = (raw: RawClient["server.integration"]) => (input: Endpoint9_1Input) =>
|
||||
raw["integration.get"]({
|
||||
params: { integrationID: input["integrationID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_2Request = Parameters<RawClient["server.integration"]["integration.connect.key"]>[0]
|
||||
type Endpoint7_2Input = {
|
||||
readonly integrationID: Endpoint7_2Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint7_2Request["query"]["location"]
|
||||
readonly key: Endpoint7_2Request["payload"]["key"]
|
||||
readonly label?: Endpoint7_2Request["payload"]["label"]
|
||||
type Endpoint9_2Request = Parameters<RawClient["server.integration"]["integration.connect.key"]>[0]
|
||||
type Endpoint9_2Input = {
|
||||
readonly integrationID: Endpoint9_2Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint9_2Request["query"]["location"]
|
||||
readonly key: Endpoint9_2Request["payload"]["key"]
|
||||
readonly label?: Endpoint9_2Request["payload"]["label"]
|
||||
}
|
||||
const Endpoint7_2 = (raw: RawClient["server.integration"]) => (input: Endpoint7_2Input) =>
|
||||
const Endpoint9_2 = (raw: RawClient["server.integration"]) => (input: Endpoint9_2Input) =>
|
||||
raw["integration.connect.key"]({
|
||||
params: { integrationID: input["integrationID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { key: input["key"], label: input["label"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
|
||||
type Endpoint7_3Input = {
|
||||
readonly integrationID: Endpoint7_3Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint7_3Request["query"]["location"]
|
||||
readonly methodID: Endpoint7_3Request["payload"]["methodID"]
|
||||
readonly inputs: Endpoint7_3Request["payload"]["inputs"]
|
||||
readonly label?: Endpoint7_3Request["payload"]["label"]
|
||||
type Endpoint9_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
|
||||
type Endpoint9_3Input = {
|
||||
readonly integrationID: Endpoint9_3Request["params"]["integrationID"]
|
||||
readonly location?: Endpoint9_3Request["query"]["location"]
|
||||
readonly methodID: Endpoint9_3Request["payload"]["methodID"]
|
||||
readonly inputs: Endpoint9_3Request["payload"]["inputs"]
|
||||
readonly label?: Endpoint9_3Request["payload"]["label"]
|
||||
}
|
||||
const Endpoint7_3 = (raw: RawClient["server.integration"]) => (input: Endpoint7_3Input) =>
|
||||
const Endpoint9_3 = (raw: RawClient["server.integration"]) => (input: Endpoint9_3Input) =>
|
||||
raw["integration.connect.oauth"]({
|
||||
params: { integrationID: input["integrationID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
|
||||
type Endpoint7_4Input = {
|
||||
readonly attemptID: Endpoint7_4Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint7_4Request["query"]["location"]
|
||||
type Endpoint9_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
|
||||
type Endpoint9_4Input = {
|
||||
readonly attemptID: Endpoint9_4Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint9_4Request["query"]["location"]
|
||||
}
|
||||
const Endpoint7_4 = (raw: RawClient["server.integration"]) => (input: Endpoint7_4Input) =>
|
||||
const Endpoint9_4 = (raw: RawClient["server.integration"]) => (input: Endpoint9_4Input) =>
|
||||
raw["integration.attempt.status"]({
|
||||
params: { attemptID: input["attemptID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
|
||||
type Endpoint7_5Input = {
|
||||
readonly attemptID: Endpoint7_5Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint7_5Request["query"]["location"]
|
||||
readonly code?: Endpoint7_5Request["payload"]["code"]
|
||||
type Endpoint9_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
|
||||
type Endpoint9_5Input = {
|
||||
readonly attemptID: Endpoint9_5Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint9_5Request["query"]["location"]
|
||||
readonly code?: Endpoint9_5Request["payload"]["code"]
|
||||
}
|
||||
const Endpoint7_5 = (raw: RawClient["server.integration"]) => (input: Endpoint7_5Input) =>
|
||||
const Endpoint9_5 = (raw: RawClient["server.integration"]) => (input: Endpoint9_5Input) =>
|
||||
raw["integration.attempt.complete"]({
|
||||
params: { attemptID: input["attemptID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { code: input["code"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint7_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
|
||||
type Endpoint7_6Input = {
|
||||
readonly attemptID: Endpoint7_6Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint7_6Request["query"]["location"]
|
||||
type Endpoint9_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
|
||||
type Endpoint9_6Input = {
|
||||
readonly attemptID: Endpoint9_6Request["params"]["attemptID"]
|
||||
readonly location?: Endpoint9_6Request["query"]["location"]
|
||||
}
|
||||
const Endpoint7_6 = (raw: RawClient["server.integration"]) => (input: Endpoint7_6Input) =>
|
||||
const Endpoint9_6 = (raw: RawClient["server.integration"]) => (input: Endpoint9_6Input) =>
|
||||
raw["integration.attempt.cancel"]({
|
||||
params: { attemptID: input["attemptID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup7 = (raw: RawClient["server.integration"]) => ({
|
||||
list: Endpoint7_0(raw),
|
||||
get: Endpoint7_1(raw),
|
||||
connectKey: Endpoint7_2(raw),
|
||||
connectOauth: Endpoint7_3(raw),
|
||||
attemptStatus: Endpoint7_4(raw),
|
||||
attemptComplete: Endpoint7_5(raw),
|
||||
attemptCancel: Endpoint7_6(raw),
|
||||
const adaptGroup9 = (raw: RawClient["server.integration"]) => ({
|
||||
list: Endpoint9_0(raw),
|
||||
get: Endpoint9_1(raw),
|
||||
connectKey: Endpoint9_2(raw),
|
||||
connectOauth: Endpoint9_3(raw),
|
||||
attemptStatus: Endpoint9_4(raw),
|
||||
attemptComplete: Endpoint9_5(raw),
|
||||
attemptCancel: Endpoint9_6(raw),
|
||||
})
|
||||
|
||||
type Endpoint8_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
|
||||
type Endpoint8_0Input = {
|
||||
readonly credentialID: Endpoint8_0Request["params"]["credentialID"]
|
||||
readonly location?: Endpoint8_0Request["query"]["location"]
|
||||
readonly label: Endpoint8_0Request["payload"]["label"]
|
||||
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
|
||||
type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] }
|
||||
const Endpoint10_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_0Input) =>
|
||||
raw["mcp.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ list: Endpoint10_0(raw) })
|
||||
|
||||
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
|
||||
type Endpoint11_0Input = {
|
||||
readonly credentialID: Endpoint11_0Request["params"]["credentialID"]
|
||||
readonly location?: Endpoint11_0Request["query"]["location"]
|
||||
readonly label: Endpoint11_0Request["payload"]["label"]
|
||||
}
|
||||
const Endpoint8_0 = (raw: RawClient["server.credential"]) => (input: Endpoint8_0Input) =>
|
||||
const Endpoint11_0 = (raw: RawClient["server.credential"]) => (input: Endpoint11_0Input) =>
|
||||
raw["credential.update"]({
|
||||
params: { credentialID: input["credentialID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { label: input["label"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint8_1Request = Parameters<RawClient["server.credential"]["credential.remove"]>[0]
|
||||
type Endpoint8_1Input = {
|
||||
readonly credentialID: Endpoint8_1Request["params"]["credentialID"]
|
||||
readonly location?: Endpoint8_1Request["query"]["location"]
|
||||
type Endpoint11_1Request = Parameters<RawClient["server.credential"]["credential.remove"]>[0]
|
||||
type Endpoint11_1Input = {
|
||||
readonly credentialID: Endpoint11_1Request["params"]["credentialID"]
|
||||
readonly location?: Endpoint11_1Request["query"]["location"]
|
||||
}
|
||||
const Endpoint8_1 = (raw: RawClient["server.credential"]) => (input: Endpoint8_1Input) =>
|
||||
const Endpoint11_1 = (raw: RawClient["server.credential"]) => (input: Endpoint11_1Input) =>
|
||||
raw["credential.remove"]({
|
||||
params: { credentialID: input["credentialID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup8 = (raw: RawClient["server.credential"]) => ({ update: Endpoint8_0(raw), remove: Endpoint8_1(raw) })
|
||||
const adaptGroup11 = (raw: RawClient["server.credential"]) => ({ update: Endpoint11_0(raw), remove: Endpoint11_1(raw) })
|
||||
|
||||
type Endpoint9_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
|
||||
type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] }
|
||||
const Endpoint9_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint9_0Input) =>
|
||||
type Endpoint12_0Request = Parameters<RawClient["server.project"]["project.current"]>[0]
|
||||
type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] }
|
||||
const Endpoint12_0 = (raw: RawClient["server.project"]) => (input?: Endpoint12_0Input) =>
|
||||
raw["project.current"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint12_1Request = Parameters<RawClient["server.project"]["project.directories"]>[0]
|
||||
type Endpoint12_1Input = {
|
||||
readonly projectID: Endpoint12_1Request["params"]["projectID"]
|
||||
readonly location?: Endpoint12_1Request["query"]["location"]
|
||||
}
|
||||
const Endpoint12_1 = (raw: RawClient["server.project"]) => (input: Endpoint12_1Input) =>
|
||||
raw["project.directories"]({
|
||||
params: { projectID: input["projectID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup12 = (raw: RawClient["server.project"]) => ({
|
||||
current: Endpoint12_0(raw),
|
||||
directories: Endpoint12_1(raw),
|
||||
})
|
||||
|
||||
type Endpoint13_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
|
||||
type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] }
|
||||
const Endpoint13_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint13_0Input) =>
|
||||
raw["permission.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint9_1Request = Parameters<RawClient["server.permission"]["permission.saved.list"]>[0]
|
||||
type Endpoint9_1Input = { readonly projectID?: Endpoint9_1Request["query"]["projectID"] }
|
||||
const Endpoint9_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint9_1Input) =>
|
||||
type Endpoint13_1Request = Parameters<RawClient["server.permission"]["permission.saved.list"]>[0]
|
||||
type Endpoint13_1Input = { readonly projectID?: Endpoint13_1Request["query"]["projectID"] }
|
||||
const Endpoint13_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint13_1Input) =>
|
||||
raw["permission.saved.list"]({ query: { projectID: input?.["projectID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint9_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
|
||||
type Endpoint9_2Input = { readonly id: Endpoint9_2Request["params"]["id"] }
|
||||
const Endpoint9_2 = (raw: RawClient["server.permission"]) => (input: Endpoint9_2Input) =>
|
||||
type Endpoint13_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
|
||||
type Endpoint13_2Input = { readonly id: Endpoint13_2Request["params"]["id"] }
|
||||
const Endpoint13_2 = (raw: RawClient["server.permission"]) => (input: Endpoint13_2Input) =>
|
||||
raw["permission.saved.remove"]({ params: { id: input["id"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint9_3Request = Parameters<RawClient["server.permission"]["session.permission.create"]>[0]
|
||||
type Endpoint9_3Input = {
|
||||
readonly sessionID: Endpoint9_3Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint9_3Request["payload"]["id"]
|
||||
readonly action: Endpoint9_3Request["payload"]["action"]
|
||||
readonly resources: Endpoint9_3Request["payload"]["resources"]
|
||||
readonly save?: Endpoint9_3Request["payload"]["save"]
|
||||
readonly metadata?: Endpoint9_3Request["payload"]["metadata"]
|
||||
readonly source?: Endpoint9_3Request["payload"]["source"]
|
||||
readonly agent?: Endpoint9_3Request["payload"]["agent"]
|
||||
type Endpoint13_3Request = Parameters<RawClient["server.permission"]["session.permission.create"]>[0]
|
||||
type Endpoint13_3Input = {
|
||||
readonly sessionID: Endpoint13_3Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint13_3Request["payload"]["id"]
|
||||
readonly action: Endpoint13_3Request["payload"]["action"]
|
||||
readonly resources: Endpoint13_3Request["payload"]["resources"]
|
||||
readonly save?: Endpoint13_3Request["payload"]["save"]
|
||||
readonly metadata?: Endpoint13_3Request["payload"]["metadata"]
|
||||
readonly source?: Endpoint13_3Request["payload"]["source"]
|
||||
readonly agent?: Endpoint13_3Request["payload"]["agent"]
|
||||
}
|
||||
const Endpoint9_3 = (raw: RawClient["server.permission"]) => (input: Endpoint9_3Input) =>
|
||||
const Endpoint13_3 = (raw: RawClient["server.permission"]) => (input: Endpoint13_3Input) =>
|
||||
raw["session.permission.create"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: {
|
||||
@@ -439,87 +596,87 @@ const Endpoint9_3 = (raw: RawClient["server.permission"]) => (input: Endpoint9_3
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint9_4Request = Parameters<RawClient["server.permission"]["session.permission.list"]>[0]
|
||||
type Endpoint9_4Input = { readonly sessionID: Endpoint9_4Request["params"]["sessionID"] }
|
||||
const Endpoint9_4 = (raw: RawClient["server.permission"]) => (input: Endpoint9_4Input) =>
|
||||
type Endpoint13_4Request = Parameters<RawClient["server.permission"]["session.permission.list"]>[0]
|
||||
type Endpoint13_4Input = { readonly sessionID: Endpoint13_4Request["params"]["sessionID"] }
|
||||
const Endpoint13_4 = (raw: RawClient["server.permission"]) => (input: Endpoint13_4Input) =>
|
||||
raw["session.permission.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint9_5Request = Parameters<RawClient["server.permission"]["session.permission.get"]>[0]
|
||||
type Endpoint9_5Input = {
|
||||
readonly sessionID: Endpoint9_5Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint9_5Request["params"]["requestID"]
|
||||
type Endpoint13_5Request = Parameters<RawClient["server.permission"]["session.permission.get"]>[0]
|
||||
type Endpoint13_5Input = {
|
||||
readonly sessionID: Endpoint13_5Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint13_5Request["params"]["requestID"]
|
||||
}
|
||||
const Endpoint9_5 = (raw: RawClient["server.permission"]) => (input: Endpoint9_5Input) =>
|
||||
const Endpoint13_5 = (raw: RawClient["server.permission"]) => (input: Endpoint13_5Input) =>
|
||||
raw["session.permission.get"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint9_6Request = Parameters<RawClient["server.permission"]["session.permission.reply"]>[0]
|
||||
type Endpoint9_6Input = {
|
||||
readonly sessionID: Endpoint9_6Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint9_6Request["params"]["requestID"]
|
||||
readonly reply: Endpoint9_6Request["payload"]["reply"]
|
||||
readonly message?: Endpoint9_6Request["payload"]["message"]
|
||||
type Endpoint13_6Request = Parameters<RawClient["server.permission"]["session.permission.reply"]>[0]
|
||||
type Endpoint13_6Input = {
|
||||
readonly sessionID: Endpoint13_6Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint13_6Request["params"]["requestID"]
|
||||
readonly reply: Endpoint13_6Request["payload"]["reply"]
|
||||
readonly message?: Endpoint13_6Request["payload"]["message"]
|
||||
}
|
||||
const Endpoint9_6 = (raw: RawClient["server.permission"]) => (input: Endpoint9_6Input) =>
|
||||
const Endpoint13_6 = (raw: RawClient["server.permission"]) => (input: Endpoint13_6Input) =>
|
||||
raw["session.permission.reply"]({
|
||||
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
|
||||
payload: { reply: input["reply"], message: input["message"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup9 = (raw: RawClient["server.permission"]) => ({
|
||||
listRequests: Endpoint9_0(raw),
|
||||
listSaved: Endpoint9_1(raw),
|
||||
removeSaved: Endpoint9_2(raw),
|
||||
create: Endpoint9_3(raw),
|
||||
list: Endpoint9_4(raw),
|
||||
get: Endpoint9_5(raw),
|
||||
reply: Endpoint9_6(raw),
|
||||
const adaptGroup13 = (raw: RawClient["server.permission"]) => ({
|
||||
listRequests: Endpoint13_0(raw),
|
||||
listSaved: Endpoint13_1(raw),
|
||||
removeSaved: Endpoint13_2(raw),
|
||||
create: Endpoint13_3(raw),
|
||||
list: Endpoint13_4(raw),
|
||||
get: Endpoint13_5(raw),
|
||||
reply: Endpoint13_6(raw),
|
||||
})
|
||||
|
||||
type Endpoint10_0Request = Parameters<RawClient["server.fs"]["fs.list"]>[0]
|
||||
type Endpoint10_0Input = {
|
||||
readonly location?: Endpoint10_0Request["query"]["location"]
|
||||
readonly path?: Endpoint10_0Request["query"]["path"]
|
||||
type Endpoint14_0Request = Parameters<RawClient["server.fs"]["fs.list"]>[0]
|
||||
type Endpoint14_0Input = {
|
||||
readonly location?: Endpoint14_0Request["query"]["location"]
|
||||
readonly path?: Endpoint14_0Request["query"]["path"]
|
||||
}
|
||||
const Endpoint10_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint10_0Input) =>
|
||||
const Endpoint14_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint14_0Input) =>
|
||||
raw["fs.list"]({ query: { location: input?.["location"], path: input?.["path"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint10_1Request = Parameters<RawClient["server.fs"]["fs.find"]>[0]
|
||||
type Endpoint10_1Input = {
|
||||
readonly location?: Endpoint10_1Request["query"]["location"]
|
||||
readonly query: Endpoint10_1Request["query"]["query"]
|
||||
readonly type?: Endpoint10_1Request["query"]["type"]
|
||||
readonly limit?: Endpoint10_1Request["query"]["limit"]
|
||||
type Endpoint14_1Request = Parameters<RawClient["server.fs"]["fs.find"]>[0]
|
||||
type Endpoint14_1Input = {
|
||||
readonly location?: Endpoint14_1Request["query"]["location"]
|
||||
readonly query: Endpoint14_1Request["query"]["query"]
|
||||
readonly type?: Endpoint14_1Request["query"]["type"]
|
||||
readonly limit?: Endpoint14_1Request["query"]["limit"]
|
||||
}
|
||||
const Endpoint10_1 = (raw: RawClient["server.fs"]) => (input: Endpoint10_1Input) =>
|
||||
const Endpoint14_1 = (raw: RawClient["server.fs"]) => (input: Endpoint14_1Input) =>
|
||||
raw["fs.find"]({
|
||||
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup10 = (raw: RawClient["server.fs"]) => ({ list: Endpoint10_0(raw), find: Endpoint10_1(raw) })
|
||||
const adaptGroup14 = (raw: RawClient["server.fs"]) => ({ list: Endpoint14_0(raw), find: Endpoint14_1(raw) })
|
||||
|
||||
type Endpoint11_0Request = Parameters<RawClient["server.command"]["command.list"]>[0]
|
||||
type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] }
|
||||
const Endpoint11_0 = (raw: RawClient["server.command"]) => (input?: Endpoint11_0Input) =>
|
||||
type Endpoint15_0Request = Parameters<RawClient["server.command"]["command.list"]>[0]
|
||||
type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] }
|
||||
const Endpoint15_0 = (raw: RawClient["server.command"]) => (input?: Endpoint15_0Input) =>
|
||||
raw["command.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup11 = (raw: RawClient["server.command"]) => ({ list: Endpoint11_0(raw) })
|
||||
const adaptGroup15 = (raw: RawClient["server.command"]) => ({ list: Endpoint15_0(raw) })
|
||||
|
||||
type Endpoint12_0Request = Parameters<RawClient["server.skill"]["skill.list"]>[0]
|
||||
type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] }
|
||||
const Endpoint12_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint12_0Input) =>
|
||||
type Endpoint16_0Request = Parameters<RawClient["server.skill"]["skill.list"]>[0]
|
||||
type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] }
|
||||
const Endpoint16_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint16_0Input) =>
|
||||
raw["skill.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup12 = (raw: RawClient["server.skill"]) => ({ list: Endpoint12_0(raw) })
|
||||
const adaptGroup16 = (raw: RawClient["server.skill"]) => ({ list: Endpoint16_0(raw) })
|
||||
|
||||
const Endpoint13_0 = (raw: RawClient["server.event"]) => () =>
|
||||
const Endpoint17_0 = (raw: RawClient["server.event"]) => () =>
|
||||
Stream.unwrap(
|
||||
raw["event.subscribe"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
@@ -527,23 +684,31 @@ const Endpoint13_0 = (raw: RawClient["server.event"]) => () =>
|
||||
),
|
||||
)
|
||||
|
||||
const adaptGroup13 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint13_0(raw) })
|
||||
const Endpoint17_1 = (raw: RawClient["server.event"]) => () =>
|
||||
Stream.unwrap(
|
||||
raw["event.changes"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
|
||||
),
|
||||
)
|
||||
|
||||
type Endpoint14_0Request = Parameters<RawClient["server.pty"]["pty.list"]>[0]
|
||||
type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
|
||||
const Endpoint14_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_0Input) =>
|
||||
const adaptGroup17 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint17_0(raw), changes: Endpoint17_1(raw) })
|
||||
|
||||
type Endpoint18_0Request = Parameters<RawClient["server.pty"]["pty.list"]>[0]
|
||||
type Endpoint18_0Input = { readonly location?: Endpoint18_0Request["query"]["location"] }
|
||||
const Endpoint18_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint18_0Input) =>
|
||||
raw["pty.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint14_1Request = Parameters<RawClient["server.pty"]["pty.create"]>[0]
|
||||
type Endpoint14_1Input = {
|
||||
readonly location?: Endpoint14_1Request["query"]["location"]
|
||||
readonly command?: Endpoint14_1Request["payload"]["command"]
|
||||
readonly args?: Endpoint14_1Request["payload"]["args"]
|
||||
readonly cwd?: Endpoint14_1Request["payload"]["cwd"]
|
||||
readonly title?: Endpoint14_1Request["payload"]["title"]
|
||||
readonly env?: Endpoint14_1Request["payload"]["env"]
|
||||
type Endpoint18_1Request = Parameters<RawClient["server.pty"]["pty.create"]>[0]
|
||||
type Endpoint18_1Input = {
|
||||
readonly location?: Endpoint18_1Request["query"]["location"]
|
||||
readonly command?: Endpoint18_1Request["payload"]["command"]
|
||||
readonly args?: Endpoint18_1Request["payload"]["args"]
|
||||
readonly cwd?: Endpoint18_1Request["payload"]["cwd"]
|
||||
readonly title?: Endpoint18_1Request["payload"]["title"]
|
||||
readonly env?: Endpoint18_1Request["payload"]["env"]
|
||||
}
|
||||
const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Input) =>
|
||||
const Endpoint18_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint18_1Input) =>
|
||||
raw["pty.create"]({
|
||||
query: { location: input?.["location"] },
|
||||
payload: {
|
||||
@@ -555,162 +720,227 @@ const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Inpu
|
||||
},
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint14_2Request = Parameters<RawClient["server.pty"]["pty.get"]>[0]
|
||||
type Endpoint14_2Input = {
|
||||
readonly ptyID: Endpoint14_2Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint14_2Request["query"]["location"]
|
||||
type Endpoint18_2Request = Parameters<RawClient["server.pty"]["pty.get"]>[0]
|
||||
type Endpoint18_2Input = {
|
||||
readonly ptyID: Endpoint18_2Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint18_2Request["query"]["location"]
|
||||
}
|
||||
const Endpoint14_2 = (raw: RawClient["server.pty"]) => (input: Endpoint14_2Input) =>
|
||||
const Endpoint18_2 = (raw: RawClient["server.pty"]) => (input: Endpoint18_2Input) =>
|
||||
raw["pty.get"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint14_3Request = Parameters<RawClient["server.pty"]["pty.update"]>[0]
|
||||
type Endpoint14_3Input = {
|
||||
readonly ptyID: Endpoint14_3Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint14_3Request["query"]["location"]
|
||||
readonly title?: Endpoint14_3Request["payload"]["title"]
|
||||
readonly size?: Endpoint14_3Request["payload"]["size"]
|
||||
type Endpoint18_3Request = Parameters<RawClient["server.pty"]["pty.update"]>[0]
|
||||
type Endpoint18_3Input = {
|
||||
readonly ptyID: Endpoint18_3Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint18_3Request["query"]["location"]
|
||||
readonly title?: Endpoint18_3Request["payload"]["title"]
|
||||
readonly size?: Endpoint18_3Request["payload"]["size"]
|
||||
}
|
||||
const Endpoint14_3 = (raw: RawClient["server.pty"]) => (input: Endpoint14_3Input) =>
|
||||
const Endpoint18_3 = (raw: RawClient["server.pty"]) => (input: Endpoint18_3Input) =>
|
||||
raw["pty.update"]({
|
||||
params: { ptyID: input["ptyID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { title: input["title"], size: input["size"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint14_4Request = Parameters<RawClient["server.pty"]["pty.remove"]>[0]
|
||||
type Endpoint14_4Input = {
|
||||
readonly ptyID: Endpoint14_4Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint14_4Request["query"]["location"]
|
||||
type Endpoint18_4Request = Parameters<RawClient["server.pty"]["pty.remove"]>[0]
|
||||
type Endpoint18_4Input = {
|
||||
readonly ptyID: Endpoint18_4Request["params"]["ptyID"]
|
||||
readonly location?: Endpoint18_4Request["query"]["location"]
|
||||
}
|
||||
const Endpoint14_4 = (raw: RawClient["server.pty"]) => (input: Endpoint14_4Input) =>
|
||||
const Endpoint18_4 = (raw: RawClient["server.pty"]) => (input: Endpoint18_4Input) =>
|
||||
raw["pty.remove"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
const adaptGroup14 = (raw: RawClient["server.pty"]) => ({
|
||||
list: Endpoint14_0(raw),
|
||||
create: Endpoint14_1(raw),
|
||||
get: Endpoint14_2(raw),
|
||||
update: Endpoint14_3(raw),
|
||||
remove: Endpoint14_4(raw),
|
||||
const adaptGroup18 = (raw: RawClient["server.pty"]) => ({
|
||||
list: Endpoint18_0(raw),
|
||||
create: Endpoint18_1(raw),
|
||||
get: Endpoint18_2(raw),
|
||||
update: Endpoint18_3(raw),
|
||||
remove: Endpoint18_4(raw),
|
||||
})
|
||||
|
||||
type Endpoint15_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
|
||||
type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] }
|
||||
const Endpoint15_0 = (raw: RawClient["server.question"]) => (input?: Endpoint15_0Input) =>
|
||||
type Endpoint19_0Request = Parameters<RawClient["server.shell"]["shell.list"]>[0]
|
||||
type Endpoint19_0Input = { readonly location?: Endpoint19_0Request["query"]["location"] }
|
||||
const Endpoint19_0 = (raw: RawClient["server.shell"]) => (input?: Endpoint19_0Input) =>
|
||||
raw["shell.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint19_1Request = Parameters<RawClient["server.shell"]["shell.create"]>[0]
|
||||
type Endpoint19_1Input = {
|
||||
readonly location?: Endpoint19_1Request["query"]["location"]
|
||||
readonly command: Endpoint19_1Request["payload"]["command"]
|
||||
readonly cwd?: Endpoint19_1Request["payload"]["cwd"]
|
||||
readonly timeout?: Endpoint19_1Request["payload"]["timeout"]
|
||||
readonly metadata?: Endpoint19_1Request["payload"]["metadata"]
|
||||
}
|
||||
const Endpoint19_1 = (raw: RawClient["server.shell"]) => (input: Endpoint19_1Input) =>
|
||||
raw["shell.create"]({
|
||||
query: { location: input["location"] },
|
||||
payload: { command: input["command"], cwd: input["cwd"], timeout: input["timeout"], metadata: input["metadata"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint19_2Request = Parameters<RawClient["server.shell"]["shell.get"]>[0]
|
||||
type Endpoint19_2Input = {
|
||||
readonly id: Endpoint19_2Request["params"]["id"]
|
||||
readonly location?: Endpoint19_2Request["query"]["location"]
|
||||
}
|
||||
const Endpoint19_2 = (raw: RawClient["server.shell"]) => (input: Endpoint19_2Input) =>
|
||||
raw["shell.get"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
type Endpoint19_3Request = Parameters<RawClient["server.shell"]["shell.output"]>[0]
|
||||
type Endpoint19_3Input = {
|
||||
readonly id: Endpoint19_3Request["params"]["id"]
|
||||
readonly location?: Endpoint19_3Request["query"]["location"]
|
||||
readonly cursor?: Endpoint19_3Request["query"]["cursor"]
|
||||
readonly limit?: Endpoint19_3Request["query"]["limit"]
|
||||
}
|
||||
const Endpoint19_3 = (raw: RawClient["server.shell"]) => (input: Endpoint19_3Input) =>
|
||||
raw["shell.output"]({
|
||||
params: { id: input["id"] },
|
||||
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint19_4Request = Parameters<RawClient["server.shell"]["shell.remove"]>[0]
|
||||
type Endpoint19_4Input = {
|
||||
readonly id: Endpoint19_4Request["params"]["id"]
|
||||
readonly location?: Endpoint19_4Request["query"]["location"]
|
||||
}
|
||||
const Endpoint19_4 = (raw: RawClient["server.shell"]) => (input: Endpoint19_4Input) =>
|
||||
raw["shell.remove"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
const adaptGroup19 = (raw: RawClient["server.shell"]) => ({
|
||||
list: Endpoint19_0(raw),
|
||||
create: Endpoint19_1(raw),
|
||||
get: Endpoint19_2(raw),
|
||||
output: Endpoint19_3(raw),
|
||||
remove: Endpoint19_4(raw),
|
||||
})
|
||||
|
||||
type Endpoint20_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
|
||||
type Endpoint20_0Input = { readonly location?: Endpoint20_0Request["query"]["location"] }
|
||||
const Endpoint20_0 = (raw: RawClient["server.question"]) => (input?: Endpoint20_0Input) =>
|
||||
raw["question.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint15_1Request = Parameters<RawClient["server.question"]["session.question.list"]>[0]
|
||||
type Endpoint15_1Input = { readonly sessionID: Endpoint15_1Request["params"]["sessionID"] }
|
||||
const Endpoint15_1 = (raw: RawClient["server.question"]) => (input: Endpoint15_1Input) =>
|
||||
type Endpoint20_1Request = Parameters<RawClient["server.question"]["session.question.list"]>[0]
|
||||
type Endpoint20_1Input = { readonly sessionID: Endpoint20_1Request["params"]["sessionID"] }
|
||||
const Endpoint20_1 = (raw: RawClient["server.question"]) => (input: Endpoint20_1Input) =>
|
||||
raw["session.question.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint15_2Request = Parameters<RawClient["server.question"]["session.question.reply"]>[0]
|
||||
type Endpoint15_2Input = {
|
||||
readonly sessionID: Endpoint15_2Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint15_2Request["params"]["requestID"]
|
||||
readonly answers: Endpoint15_2Request["payload"]["answers"]
|
||||
type Endpoint20_2Request = Parameters<RawClient["server.question"]["session.question.reply"]>[0]
|
||||
type Endpoint20_2Input = {
|
||||
readonly sessionID: Endpoint20_2Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint20_2Request["params"]["requestID"]
|
||||
readonly answers: Endpoint20_2Request["payload"]["answers"]
|
||||
}
|
||||
const Endpoint15_2 = (raw: RawClient["server.question"]) => (input: Endpoint15_2Input) =>
|
||||
const Endpoint20_2 = (raw: RawClient["server.question"]) => (input: Endpoint20_2Input) =>
|
||||
raw["session.question.reply"]({
|
||||
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
|
||||
payload: { answers: input["answers"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint15_3Request = Parameters<RawClient["server.question"]["session.question.reject"]>[0]
|
||||
type Endpoint15_3Input = {
|
||||
readonly sessionID: Endpoint15_3Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint15_3Request["params"]["requestID"]
|
||||
type Endpoint20_3Request = Parameters<RawClient["server.question"]["session.question.reject"]>[0]
|
||||
type Endpoint20_3Input = {
|
||||
readonly sessionID: Endpoint20_3Request["params"]["sessionID"]
|
||||
readonly requestID: Endpoint20_3Request["params"]["requestID"]
|
||||
}
|
||||
const Endpoint15_3 = (raw: RawClient["server.question"]) => (input: Endpoint15_3Input) =>
|
||||
const Endpoint20_3 = (raw: RawClient["server.question"]) => (input: Endpoint20_3Input) =>
|
||||
raw["session.question.reject"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
)
|
||||
|
||||
const adaptGroup15 = (raw: RawClient["server.question"]) => ({
|
||||
listRequests: Endpoint15_0(raw),
|
||||
list: Endpoint15_1(raw),
|
||||
reply: Endpoint15_2(raw),
|
||||
reject: Endpoint15_3(raw),
|
||||
const adaptGroup20 = (raw: RawClient["server.question"]) => ({
|
||||
listRequests: Endpoint20_0(raw),
|
||||
list: Endpoint20_1(raw),
|
||||
reply: Endpoint20_2(raw),
|
||||
reject: Endpoint20_3(raw),
|
||||
})
|
||||
|
||||
type Endpoint16_0Request = Parameters<RawClient["server.reference"]["reference.list"]>[0]
|
||||
type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] }
|
||||
const Endpoint16_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint16_0Input) =>
|
||||
type Endpoint21_0Request = Parameters<RawClient["server.reference"]["reference.list"]>[0]
|
||||
type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] }
|
||||
const Endpoint21_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint21_0Input) =>
|
||||
raw["reference.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup16 = (raw: RawClient["server.reference"]) => ({ list: Endpoint16_0(raw) })
|
||||
const adaptGroup21 = (raw: RawClient["server.reference"]) => ({ list: Endpoint21_0(raw) })
|
||||
|
||||
type Endpoint17_0Request = Parameters<RawClient["server.projectCopy"]["projectCopy.create"]>[0]
|
||||
type Endpoint17_0Input = {
|
||||
readonly projectID: Endpoint17_0Request["params"]["projectID"]
|
||||
readonly location?: Endpoint17_0Request["query"]["location"]
|
||||
readonly strategy: Endpoint17_0Request["payload"]["strategy"]
|
||||
readonly directory: Endpoint17_0Request["payload"]["directory"]
|
||||
readonly name?: Endpoint17_0Request["payload"]["name"]
|
||||
type Endpoint22_0Request = Parameters<RawClient["server.projectCopy"]["projectCopy.create"]>[0]
|
||||
type Endpoint22_0Input = {
|
||||
readonly projectID: Endpoint22_0Request["params"]["projectID"]
|
||||
readonly location?: Endpoint22_0Request["query"]["location"]
|
||||
readonly strategy: Endpoint22_0Request["payload"]["strategy"]
|
||||
readonly directory: Endpoint22_0Request["payload"]["directory"]
|
||||
readonly name?: Endpoint22_0Request["payload"]["name"]
|
||||
}
|
||||
const Endpoint17_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_0Input) =>
|
||||
const Endpoint22_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint22_0Input) =>
|
||||
raw["projectCopy.create"]({
|
||||
params: { projectID: input["projectID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { strategy: input["strategy"], directory: input["directory"], name: input["name"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint17_1Request = Parameters<RawClient["server.projectCopy"]["projectCopy.remove"]>[0]
|
||||
type Endpoint17_1Input = {
|
||||
readonly projectID: Endpoint17_1Request["params"]["projectID"]
|
||||
readonly location?: Endpoint17_1Request["query"]["location"]
|
||||
readonly directory: Endpoint17_1Request["payload"]["directory"]
|
||||
readonly force: Endpoint17_1Request["payload"]["force"]
|
||||
type Endpoint22_1Request = Parameters<RawClient["server.projectCopy"]["projectCopy.remove"]>[0]
|
||||
type Endpoint22_1Input = {
|
||||
readonly projectID: Endpoint22_1Request["params"]["projectID"]
|
||||
readonly location?: Endpoint22_1Request["query"]["location"]
|
||||
readonly directory: Endpoint22_1Request["payload"]["directory"]
|
||||
readonly force: Endpoint22_1Request["payload"]["force"]
|
||||
}
|
||||
const Endpoint17_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_1Input) =>
|
||||
const Endpoint22_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint22_1Input) =>
|
||||
raw["projectCopy.remove"]({
|
||||
params: { projectID: input["projectID"] },
|
||||
query: { location: input["location"] },
|
||||
payload: { directory: input["directory"], force: input["force"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint17_2Request = Parameters<RawClient["server.projectCopy"]["projectCopy.refresh"]>[0]
|
||||
type Endpoint17_2Input = {
|
||||
readonly projectID: Endpoint17_2Request["params"]["projectID"]
|
||||
readonly location?: Endpoint17_2Request["query"]["location"]
|
||||
type Endpoint22_2Request = Parameters<RawClient["server.projectCopy"]["projectCopy.refresh"]>[0]
|
||||
type Endpoint22_2Input = {
|
||||
readonly projectID: Endpoint22_2Request["params"]["projectID"]
|
||||
readonly location?: Endpoint22_2Request["query"]["location"]
|
||||
}
|
||||
const Endpoint17_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_2Input) =>
|
||||
const Endpoint22_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint22_2Input) =>
|
||||
raw["projectCopy.refresh"]({
|
||||
params: { projectID: input["projectID"] },
|
||||
query: { location: input["location"] },
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup17 = (raw: RawClient["server.projectCopy"]) => ({
|
||||
create: Endpoint17_0(raw),
|
||||
remove: Endpoint17_1(raw),
|
||||
refresh: Endpoint17_2(raw),
|
||||
const adaptGroup22 = (raw: RawClient["server.projectCopy"]) => ({
|
||||
create: Endpoint22_0(raw),
|
||||
remove: Endpoint22_1(raw),
|
||||
refresh: Endpoint22_2(raw),
|
||||
})
|
||||
|
||||
const adaptClient = (raw: RawClient) => ({
|
||||
health: adaptGroup0(raw["server.health"]),
|
||||
location: adaptGroup1(raw["server.location"]),
|
||||
agents: adaptGroup2(raw["server.agent"]),
|
||||
sessions: adaptGroup3(raw["server.session"]),
|
||||
messages: adaptGroup4(raw["server.message"]),
|
||||
models: adaptGroup5(raw["server.model"]),
|
||||
providers: adaptGroup6(raw["server.provider"]),
|
||||
integrations: adaptGroup7(raw["server.integration"]),
|
||||
credentials: adaptGroup8(raw["server.credential"]),
|
||||
permissions: adaptGroup9(raw["server.permission"]),
|
||||
files: adaptGroup10(raw["server.fs"]),
|
||||
commands: adaptGroup11(raw["server.command"]),
|
||||
skills: adaptGroup12(raw["server.skill"]),
|
||||
events: adaptGroup13(raw["server.event"]),
|
||||
ptys: adaptGroup14(raw["server.pty"]),
|
||||
questions: adaptGroup15(raw["server.question"]),
|
||||
references: adaptGroup16(raw["server.reference"]),
|
||||
projectCopies: adaptGroup17(raw["server.projectCopy"]),
|
||||
agent: adaptGroup2(raw["server.agent"]),
|
||||
plugin: adaptGroup3(raw["server.plugin"]),
|
||||
session: adaptGroup4(raw["server.session"]),
|
||||
message: adaptGroup5(raw["server.message"]),
|
||||
model: adaptGroup6(raw["server.model"]),
|
||||
generate: adaptGroup7(raw["server.generate"]),
|
||||
provider: adaptGroup8(raw["server.provider"]),
|
||||
integration: adaptGroup9(raw["server.integration"]),
|
||||
"server.mcp": adaptGroup10(raw["server.mcp"]),
|
||||
credential: adaptGroup11(raw["server.credential"]),
|
||||
project: adaptGroup12(raw["server.project"]),
|
||||
permission: adaptGroup13(raw["server.permission"]),
|
||||
file: adaptGroup14(raw["server.fs"]),
|
||||
command: adaptGroup15(raw["server.command"]),
|
||||
skill: adaptGroup16(raw["server.skill"]),
|
||||
event: adaptGroup17(raw["server.event"]),
|
||||
pty: adaptGroup18(raw["server.pty"]),
|
||||
shell: adaptGroup19(raw["server.shell"]),
|
||||
question: adaptGroup20(raw["server.question"]),
|
||||
reference: adaptGroup21(raw["server.reference"]),
|
||||
projectCopy: adaptGroup22(raw["server.projectCopy"]),
|
||||
})
|
||||
|
||||
export const make = (options?: { readonly baseUrl?: URL | string }) =>
|
||||
|
||||
@@ -2,118 +2,157 @@ import type {
|
||||
HealthGetOutput,
|
||||
LocationGetInput,
|
||||
LocationGetOutput,
|
||||
AgentsListInput,
|
||||
AgentsListOutput,
|
||||
SessionsListInput,
|
||||
SessionsListOutput,
|
||||
SessionsCreateInput,
|
||||
SessionsCreateOutput,
|
||||
SessionsActiveOutput,
|
||||
SessionsGetInput,
|
||||
SessionsGetOutput,
|
||||
SessionsSwitchAgentInput,
|
||||
SessionsSwitchAgentOutput,
|
||||
SessionsSwitchModelInput,
|
||||
SessionsSwitchModelOutput,
|
||||
SessionsRenameInput,
|
||||
SessionsRenameOutput,
|
||||
SessionsPromptInput,
|
||||
SessionsPromptOutput,
|
||||
SessionsCompactInput,
|
||||
SessionsCompactOutput,
|
||||
SessionsWaitInput,
|
||||
SessionsWaitOutput,
|
||||
SessionsStageInput,
|
||||
SessionsStageOutput,
|
||||
SessionsClearInput,
|
||||
SessionsClearOutput,
|
||||
SessionsCommitInput,
|
||||
SessionsCommitOutput,
|
||||
SessionsContextInput,
|
||||
SessionsContextOutput,
|
||||
SessionsHistoryInput,
|
||||
SessionsHistoryOutput,
|
||||
SessionsEventsInput,
|
||||
SessionsEventsOutput,
|
||||
SessionsInterruptInput,
|
||||
SessionsInterruptOutput,
|
||||
SessionsMessageInput,
|
||||
SessionsMessageOutput,
|
||||
MessagesListInput,
|
||||
MessagesListOutput,
|
||||
ModelsListInput,
|
||||
ModelsListOutput,
|
||||
ProvidersListInput,
|
||||
ProvidersListOutput,
|
||||
ProvidersGetInput,
|
||||
ProvidersGetOutput,
|
||||
IntegrationsListInput,
|
||||
IntegrationsListOutput,
|
||||
IntegrationsGetInput,
|
||||
IntegrationsGetOutput,
|
||||
IntegrationsConnectKeyInput,
|
||||
IntegrationsConnectKeyOutput,
|
||||
IntegrationsConnectOauthInput,
|
||||
IntegrationsConnectOauthOutput,
|
||||
IntegrationsAttemptStatusInput,
|
||||
IntegrationsAttemptStatusOutput,
|
||||
IntegrationsAttemptCompleteInput,
|
||||
IntegrationsAttemptCompleteOutput,
|
||||
IntegrationsAttemptCancelInput,
|
||||
IntegrationsAttemptCancelOutput,
|
||||
CredentialsUpdateInput,
|
||||
CredentialsUpdateOutput,
|
||||
CredentialsRemoveInput,
|
||||
CredentialsRemoveOutput,
|
||||
PermissionsListRequestsInput,
|
||||
PermissionsListRequestsOutput,
|
||||
PermissionsListSavedInput,
|
||||
PermissionsListSavedOutput,
|
||||
PermissionsRemoveSavedInput,
|
||||
PermissionsRemoveSavedOutput,
|
||||
PermissionsCreateInput,
|
||||
PermissionsCreateOutput,
|
||||
PermissionsListInput,
|
||||
PermissionsListOutput,
|
||||
PermissionsGetInput,
|
||||
PermissionsGetOutput,
|
||||
PermissionsReplyInput,
|
||||
PermissionsReplyOutput,
|
||||
FilesListInput,
|
||||
FilesListOutput,
|
||||
FilesFindInput,
|
||||
FilesFindOutput,
|
||||
CommandsListInput,
|
||||
CommandsListOutput,
|
||||
SkillsListInput,
|
||||
SkillsListOutput,
|
||||
EventsSubscribeOutput,
|
||||
PtysListInput,
|
||||
PtysListOutput,
|
||||
PtysCreateInput,
|
||||
PtysCreateOutput,
|
||||
PtysGetInput,
|
||||
PtysGetOutput,
|
||||
PtysUpdateInput,
|
||||
PtysUpdateOutput,
|
||||
PtysRemoveInput,
|
||||
PtysRemoveOutput,
|
||||
QuestionsListRequestsInput,
|
||||
QuestionsListRequestsOutput,
|
||||
QuestionsListInput,
|
||||
QuestionsListOutput,
|
||||
QuestionsReplyInput,
|
||||
QuestionsReplyOutput,
|
||||
QuestionsRejectInput,
|
||||
QuestionsRejectOutput,
|
||||
ReferencesListInput,
|
||||
ReferencesListOutput,
|
||||
ProjectCopiesCreateInput,
|
||||
ProjectCopiesCreateOutput,
|
||||
ProjectCopiesRemoveInput,
|
||||
ProjectCopiesRemoveOutput,
|
||||
ProjectCopiesRefreshInput,
|
||||
ProjectCopiesRefreshOutput,
|
||||
AgentListInput,
|
||||
AgentListOutput,
|
||||
PluginListInput,
|
||||
PluginListOutput,
|
||||
SessionListInput,
|
||||
SessionListOutput,
|
||||
SessionCreateInput,
|
||||
SessionCreateOutput,
|
||||
SessionActiveOutput,
|
||||
SessionGetInput,
|
||||
SessionGetOutput,
|
||||
SessionForkInput,
|
||||
SessionForkOutput,
|
||||
SessionSwitchAgentInput,
|
||||
SessionSwitchAgentOutput,
|
||||
SessionSwitchModelInput,
|
||||
SessionSwitchModelOutput,
|
||||
SessionRenameInput,
|
||||
SessionRenameOutput,
|
||||
SessionPromptInput,
|
||||
SessionPromptOutput,
|
||||
SessionCommandInput,
|
||||
SessionCommandOutput,
|
||||
SessionSkillInput,
|
||||
SessionSkillOutput,
|
||||
SessionSyntheticInput,
|
||||
SessionSyntheticOutput,
|
||||
SessionCompactInput,
|
||||
SessionCompactOutput,
|
||||
SessionWaitInput,
|
||||
SessionWaitOutput,
|
||||
SessionRevertStageInput,
|
||||
SessionRevertStageOutput,
|
||||
SessionRevertClearInput,
|
||||
SessionRevertClearOutput,
|
||||
SessionRevertCommitInput,
|
||||
SessionRevertCommitOutput,
|
||||
SessionContextInput,
|
||||
SessionContextOutput,
|
||||
SessionListContextEntriesInput,
|
||||
SessionListContextEntriesOutput,
|
||||
SessionPutContextEntryInput,
|
||||
SessionPutContextEntryOutput,
|
||||
SessionRemoveContextEntryInput,
|
||||
SessionRemoveContextEntryOutput,
|
||||
SessionLogInput,
|
||||
SessionLogOutput,
|
||||
SessionInterruptInput,
|
||||
SessionInterruptOutput,
|
||||
SessionBackgroundInput,
|
||||
SessionBackgroundOutput,
|
||||
SessionMessageInput,
|
||||
SessionMessageOutput,
|
||||
MessageListInput,
|
||||
MessageListOutput,
|
||||
ModelListInput,
|
||||
ModelListOutput,
|
||||
ModelDefaultInput,
|
||||
ModelDefaultOutput,
|
||||
GenerateTextInput,
|
||||
GenerateTextOutput,
|
||||
ProviderListInput,
|
||||
ProviderListOutput,
|
||||
ProviderGetInput,
|
||||
ProviderGetOutput,
|
||||
IntegrationListInput,
|
||||
IntegrationListOutput,
|
||||
IntegrationGetInput,
|
||||
IntegrationGetOutput,
|
||||
IntegrationConnectKeyInput,
|
||||
IntegrationConnectKeyOutput,
|
||||
IntegrationConnectOauthInput,
|
||||
IntegrationConnectOauthOutput,
|
||||
IntegrationAttemptStatusInput,
|
||||
IntegrationAttemptStatusOutput,
|
||||
IntegrationAttemptCompleteInput,
|
||||
IntegrationAttemptCompleteOutput,
|
||||
IntegrationAttemptCancelInput,
|
||||
IntegrationAttemptCancelOutput,
|
||||
ServerMcpListInput,
|
||||
ServerMcpListOutput,
|
||||
CredentialUpdateInput,
|
||||
CredentialUpdateOutput,
|
||||
CredentialRemoveInput,
|
||||
CredentialRemoveOutput,
|
||||
ProjectCurrentInput,
|
||||
ProjectCurrentOutput,
|
||||
ProjectDirectoriesInput,
|
||||
ProjectDirectoriesOutput,
|
||||
PermissionListRequestsInput,
|
||||
PermissionListRequestsOutput,
|
||||
PermissionListSavedInput,
|
||||
PermissionListSavedOutput,
|
||||
PermissionRemoveSavedInput,
|
||||
PermissionRemoveSavedOutput,
|
||||
PermissionCreateInput,
|
||||
PermissionCreateOutput,
|
||||
PermissionListInput,
|
||||
PermissionListOutput,
|
||||
PermissionGetInput,
|
||||
PermissionGetOutput,
|
||||
PermissionReplyInput,
|
||||
PermissionReplyOutput,
|
||||
FileReadInput,
|
||||
FileReadOutput,
|
||||
FileListInput,
|
||||
FileListOutput,
|
||||
FileFindInput,
|
||||
FileFindOutput,
|
||||
CommandListInput,
|
||||
CommandListOutput,
|
||||
SkillListInput,
|
||||
SkillListOutput,
|
||||
EventSubscribeOutput,
|
||||
EventChangesOutput,
|
||||
PtyListInput,
|
||||
PtyListOutput,
|
||||
PtyCreateInput,
|
||||
PtyCreateOutput,
|
||||
PtyGetInput,
|
||||
PtyGetOutput,
|
||||
PtyUpdateInput,
|
||||
PtyUpdateOutput,
|
||||
PtyRemoveInput,
|
||||
PtyRemoveOutput,
|
||||
ShellListInput,
|
||||
ShellListOutput,
|
||||
ShellCreateInput,
|
||||
ShellCreateOutput,
|
||||
ShellGetInput,
|
||||
ShellGetOutput,
|
||||
ShellOutputInput,
|
||||
ShellOutputOutput,
|
||||
ShellRemoveInput,
|
||||
ShellRemoveOutput,
|
||||
QuestionListRequestsInput,
|
||||
QuestionListRequestsOutput,
|
||||
QuestionListInput,
|
||||
QuestionListOutput,
|
||||
QuestionReplyInput,
|
||||
QuestionReplyOutput,
|
||||
QuestionRejectInput,
|
||||
QuestionRejectOutput,
|
||||
ReferenceListInput,
|
||||
ReferenceListOutput,
|
||||
ProjectCopyCreateInput,
|
||||
ProjectCopyCreateOutput,
|
||||
ProjectCopyRemoveInput,
|
||||
ProjectCopyRemoveOutput,
|
||||
ProjectCopyRefreshInput,
|
||||
ProjectCopyRefreshOutput,
|
||||
} from "./types"
|
||||
import { ClientError } from "./client-error"
|
||||
|
||||
@@ -137,6 +176,7 @@ interface RequestDescriptor {
|
||||
readonly successStatus: number
|
||||
readonly declaredStatuses: ReadonlyArray<number>
|
||||
readonly empty: boolean
|
||||
readonly binary?: true
|
||||
}
|
||||
|
||||
export function make(options: ClientOptions) {
|
||||
@@ -182,6 +222,7 @@ export function make(options: ClientOptions) {
|
||||
const request = async <A>(descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise<A> => {
|
||||
const response = await execute(descriptor, requestOptions)
|
||||
if (response.status !== descriptor.successStatus) return responseError(response, descriptor)
|
||||
if (descriptor.binary) return new Uint8Array(await response.arrayBuffer()) as A
|
||||
if (descriptor.empty) {
|
||||
try {
|
||||
await response.body?.cancel()
|
||||
@@ -270,9 +311,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
agents: {
|
||||
list: (input?: AgentsListInput, requestOptions?: RequestOptions) =>
|
||||
request<AgentsListOutput>(
|
||||
agent: {
|
||||
list: (input?: AgentListInput, requestOptions?: RequestOptions) =>
|
||||
request<AgentListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/agent`,
|
||||
@@ -284,9 +325,23 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
sessions: {
|
||||
list: (input?: SessionsListInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsListOutput>(
|
||||
plugin: {
|
||||
list: (input?: PluginListInput, requestOptions?: RequestOptions) =>
|
||||
request<PluginListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/plugin`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
session: {
|
||||
list: (input?: SessionListInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session`,
|
||||
@@ -306,8 +361,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
create: (input?: SessionsCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsCreateOutput }>(
|
||||
create: (input?: SessionCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionCreateOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session`,
|
||||
@@ -324,7 +379,7 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
active: (requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsActiveOutput }>(
|
||||
request<SessionActiveOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/active`,
|
||||
@@ -333,9 +388,9 @@ export function make(options: ClientOptions) {
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
get: (input: SessionsGetInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsGetOutput }>(
|
||||
),
|
||||
get: (input: SessionGetInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionGetOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}`,
|
||||
@@ -345,8 +400,20 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
switchAgent: (input: SessionsSwitchAgentInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsSwitchAgentOutput>(
|
||||
fork: (input: SessionForkInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionForkOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`,
|
||||
body: { messageID: input["messageID"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
switchAgent: (input: SessionSwitchAgentInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionSwitchAgentOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/agent`,
|
||||
@@ -357,8 +424,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
switchModel: (input: SessionsSwitchModelInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsSwitchModelOutput>(
|
||||
switchModel: (input: SessionSwitchModelInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionSwitchModelOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/model`,
|
||||
@@ -369,8 +436,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
rename: (input: SessionsRenameInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsRenameOutput>(
|
||||
rename: (input: SessionRenameInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionRenameOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/rename`,
|
||||
@@ -381,8 +448,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
prompt: (input: SessionsPromptInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsPromptOutput }>(
|
||||
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionPromptOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`,
|
||||
@@ -393,19 +460,65 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
compact: (input: SessionsCompactInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsCompactOutput>(
|
||||
command: (input: SessionCommandInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionCommandOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/command`,
|
||||
body: {
|
||||
id: input["id"],
|
||||
command: input["command"],
|
||||
arguments: input["arguments"],
|
||||
agent: input["agent"],
|
||||
model: input["model"],
|
||||
files: input["files"],
|
||||
agents: input["agents"],
|
||||
delivery: input["delivery"],
|
||||
resume: input["resume"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [409, 404, 500, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
skill: (input: SessionSkillInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionSkillOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/skill`,
|
||||
body: { id: input["id"], skill: input["skill"], resume: input["resume"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 503, 400, 401],
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
wait: (input: SessionsWaitInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsWaitOutput>(
|
||||
synthetic: (input: SessionSyntheticInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionSyntheticOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`,
|
||||
body: { text: input["text"], description: input["description"], metadata: input["metadata"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
compact: (input: SessionCompactInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionCompactOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 409, 503, 500, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
wait: (input: SessionWaitInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionWaitOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/wait`,
|
||||
@@ -415,8 +528,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
stage: (input: SessionsStageInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsStageOutput }>(
|
||||
revertStage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionRevertStageOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
|
||||
@@ -427,8 +540,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
clear: (input: SessionsClearInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsClearOutput>(
|
||||
revertClear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionRevertClearOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`,
|
||||
@@ -438,8 +551,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
commit: (input: SessionsCommitInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsCommitOutput>(
|
||||
revertCommit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionRevertCommitOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
|
||||
@@ -449,8 +562,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
context: (input: SessionsContextInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsContextOutput }>(
|
||||
context: (input: SessionContextInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionContextOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/context`,
|
||||
@@ -460,32 +573,54 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
history: (input: SessionsHistoryInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsHistoryOutput>(
|
||||
listContextEntries: (input: SessionListContextEntriesInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionListContextEntriesOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/history`,
|
||||
query: { limit: input["limit"], after: input["after"] },
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry`,
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
putContextEntry: (input: SessionPutContextEntryInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionPutContextEntryOutput>(
|
||||
{
|
||||
method: "PUT",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry/${encodeURIComponent(input.key)}`,
|
||||
body: { value: input["value"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
removeContextEntry: (input: SessionRemoveContextEntryInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionRemoveContextEntryOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry/${encodeURIComponent(input.key)}`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
log: (input: SessionLogInput, requestOptions?: RequestOptions): AsyncIterable<SessionLogOutput> =>
|
||||
sse<SessionLogOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/log`,
|
||||
query: { after: input["after"], follow: input["follow"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
events: (input: SessionsEventsInput, requestOptions?: RequestOptions): AsyncIterable<SessionsEventsOutput> =>
|
||||
sse<SessionsEventsOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/event`,
|
||||
query: { after: input["after"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
interrupt: (input: SessionsInterruptInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionsInterruptOutput>(
|
||||
interrupt: (input: SessionInterruptInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionInterruptOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`,
|
||||
@@ -495,8 +630,19 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
message: (input: SessionsMessageInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionsMessageOutput }>(
|
||||
background: (input: SessionBackgroundInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionBackgroundOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/background`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 400, 401],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
message: (input: SessionMessageInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionMessageOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/message/${encodeURIComponent(input.messageID)}`,
|
||||
@@ -507,9 +653,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
},
|
||||
messages: {
|
||||
list: (input: MessagesListInput, requestOptions?: RequestOptions) =>
|
||||
request<MessagesListOutput>(
|
||||
message: {
|
||||
list: (input: MessageListInput, requestOptions?: RequestOptions) =>
|
||||
request<MessageListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/message`,
|
||||
@@ -521,9 +667,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
models: {
|
||||
list: (input?: ModelsListInput, requestOptions?: RequestOptions) =>
|
||||
request<ModelsListOutput>(
|
||||
model: {
|
||||
list: (input?: ModelListInput, requestOptions?: RequestOptions) =>
|
||||
request<ModelListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/model`,
|
||||
@@ -534,10 +680,37 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
default: (input?: ModelDefaultInput, requestOptions?: RequestOptions) =>
|
||||
request<ModelDefaultOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/model/default`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [503, 401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
providers: {
|
||||
list: (input?: ProvidersListInput, requestOptions?: RequestOptions) =>
|
||||
request<ProvidersListOutput>(
|
||||
generate: {
|
||||
text: (input: GenerateTextInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: GenerateTextOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/generate`,
|
||||
query: { location: input["location"] },
|
||||
body: { prompt: input["prompt"], model: input["model"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 503, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
},
|
||||
provider: {
|
||||
list: (input?: ProviderListInput, requestOptions?: RequestOptions) =>
|
||||
request<ProviderListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/provider`,
|
||||
@@ -548,8 +721,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
get: (input: ProvidersGetInput, requestOptions?: RequestOptions) =>
|
||||
request<ProvidersGetOutput>(
|
||||
get: (input: ProviderGetInput, requestOptions?: RequestOptions) =>
|
||||
request<ProviderGetOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/provider/${encodeURIComponent(input.providerID)}`,
|
||||
@@ -561,9 +734,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
integrations: {
|
||||
list: (input?: IntegrationsListInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsListOutput>(
|
||||
integration: {
|
||||
list: (input?: IntegrationListInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/integration`,
|
||||
@@ -574,8 +747,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
get: (input: IntegrationsGetInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsGetOutput>(
|
||||
get: (input: IntegrationGetInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationGetOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
|
||||
@@ -586,8 +759,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
connectKey: (input: IntegrationsConnectKeyInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsConnectKeyOutput>(
|
||||
connectKey: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationConnectKeyOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`,
|
||||
@@ -599,8 +772,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
connectOauth: (input: IntegrationsConnectOauthInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsConnectOauthOutput>(
|
||||
connectOauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationConnectOauthOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
|
||||
@@ -612,8 +785,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
attemptStatus: (input: IntegrationsAttemptStatusInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsAttemptStatusOutput>(
|
||||
attemptStatus: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationAttemptStatusOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
|
||||
@@ -624,8 +797,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
attemptComplete: (input: IntegrationsAttemptCompleteInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsAttemptCompleteOutput>(
|
||||
attemptComplete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationAttemptCompleteOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`,
|
||||
@@ -637,8 +810,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
attemptCancel: (input: IntegrationsAttemptCancelInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationsAttemptCancelOutput>(
|
||||
attemptCancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) =>
|
||||
request<IntegrationAttemptCancelOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
|
||||
@@ -650,9 +823,23 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
credentials: {
|
||||
update: (input: CredentialsUpdateInput, requestOptions?: RequestOptions) =>
|
||||
request<CredentialsUpdateOutput>(
|
||||
"server.mcp": {
|
||||
list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) =>
|
||||
request<ServerMcpListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/mcp`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
credential: {
|
||||
update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) =>
|
||||
request<CredentialUpdateOutput>(
|
||||
{
|
||||
method: "PATCH",
|
||||
path: `/api/credential/${encodeURIComponent(input.credentialID)}`,
|
||||
@@ -664,8 +851,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
remove: (input: CredentialsRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<CredentialsRemoveOutput>(
|
||||
remove: (input: CredentialRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<CredentialRemoveOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/credential/${encodeURIComponent(input.credentialID)}`,
|
||||
@@ -677,9 +864,35 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
permissions: {
|
||||
listRequests: (input?: PermissionsListRequestsInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionsListRequestsOutput>(
|
||||
project: {
|
||||
current: (input?: ProjectCurrentInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCurrentOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/project/current`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
directories: (input: ProjectDirectoriesInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectDirectoriesOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/project/${encodeURIComponent(input.projectID)}/directories`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
permission: {
|
||||
listRequests: (input?: PermissionListRequestsInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionListRequestsOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/permission/request`,
|
||||
@@ -690,8 +903,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
listSaved: (input?: PermissionsListSavedInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionsListSavedOutput }>(
|
||||
listSaved: (input?: PermissionListSavedInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionListSavedOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/permission/saved`,
|
||||
@@ -702,8 +915,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
removeSaved: (input: PermissionsRemoveSavedInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionsRemoveSavedOutput>(
|
||||
removeSaved: (input: PermissionRemoveSavedInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionRemoveSavedOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
|
||||
@@ -713,8 +926,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
create: (input: PermissionsCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionsCreateOutput }>(
|
||||
create: (input: PermissionCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionCreateOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
|
||||
@@ -733,8 +946,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
list: (input: PermissionsListInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionsListOutput }>(
|
||||
list: (input: PermissionListInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionListOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
|
||||
@@ -744,8 +957,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
get: (input: PermissionsGetInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionsGetOutput }>(
|
||||
get: (input: PermissionGetInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: PermissionGetOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`,
|
||||
@@ -755,8 +968,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
reply: (input: PermissionsReplyInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionsReplyOutput>(
|
||||
reply: (input: PermissionReplyInput, requestOptions?: RequestOptions) =>
|
||||
request<PermissionReplyOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}/reply`,
|
||||
@@ -768,9 +981,22 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
files: {
|
||||
list: (input?: FilesListInput, requestOptions?: RequestOptions) =>
|
||||
request<FilesListOutput>(
|
||||
file: {
|
||||
read: (input: FileReadInput, requestOptions?: RequestOptions) =>
|
||||
request<FileReadOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/fs/read/${encodePath(input.path)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
binary: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
list: (input?: FileListInput, requestOptions?: RequestOptions) =>
|
||||
request<FileListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/fs/list`,
|
||||
@@ -781,8 +1007,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
find: (input: FilesFindInput, requestOptions?: RequestOptions) =>
|
||||
request<FilesFindOutput>(
|
||||
find: (input: FileFindInput, requestOptions?: RequestOptions) =>
|
||||
request<FileFindOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/fs/find`,
|
||||
@@ -794,9 +1020,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
commands: {
|
||||
list: (input?: CommandsListInput, requestOptions?: RequestOptions) =>
|
||||
request<CommandsListOutput>(
|
||||
command: {
|
||||
list: (input?: CommandListInput, requestOptions?: RequestOptions) =>
|
||||
request<CommandListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/command`,
|
||||
@@ -808,9 +1034,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
skills: {
|
||||
list: (input?: SkillsListInput, requestOptions?: RequestOptions) =>
|
||||
request<SkillsListOutput>(
|
||||
skill: {
|
||||
list: (input?: SkillListInput, requestOptions?: RequestOptions) =>
|
||||
request<SkillListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/skill`,
|
||||
@@ -822,16 +1048,21 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
events: {
|
||||
subscribe: (requestOptions?: RequestOptions): AsyncIterable<EventsSubscribeOutput> =>
|
||||
sse<EventsSubscribeOutput>(
|
||||
event: {
|
||||
subscribe: (requestOptions?: RequestOptions): AsyncIterable<EventSubscribeOutput> =>
|
||||
sse<EventSubscribeOutput>(
|
||||
{ method: "GET", path: `/api/event`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
|
||||
requestOptions,
|
||||
),
|
||||
changes: (requestOptions?: RequestOptions): AsyncIterable<EventChangesOutput> =>
|
||||
sse<EventChangesOutput>(
|
||||
{ method: "GET", path: `/api/event/changes`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
ptys: {
|
||||
list: (input?: PtysListInput, requestOptions?: RequestOptions) =>
|
||||
request<PtysListOutput>(
|
||||
pty: {
|
||||
list: (input?: PtyListInput, requestOptions?: RequestOptions) =>
|
||||
request<PtyListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/pty`,
|
||||
@@ -842,8 +1073,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
create: (input?: PtysCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<PtysCreateOutput>(
|
||||
create: (input?: PtyCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<PtyCreateOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/pty`,
|
||||
@@ -861,8 +1092,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
get: (input: PtysGetInput, requestOptions?: RequestOptions) =>
|
||||
request<PtysGetOutput>(
|
||||
get: (input: PtyGetInput, requestOptions?: RequestOptions) =>
|
||||
request<PtyGetOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
@@ -873,8 +1104,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
update: (input: PtysUpdateInput, requestOptions?: RequestOptions) =>
|
||||
request<PtysUpdateOutput>(
|
||||
update: (input: PtyUpdateInput, requestOptions?: RequestOptions) =>
|
||||
request<PtyUpdateOutput>(
|
||||
{
|
||||
method: "PUT",
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
@@ -886,8 +1117,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
remove: (input: PtysRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<PtysRemoveOutput>(
|
||||
remove: (input: PtyRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<PtyRemoveOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
|
||||
@@ -899,9 +1130,77 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
questions: {
|
||||
listRequests: (input?: QuestionsListRequestsInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionsListRequestsOutput>(
|
||||
shell: {
|
||||
list: (input?: ShellListInput, requestOptions?: RequestOptions) =>
|
||||
request<ShellListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/shell`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
create: (input: ShellCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<ShellCreateOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/shell`,
|
||||
query: { location: input["location"] },
|
||||
body: {
|
||||
command: input["command"],
|
||||
cwd: input["cwd"],
|
||||
timeout: input["timeout"],
|
||||
metadata: input["metadata"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
get: (input: ShellGetInput, requestOptions?: RequestOptions) =>
|
||||
request<ShellGetOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
output: (input: ShellOutputInput, requestOptions?: RequestOptions) =>
|
||||
request<ShellOutputOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}/output`,
|
||||
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [404, 401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
remove: (input: ShellRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<ShellRemoveOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/api/shell/${encodeURIComponent(input.id)}`,
|
||||
query: { location: input["location"] },
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 401, 400],
|
||||
empty: true,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
question: {
|
||||
listRequests: (input?: QuestionListRequestsInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionListRequestsOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/question/request`,
|
||||
@@ -912,8 +1211,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
list: (input: QuestionsListInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: QuestionsListOutput }>(
|
||||
list: (input: QuestionListInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: QuestionListOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/question`,
|
||||
@@ -923,8 +1222,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
reply: (input: QuestionsReplyInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionsReplyOutput>(
|
||||
reply: (input: QuestionReplyInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionReplyOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reply`,
|
||||
@@ -935,8 +1234,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
reject: (input: QuestionsRejectInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionsRejectOutput>(
|
||||
reject: (input: QuestionRejectInput, requestOptions?: RequestOptions) =>
|
||||
request<QuestionRejectOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reject`,
|
||||
@@ -947,9 +1246,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
references: {
|
||||
list: (input?: ReferencesListInput, requestOptions?: RequestOptions) =>
|
||||
request<ReferencesListOutput>(
|
||||
reference: {
|
||||
list: (input?: ReferenceListInput, requestOptions?: RequestOptions) =>
|
||||
request<ReferenceListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/reference`,
|
||||
@@ -961,9 +1260,9 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
projectCopies: {
|
||||
create: (input: ProjectCopiesCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopiesCreateOutput>(
|
||||
projectCopy: {
|
||||
create: (input: ProjectCopyCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopyCreateOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy`,
|
||||
@@ -975,8 +1274,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
remove: (input: ProjectCopiesRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopiesRemoveOutput>(
|
||||
remove: (input: ProjectCopyRemoveInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopyRemoveOutput>(
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy`,
|
||||
@@ -988,8 +1287,8 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
refresh: (input: ProjectCopiesRefreshInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopiesRefreshOutput>(
|
||||
refresh: (input: ProjectCopyRefreshInput, requestOptions?: RequestOptions) =>
|
||||
request<ProjectCopyRefreshOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy/refresh`,
|
||||
@@ -1004,6 +1303,10 @@ export function make(options: ClientOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
function encodePath(value: string): string {
|
||||
return value.split("/").map(encodeURIComponent).join("/")
|
||||
}
|
||||
|
||||
function appendQuery(params: URLSearchParams, key: string, value: unknown): void {
|
||||
if (value === undefined || value === null) return
|
||||
if (Array.isArray(value)) {
|
||||
|
||||
+3077
-1482
@@ -33,22 +33,6 @@ export type SessionNotFoundError = {
|
||||
export const isSessionNotFoundError = (value: unknown): value is SessionNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionNotFoundError"
|
||||
|
||||
export type ConflictError = {
|
||||
readonly _tag: "ConflictError"
|
||||
readonly message: string
|
||||
readonly resource?: string | undefined
|
||||
}
|
||||
export const isConflictError = (value: unknown): value is ConflictError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ConflictError"
|
||||
|
||||
export type ServiceUnavailableError = {
|
||||
readonly _tag: "ServiceUnavailableError"
|
||||
readonly message: string
|
||||
readonly service?: string | undefined
|
||||
}
|
||||
export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError"
|
||||
|
||||
export type MessageNotFoundError = {
|
||||
readonly _tag: "MessageNotFoundError"
|
||||
readonly sessionID: string
|
||||
@@ -58,6 +42,38 @@ export type MessageNotFoundError = {
|
||||
export const isMessageNotFoundError = (value: unknown): value is MessageNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "MessageNotFoundError"
|
||||
|
||||
export type ConflictError = {
|
||||
readonly _tag: "ConflictError"
|
||||
readonly message: string
|
||||
readonly resource?: string | undefined
|
||||
}
|
||||
export const isConflictError = (value: unknown): value is ConflictError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ConflictError"
|
||||
|
||||
export type CommandNotFoundError = {
|
||||
readonly _tag: "CommandNotFoundError"
|
||||
readonly command: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isCommandNotFoundError = (value: unknown): value is CommandNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "CommandNotFoundError"
|
||||
|
||||
export type CommandEvaluationError = {
|
||||
readonly _tag: "CommandEvaluationError"
|
||||
readonly command: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isCommandEvaluationError = (value: unknown): value is CommandEvaluationError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "CommandEvaluationError"
|
||||
|
||||
export type SkillNotFoundError = {
|
||||
readonly _tag: "SkillNotFoundError"
|
||||
readonly skill: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isSkillNotFoundError = (value: unknown): value is SkillNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SkillNotFoundError"
|
||||
|
||||
export type SessionBusyError = {
|
||||
readonly _tag: "SessionBusyError"
|
||||
readonly sessionID: string
|
||||
@@ -66,6 +82,14 @@ export type SessionBusyError = {
|
||||
export const isSessionBusyError = (value: unknown): value is SessionBusyError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError"
|
||||
|
||||
export type ServiceUnavailableError = {
|
||||
readonly _tag: "ServiceUnavailableError"
|
||||
readonly message: string
|
||||
readonly service?: string | undefined
|
||||
}
|
||||
export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError"
|
||||
|
||||
export type UnknownError = {
|
||||
readonly _tag: "UnknownError"
|
||||
readonly message: string
|
||||
@@ -94,6 +118,10 @@ export type PtyNotFoundError = { readonly _tag: "PtyNotFoundError"; readonly pty
|
||||
export const isPtyNotFoundError = (value: unknown): value is PtyNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PtyNotFoundError"
|
||||
|
||||
export type ShellNotFoundError = { readonly _tag: "ShellNotFoundError"; readonly id: string; readonly message: string }
|
||||
export const isShellNotFoundError = (value: unknown): value is ShellNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ShellNotFoundError"
|
||||
|
||||
export type QuestionNotFoundError = {
|
||||
readonly _tag: "QuestionNotFoundError"
|
||||
readonly requestID: string
|
||||
@@ -123,13 +151,13 @@ export type LocationGetOutput = {
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
|
||||
export type AgentsListInput = {
|
||||
export type AgentListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type AgentsListOutput = {
|
||||
export type AgentListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -139,6 +167,7 @@ export type AgentsListOutput = {
|
||||
readonly id: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly request: {
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
@@ -156,7 +185,22 @@ export type AgentsListOutput = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type SessionsListInput = {
|
||||
export type PluginListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PluginListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly id: string }>
|
||||
}
|
||||
|
||||
export type SessionListInput = {
|
||||
readonly workspace?: {
|
||||
readonly workspace?: string | undefined
|
||||
readonly limit?: number | undefined
|
||||
@@ -239,7 +283,7 @@ export type SessionsListInput = {
|
||||
}["cursor"]
|
||||
}
|
||||
|
||||
export type SessionsListOutput = {
|
||||
export type SessionListOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly parentID?: string
|
||||
@@ -271,10 +315,11 @@ export type SessionsListOutput = {
|
||||
}>
|
||||
}
|
||||
}>
|
||||
readonly watermarks: { readonly [x: string]: number }
|
||||
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
|
||||
}
|
||||
|
||||
export type SessionsCreateInput = {
|
||||
export type SessionCreateInput = {
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly agent?: string | null
|
||||
@@ -301,7 +346,7 @@ export type SessionsCreateInput = {
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type SessionsCreateOutput = {
|
||||
export type SessionCreateOutput = {
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly parentID?: string
|
||||
@@ -335,11 +380,14 @@ export type SessionsCreateOutput = {
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionsActiveOutput = { readonly data: { readonly [x: string]: { readonly type: "running" } } }["data"]
|
||||
export type SessionActiveOutput = {
|
||||
readonly data: { readonly [x: string]: { readonly type: "running" } }
|
||||
readonly watermarks: { readonly [x: string]: number }
|
||||
}
|
||||
|
||||
export type SessionsGetInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionGetInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionsGetOutput = {
|
||||
export type SessionGetOutput = {
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly parentID?: string
|
||||
@@ -373,30 +421,69 @@ export type SessionsGetOutput = {
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionsSwitchAgentInput = {
|
||||
export type SessionForkInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly messageID?: { readonly messageID?: string | undefined }["messageID"]
|
||||
}
|
||||
|
||||
export type SessionForkOutput = {
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly parentID?: string
|
||||
readonly projectID: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
|
||||
readonly title: string
|
||||
readonly location: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly subpath?: string
|
||||
readonly revert?: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
readonly snapshot?: string
|
||||
readonly diff?: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly path: string
|
||||
readonly status: "added" | "modified" | "deleted"
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly patch: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionSwitchAgentInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly agent: { readonly agent: string }["agent"]
|
||||
}
|
||||
|
||||
export type SessionsSwitchAgentOutput = void
|
||||
export type SessionSwitchAgentOutput = void
|
||||
|
||||
export type SessionsSwitchModelInput = {
|
||||
export type SessionSwitchModelInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly model: {
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}["model"]
|
||||
}
|
||||
|
||||
export type SessionsSwitchModelOutput = void
|
||||
export type SessionSwitchModelOutput = void
|
||||
|
||||
export type SessionsRenameInput = {
|
||||
export type SessionRenameInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly title: { readonly title: string }["title"]
|
||||
}
|
||||
|
||||
export type SessionsRenameOutput = void
|
||||
export type SessionRenameOutput = void
|
||||
|
||||
export type SessionsPromptInput = {
|
||||
export type SessionPromptInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
@@ -412,7 +499,6 @@ export type SessionsPromptInput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
@@ -431,7 +517,6 @@ export type SessionsPromptInput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
@@ -450,7 +535,6 @@ export type SessionsPromptInput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
@@ -469,14 +553,13 @@ export type SessionsPromptInput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["resume"]
|
||||
}
|
||||
|
||||
export type SessionsPromptOutput = {
|
||||
export type SessionPromptOutput = {
|
||||
readonly data: {
|
||||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
@@ -494,7 +577,6 @@ export type SessionsPromptOutput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
readonly timeCreated: number
|
||||
@@ -502,21 +584,263 @@ export type SessionsPromptOutput = {
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionsCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionCommandInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["id"]
|
||||
readonly command: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["command"]
|
||||
readonly arguments?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["arguments"]
|
||||
readonly agent?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["agent"]
|
||||
readonly model?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["model"]
|
||||
readonly files?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["files"]
|
||||
readonly agents?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["agents"]
|
||||
readonly delivery?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["delivery"]
|
||||
readonly resume?: {
|
||||
readonly id?: string | null
|
||||
readonly command: string
|
||||
readonly arguments?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly delivery?: "steer" | "queue" | null
|
||||
readonly resume?: boolean | null
|
||||
}["resume"]
|
||||
}
|
||||
|
||||
export type SessionsCompactOutput = void
|
||||
export type SessionCommandOutput = {
|
||||
readonly data: {
|
||||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
readonly timeCreated: number
|
||||
readonly promotedSeq?: number
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionsWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionSkillInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | undefined
|
||||
readonly skill: string
|
||||
readonly resume?: boolean | undefined
|
||||
}["id"]
|
||||
readonly skill: {
|
||||
readonly id?: string | undefined
|
||||
readonly skill: string
|
||||
readonly resume?: boolean | undefined
|
||||
}["skill"]
|
||||
readonly resume?: {
|
||||
readonly id?: string | undefined
|
||||
readonly skill: string
|
||||
readonly resume?: boolean | undefined
|
||||
}["resume"]
|
||||
}
|
||||
|
||||
export type SessionsWaitOutput = void
|
||||
export type SessionSkillOutput = void
|
||||
|
||||
export type SessionsStageInput = {
|
||||
export type SessionSyntheticInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly text: {
|
||||
readonly text: string
|
||||
readonly description?: string | null
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["text"]
|
||||
readonly description?: {
|
||||
readonly text: string
|
||||
readonly description?: string | null
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["description"]
|
||||
readonly metadata?: {
|
||||
readonly text: string
|
||||
readonly description?: string | null
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["metadata"]
|
||||
}
|
||||
|
||||
export type SessionSyntheticOutput = void
|
||||
|
||||
export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionCompactOutput = void
|
||||
|
||||
export type SessionWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionWaitOutput = void
|
||||
|
||||
export type SessionRevertStageInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly messageID: { readonly messageID: string; readonly files?: boolean | undefined }["messageID"]
|
||||
readonly files?: { readonly messageID: string; readonly files?: boolean | undefined }["files"]
|
||||
}
|
||||
|
||||
export type SessionsStageOutput = {
|
||||
export type SessionRevertStageOutput = {
|
||||
readonly data: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
@@ -532,17 +856,17 @@ export type SessionsStageOutput = {
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionsClearInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionRevertClearInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionsClearOutput = void
|
||||
export type SessionRevertClearOutput = void
|
||||
|
||||
export type SessionsCommitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionRevertCommitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionsCommitOutput = void
|
||||
export type SessionRevertCommitOutput = void
|
||||
|
||||
export type SessionsContextInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionContextInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionsContextOutput = {
|
||||
export type SessionContextOutput = {
|
||||
readonly data: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -574,7 +898,6 @@ export type SessionsContextOutput = {
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
readonly type: "user"
|
||||
}
|
||||
| {
|
||||
@@ -583,6 +906,7 @@ export type SessionsContextOutput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly sessionID: string
|
||||
readonly text: string
|
||||
readonly description?: string
|
||||
readonly type: "synthetic"
|
||||
}
|
||||
| {
|
||||
@@ -592,6 +916,14 @@ export type SessionsContextOutput = {
|
||||
readonly type: "system"
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "skill"
|
||||
readonly name: string
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
@@ -697,482 +1029,2206 @@ export type SessionsContextOutput = {
|
||||
>
|
||||
}["data"]
|
||||
|
||||
export type SessionsHistoryInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly limit?: { readonly limit?: number | undefined; readonly after?: number | undefined }["limit"]
|
||||
readonly after?: { readonly limit?: number | undefined; readonly after?: number | undefined }["after"]
|
||||
export type SessionListContextEntriesInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionListContextEntriesOutput = {
|
||||
readonly data: ReadonlyArray<{ readonly key: string; readonly value: JsonValue }>
|
||||
}["data"]
|
||||
|
||||
export type SessionPutContextEntryInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly key: string }["sessionID"]
|
||||
readonly key: { readonly sessionID: string; readonly key: string }["key"]
|
||||
readonly value: { readonly value: JsonValue }["value"]
|
||||
}
|
||||
|
||||
export type SessionsHistoryOutput = {
|
||||
export type SessionPutContextEntryOutput = void
|
||||
|
||||
export type SessionRemoveContextEntryInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly key: string }["sessionID"]
|
||||
readonly key: { readonly sessionID: string; readonly key: string }["key"]
|
||||
}
|
||||
|
||||
export type SessionRemoveContextEntryOutput = void
|
||||
|
||||
export type SessionLogInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly after?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["after"]
|
||||
readonly follow?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["follow"]
|
||||
}
|
||||
|
||||
export type SessionLogOutput =
|
||||
| (
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.agent.switched"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly agent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.model.switched"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.moved"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly location: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly subdirectory?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.renamed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly title: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.forked"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly parentID: string
|
||||
readonly messageID?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.prompted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.prompt.admitted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.context.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.synthetic"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
readonly description?: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.skill.activated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly name: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.shell.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.shell.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly callID: string
|
||||
readonly output: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.step.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly snapshot?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.step.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly finish: string
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly snapshot?: string
|
||||
readonly files?: ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.step.failed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.text.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.text.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.input.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly name: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.input.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.called"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly tool: string
|
||||
readonly input: { readonly [x: string]: unknown }
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.progress"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly structured: { readonly [x: string]: unknown }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.success"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly structured: { readonly [x: string]: unknown }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.failed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.retried"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly attempt: number
|
||||
readonly error: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string }
|
||||
readonly responseBody?: string
|
||||
readonly metadata?: { readonly [x: string]: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.compaction.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly reason: "auto" | "manual"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.compaction.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly text: string
|
||||
readonly recent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.revert.staged"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly revert: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
readonly snapshot?: string
|
||||
readonly diff?: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly path: string
|
||||
readonly status: "added" | "modified" | "deleted"
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly patch: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.revert.cleared"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.revert.committed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
|
||||
}
|
||||
)
|
||||
| { readonly type: "log.caught_up"; readonly aggregateID: string; readonly seq?: number }
|
||||
|
||||
export type SessionInterruptInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionInterruptOutput = void
|
||||
|
||||
export type SessionBackgroundInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionBackgroundOutput = void
|
||||
|
||||
export type SessionMessageInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly messageID: string }["sessionID"]
|
||||
readonly messageID: { readonly sessionID: string; readonly messageID: string }["messageID"]
|
||||
}
|
||||
|
||||
export type SessionMessageOutput = {
|
||||
readonly data:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "agent-switched"
|
||||
readonly agent: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly type: "user"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly sessionID: string
|
||||
readonly text: string
|
||||
readonly description?: string
|
||||
readonly type: "synthetic"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "system"
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "skill"
|
||||
readonly name: string
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "shell"
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
readonly output: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "assistant"
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
readonly status: "running"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
| {
|
||||
readonly status: "completed"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly attachments?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly ran?: number
|
||||
readonly completed?: number
|
||||
readonly pruned?: number
|
||||
}
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type MessageListInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly limit?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["limit"]
|
||||
readonly order?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["order"]
|
||||
readonly cursor?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["cursor"]
|
||||
}
|
||||
|
||||
export type MessageListOutput = {
|
||||
readonly data: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.agent.switched"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly agent: string
|
||||
}
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "agent-switched"
|
||||
readonly agent: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.model.switched"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.moved"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly location: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly subdirectory?: string
|
||||
}
|
||||
readonly time: { readonly created: number }
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly type: "user"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.renamed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly title: string }
|
||||
readonly time: { readonly created: number }
|
||||
readonly sessionID: string
|
||||
readonly text: string
|
||||
readonly description?: string
|
||||
readonly type: "synthetic"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.prompted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "system"
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "skill"
|
||||
readonly name: string
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "shell"
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
readonly output: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "assistant"
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
readonly status: "running"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
| {
|
||||
readonly status: "completed"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly attachments?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly ran?: number
|
||||
readonly completed?: number
|
||||
readonly pruned?: number
|
||||
}
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
}
|
||||
>
|
||||
readonly watermark?: number
|
||||
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
|
||||
}
|
||||
|
||||
export type ModelListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ModelListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly providerID: string
|
||||
readonly family?: string
|
||||
readonly name: string
|
||||
readonly api:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "native"
|
||||
readonly url?: string
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
readonly capabilities: {
|
||||
readonly tools: boolean
|
||||
readonly input: ReadonlyArray<string>
|
||||
readonly output: ReadonlyArray<string>
|
||||
}
|
||||
readonly request: {
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
readonly variant?: string
|
||||
}
|
||||
readonly variants: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}>
|
||||
readonly time: { readonly released: number }
|
||||
readonly cost: ReadonlyArray<{
|
||||
readonly tier?: { readonly type: "context"; readonly size: number }
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}>
|
||||
readonly status: "alpha" | "beta" | "deprecated" | "active"
|
||||
readonly enabled: boolean
|
||||
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
||||
}>
|
||||
}
|
||||
|
||||
export type ModelDefaultInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ModelDefaultOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly providerID: string
|
||||
readonly family?: string
|
||||
readonly name: string
|
||||
readonly api:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "native"
|
||||
readonly url?: string
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
readonly capabilities: {
|
||||
readonly tools: boolean
|
||||
readonly input: ReadonlyArray<string>
|
||||
readonly output: ReadonlyArray<string>
|
||||
}
|
||||
readonly request: {
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
readonly variant?: string
|
||||
}
|
||||
readonly variants: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}>
|
||||
readonly time: { readonly released: number }
|
||||
readonly cost: ReadonlyArray<{
|
||||
readonly tier?: { readonly type: "context"; readonly size: number }
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}>
|
||||
readonly status: "alpha" | "beta" | "deprecated" | "active"
|
||||
readonly enabled: boolean
|
||||
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
||||
} | null
|
||||
}
|
||||
|
||||
export type GenerateTextInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly prompt: {
|
||||
readonly prompt: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
}["prompt"]
|
||||
readonly model?: {
|
||||
readonly prompt: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
}["model"]
|
||||
}
|
||||
|
||||
export type GenerateTextOutput = { readonly data: { readonly text: string } }["data"]
|
||||
|
||||
export type ProviderListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProviderListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly integrationID?: string
|
||||
readonly name: string
|
||||
readonly disabled?: boolean
|
||||
readonly api:
|
||||
| {
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
|
||||
readonly request: {
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export type ProviderGetInput = {
|
||||
readonly providerID: { readonly providerID: string }["providerID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProviderGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly integrationID?: string
|
||||
readonly name: string
|
||||
readonly disabled?: boolean
|
||||
readonly api:
|
||||
| {
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
|
||||
readonly request: {
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly methods: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "oauth"
|
||||
readonly label: string
|
||||
readonly prompts?: ReadonlyArray<
|
||||
| {
|
||||
readonly type: "text"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly placeholder?: string
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "select"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly options: ReadonlyArray<{
|
||||
readonly label: string
|
||||
readonly value: string
|
||||
readonly hint?: string
|
||||
}>
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
>
|
||||
}
|
||||
| { readonly type: "key"; readonly label?: string }
|
||||
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
|
||||
>
|
||||
readonly connections: ReadonlyArray<
|
||||
| { readonly type: "credential"; readonly id: string; readonly label: string }
|
||||
| { readonly type: "env"; readonly name: string }
|
||||
>
|
||||
}>
|
||||
}
|
||||
|
||||
export type IntegrationGetInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly methods: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "oauth"
|
||||
readonly label: string
|
||||
readonly prompts?: ReadonlyArray<
|
||||
| {
|
||||
readonly type: "text"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly placeholder?: string
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "select"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly options: ReadonlyArray<{
|
||||
readonly label: string
|
||||
readonly value: string
|
||||
readonly hint?: string
|
||||
}>
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
>
|
||||
}
|
||||
| { readonly type: "key"; readonly label?: string }
|
||||
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
|
||||
>
|
||||
readonly connections: ReadonlyArray<
|
||||
| { readonly type: "credential"; readonly id: string; readonly label: string }
|
||||
| { readonly type: "env"; readonly name: string }
|
||||
>
|
||||
} | null
|
||||
}
|
||||
|
||||
export type IntegrationConnectKeyInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
|
||||
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
|
||||
}
|
||||
|
||||
export type IntegrationConnectKeyOutput = void
|
||||
|
||||
export type IntegrationConnectOauthInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly methodID: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["methodID"]
|
||||
readonly inputs: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["inputs"]
|
||||
readonly label?: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["label"]
|
||||
}
|
||||
|
||||
export type IntegrationConnectOauthOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly attemptID: string
|
||||
readonly url: string
|
||||
readonly instructions: string
|
||||
readonly mode: "auto" | "code"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationAttemptStatusInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationAttemptStatusOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data:
|
||||
| {
|
||||
readonly status: "pending"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly status: "complete"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly status: "failed"
|
||||
readonly message: string
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly status: "expired"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationAttemptCompleteInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly code?: { readonly code?: string | undefined }["code"]
|
||||
}
|
||||
|
||||
export type IntegrationAttemptCompleteOutput = void
|
||||
|
||||
export type IntegrationAttemptCancelInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationAttemptCancelOutput = void
|
||||
|
||||
export type ServerMcpListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ServerMcpListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly status:
|
||||
| { readonly status: "connected" }
|
||||
| { readonly status: "disconnected" }
|
||||
| { readonly status: "disabled" }
|
||||
| { readonly status: "failed"; readonly error: string }
|
||||
| { readonly status: "needs_auth" }
|
||||
| { readonly status: "needs_client_registration"; readonly error: string }
|
||||
readonly integrationID?: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type CredentialUpdateInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly label: { readonly label: string }["label"]
|
||||
}
|
||||
|
||||
export type CredentialUpdateOutput = void
|
||||
|
||||
export type CredentialRemoveInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CredentialRemoveOutput = void
|
||||
|
||||
export type ProjectCurrentInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectCurrentOutput = { readonly id: string; readonly directory: string }
|
||||
|
||||
export type ProjectDirectoriesInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectDirectoriesOutput = ReadonlyArray<{ readonly directory: string; readonly strategy?: string }>
|
||||
|
||||
export type PermissionListRequestsInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PermissionListRequestsOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}>
|
||||
}
|
||||
|
||||
export type PermissionListSavedInput = { readonly projectID?: { readonly projectID?: string | undefined }["projectID"] }
|
||||
|
||||
export type PermissionListSavedOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly projectID: string
|
||||
readonly action: string
|
||||
readonly resource: string
|
||||
}>
|
||||
}["data"]
|
||||
|
||||
export type PermissionRemoveSavedInput = { readonly id: { readonly id: string }["id"] }
|
||||
|
||||
export type PermissionRemoveSavedOutput = void
|
||||
|
||||
export type PermissionCreateInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["id"]
|
||||
readonly action: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["action"]
|
||||
readonly resources: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["resources"]
|
||||
readonly save?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["save"]
|
||||
readonly metadata?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["metadata"]
|
||||
readonly source?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["source"]
|
||||
readonly agent?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["agent"]
|
||||
}
|
||||
|
||||
export type PermissionCreateOutput = {
|
||||
readonly data: { readonly id: string; readonly effect: "allow" | "deny" | "ask" }
|
||||
}["data"]
|
||||
|
||||
export type PermissionListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type PermissionListOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}>
|
||||
}["data"]
|
||||
|
||||
export type PermissionGetInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
}
|
||||
|
||||
export type PermissionGetOutput = {
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type PermissionReplyInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
readonly reply: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["reply"]
|
||||
readonly message?: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["message"]
|
||||
}
|
||||
|
||||
export type PermissionReplyOutput = void
|
||||
|
||||
export type FileReadInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly path: string
|
||||
}
|
||||
|
||||
export type FileReadOutput = globalThis.Uint8Array
|
||||
|
||||
export type FileListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["location"]
|
||||
readonly path?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["path"]
|
||||
}
|
||||
|
||||
export type FileListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
|
||||
}
|
||||
|
||||
export type FileFindInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["location"]
|
||||
readonly query: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["query"]
|
||||
readonly type?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["type"]
|
||||
readonly limit?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["limit"]
|
||||
}
|
||||
|
||||
export type FileFindOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
|
||||
}
|
||||
|
||||
export type CommandListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CommandListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly template: string
|
||||
readonly description?: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly subtask?: boolean
|
||||
}>
|
||||
}
|
||||
|
||||
export type SkillListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type SkillListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly description?: string
|
||||
readonly slash?: boolean
|
||||
readonly autoinvoke?: boolean
|
||||
readonly location: string
|
||||
readonly content: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type EventSubscribeOutput =
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "models-dev.refreshed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "integration.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "integration.connection.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly integrationID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "catalog.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "agent.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.created"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly slug: string
|
||||
readonly projectID: string
|
||||
readonly workspaceID?: string
|
||||
readonly directory: string
|
||||
readonly path?: string
|
||||
readonly parentID?: string
|
||||
readonly summary?: {
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly files: number
|
||||
readonly diffs?: ReadonlyArray<{
|
||||
readonly file?: string
|
||||
readonly patch?: string
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly status?: "added" | "deleted" | "modified"
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.prompt.admitted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly prompt: {
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.context.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.synthetic"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.shell.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.shell.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly callID: string
|
||||
readonly output: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.step.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly snapshot?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.step.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly finish: string
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly snapshot?: string
|
||||
readonly files?: ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.step.failed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.text.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.text.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.input.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly name: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.input.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.called"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly tool: string
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly share?: { readonly url: string }
|
||||
readonly title: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly version: string
|
||||
readonly metadata?: { readonly [x: string]: any }
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly updated: number
|
||||
readonly compacting?: number
|
||||
readonly archived?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.progress"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.success"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly result?: JsonValue
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.tool.failed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.reasoning.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.reasoning.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.retried"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly attempt: number
|
||||
readonly error: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string }
|
||||
readonly responseBody?: string
|
||||
readonly metadata?: { readonly [x: string]: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.compaction.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly reason: "auto" | "manual"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.compaction.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly text: string
|
||||
readonly recent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.revert.staged"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly revert: {
|
||||
readonly permission?: ReadonlyArray<{
|
||||
readonly permission: string
|
||||
readonly pattern: string
|
||||
readonly action: "allow" | "deny" | "ask"
|
||||
}>
|
||||
readonly revert?: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
readonly snapshot?: string
|
||||
readonly diff?: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly path: string
|
||||
readonly status: "added" | "modified" | "deleted"
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly patch: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.revert.cleared"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly slug: string
|
||||
readonly projectID: string
|
||||
readonly workspaceID?: string
|
||||
readonly directory: string
|
||||
readonly path?: string
|
||||
readonly parentID?: string
|
||||
readonly summary?: {
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly files: number
|
||||
readonly diffs?: ReadonlyArray<{
|
||||
readonly file?: string
|
||||
readonly patch?: string
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly status?: "added" | "deleted" | "modified"
|
||||
}>
|
||||
}
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly share?: { readonly url: string }
|
||||
readonly title: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly version: string
|
||||
readonly metadata?: { readonly [x: string]: any }
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly updated: number
|
||||
readonly compacting?: number
|
||||
readonly archived?: number
|
||||
}
|
||||
readonly permission?: ReadonlyArray<{
|
||||
readonly permission: string
|
||||
readonly pattern: string
|
||||
readonly action: "allow" | "deny" | "ask"
|
||||
}>
|
||||
readonly revert?: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
readonly snapshot?: string
|
||||
readonly diff?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly type: "session.next.revert.committed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.deleted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly slug: string
|
||||
readonly projectID: string
|
||||
readonly workspaceID?: string
|
||||
readonly directory: string
|
||||
readonly path?: string
|
||||
readonly parentID?: string
|
||||
readonly summary?: {
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly files: number
|
||||
readonly diffs?: ReadonlyArray<{
|
||||
readonly file?: string
|
||||
readonly patch?: string
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly status?: "added" | "deleted" | "modified"
|
||||
}>
|
||||
}
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly share?: { readonly url: string }
|
||||
readonly title: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly version: string
|
||||
readonly metadata?: { readonly [x: string]: any }
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly updated: number
|
||||
readonly compacting?: number
|
||||
readonly archived?: number
|
||||
}
|
||||
readonly permission?: ReadonlyArray<{
|
||||
readonly permission: string
|
||||
readonly pattern: string
|
||||
readonly action: "allow" | "deny" | "ask"
|
||||
}>
|
||||
readonly revert?: {
|
||||
readonly messageID: string
|
||||
readonly partID?: string
|
||||
readonly snapshot?: string
|
||||
readonly diff?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
readonly hasMore: boolean
|
||||
}
|
||||
|
||||
export type SessionsEventsInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly after?: { readonly after?: number | undefined }["after"]
|
||||
}
|
||||
|
||||
export type SessionsEventsOutput =
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "message.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly info:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly role: "user"
|
||||
readonly time: { readonly created: number }
|
||||
readonly format?:
|
||||
| (
|
||||
| { readonly type: "text" }
|
||||
| {
|
||||
readonly type: "json_schema"
|
||||
readonly schema: { readonly [x: string]: any }
|
||||
readonly retryCount?: number | undefined | undefined
|
||||
}
|
||||
)
|
||||
| undefined
|
||||
readonly summary?:
|
||||
| {
|
||||
readonly title?: string | undefined
|
||||
readonly body?: string | undefined
|
||||
readonly diffs: ReadonlyArray<{
|
||||
readonly file?: string
|
||||
readonly patch?: string
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
readonly status?: "added" | "deleted" | "modified"
|
||||
}>
|
||||
}
|
||||
| undefined
|
||||
readonly agent: string
|
||||
readonly model: {
|
||||
readonly providerID: string
|
||||
readonly modelID: string
|
||||
readonly variant?: string | undefined
|
||||
}
|
||||
readonly system?: string | undefined
|
||||
readonly tools?: { readonly [x: string]: boolean } | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly role: "assistant"
|
||||
readonly time: { readonly created: number; readonly completed?: number | undefined }
|
||||
readonly error?:
|
||||
| {
|
||||
readonly name: "ProviderAuthError"
|
||||
readonly data: { readonly providerID: string; readonly message: string }
|
||||
}
|
||||
| {
|
||||
readonly name: "UnknownError"
|
||||
readonly data: { readonly message: string; readonly ref?: string | undefined }
|
||||
}
|
||||
| { readonly name: "MessageOutputLengthError"; readonly data: {} }
|
||||
| { readonly name: "MessageAbortedError"; readonly data: { readonly message: string } }
|
||||
| {
|
||||
readonly name: "StructuredOutputError"
|
||||
readonly data: { readonly message: string; readonly retries: number }
|
||||
}
|
||||
| {
|
||||
readonly name: "ContextOverflowError"
|
||||
readonly data: { readonly message: string; readonly responseBody?: string | undefined }
|
||||
}
|
||||
| { readonly name: "ContentFilterError"; readonly data: { readonly message: string } }
|
||||
| {
|
||||
readonly name: "APIError"
|
||||
readonly data: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number | undefined
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string } | undefined
|
||||
readonly responseBody?: string | undefined
|
||||
readonly metadata?: { readonly [x: string]: string } | undefined
|
||||
}
|
||||
}
|
||||
| undefined
|
||||
readonly parentID: string
|
||||
readonly modelID: string
|
||||
readonly providerID: string
|
||||
readonly mode: string
|
||||
readonly agent: string
|
||||
readonly path: { readonly cwd: string; readonly root: string }
|
||||
readonly summary?: boolean | undefined
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly total?: number | undefined
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly structured?: any | undefined
|
||||
readonly variant?: string | undefined
|
||||
readonly finish?: string | undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "message.removed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "message.part.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly part:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "text"
|
||||
readonly text: string
|
||||
readonly synthetic?: boolean | undefined
|
||||
readonly ignored?: boolean | undefined
|
||||
readonly time?: { readonly start: number; readonly end?: number | undefined } | undefined
|
||||
readonly metadata?: { readonly [x: string]: any } | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "subtask"
|
||||
readonly prompt: string
|
||||
readonly description: string
|
||||
readonly agent: string
|
||||
readonly model?: { readonly providerID: string; readonly modelID: string } | undefined
|
||||
readonly command?: string | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly metadata?: { readonly [x: string]: any } | undefined
|
||||
readonly time: { readonly start: number; readonly end?: number | undefined }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "file"
|
||||
readonly mime: string
|
||||
readonly filename?: string | undefined
|
||||
readonly url: string
|
||||
readonly source?:
|
||||
| (
|
||||
| {
|
||||
readonly text: { readonly value: string; readonly start: number; readonly end: number }
|
||||
readonly type: "file"
|
||||
readonly path: string
|
||||
}
|
||||
| {
|
||||
readonly text: { readonly value: string; readonly start: number; readonly end: number }
|
||||
readonly type: "symbol"
|
||||
readonly path: string
|
||||
readonly range: {
|
||||
readonly start: { readonly line: number; readonly character: number }
|
||||
readonly end: { readonly line: number; readonly character: number }
|
||||
}
|
||||
readonly name: string
|
||||
readonly kind: number
|
||||
}
|
||||
| {
|
||||
readonly text: { readonly value: string; readonly start: number; readonly end: number }
|
||||
readonly type: "resource"
|
||||
readonly clientName: string
|
||||
readonly uri: string
|
||||
}
|
||||
)
|
||||
| undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "tool"
|
||||
readonly callID: string
|
||||
readonly tool: string
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: { readonly [x: string]: any }; readonly raw: string }
|
||||
| {
|
||||
readonly status: "running"
|
||||
readonly input: { readonly [x: string]: any }
|
||||
readonly title?: string | undefined
|
||||
readonly metadata?: { readonly [x: string]: any } | undefined
|
||||
readonly time: { readonly start: number }
|
||||
}
|
||||
| {
|
||||
readonly status: "completed"
|
||||
readonly input: { readonly [x: string]: any }
|
||||
readonly output: string
|
||||
readonly title: string
|
||||
readonly metadata: { readonly [x: string]: any }
|
||||
readonly time: {
|
||||
readonly start: number
|
||||
readonly end: number
|
||||
readonly compacted?: number | undefined
|
||||
}
|
||||
readonly attachments?:
|
||||
| ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "file"
|
||||
readonly mime: string
|
||||
readonly filename?: string | undefined
|
||||
readonly url: string
|
||||
readonly source?:
|
||||
| (
|
||||
| {
|
||||
readonly text: {
|
||||
readonly value: string
|
||||
readonly start: number
|
||||
readonly end: number
|
||||
}
|
||||
readonly type: "file"
|
||||
readonly path: string
|
||||
}
|
||||
| {
|
||||
readonly text: {
|
||||
readonly value: string
|
||||
readonly start: number
|
||||
readonly end: number
|
||||
}
|
||||
readonly type: "symbol"
|
||||
readonly path: string
|
||||
readonly range: {
|
||||
readonly start: { readonly line: number; readonly character: number }
|
||||
readonly end: { readonly line: number; readonly character: number }
|
||||
}
|
||||
readonly name: string
|
||||
readonly kind: number
|
||||
}
|
||||
| {
|
||||
readonly text: {
|
||||
readonly value: string
|
||||
readonly start: number
|
||||
readonly end: number
|
||||
}
|
||||
readonly type: "resource"
|
||||
readonly clientName: string
|
||||
readonly uri: string
|
||||
}
|
||||
)
|
||||
| undefined
|
||||
}>
|
||||
| undefined
|
||||
}
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: any }
|
||||
readonly error: string
|
||||
readonly metadata?: { readonly [x: string]: any } | undefined
|
||||
readonly time: { readonly start: number; readonly end: number }
|
||||
}
|
||||
readonly metadata?: { readonly [x: string]: any } | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "step-start"
|
||||
readonly snapshot?: string | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "step-finish"
|
||||
readonly reason: string
|
||||
readonly snapshot?: string | undefined
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly total?: number | undefined
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "snapshot"
|
||||
readonly snapshot: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "patch"
|
||||
readonly hash: string
|
||||
readonly files: ReadonlyArray<string>
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "agent"
|
||||
readonly name: string
|
||||
readonly source?: { readonly value: string; readonly start: number; readonly end: number } | undefined
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "retry"
|
||||
readonly attempt: number
|
||||
readonly error: {
|
||||
readonly name: "APIError"
|
||||
readonly data: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number | undefined
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string } | undefined
|
||||
readonly responseBody?: string | undefined
|
||||
readonly metadata?: { readonly [x: string]: string } | undefined
|
||||
}
|
||||
}
|
||||
readonly time: { readonly created: number }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly type: "compaction"
|
||||
readonly auto: boolean
|
||||
readonly overflow?: boolean | undefined
|
||||
readonly tail_start_id?: string | undefined
|
||||
}
|
||||
readonly time: number
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "message.part.removed"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string; readonly partID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1220,6 +3276,19 @@ export type SessionsEventsOutput =
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly title: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.forked"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly parentID: string
|
||||
readonly messageID?: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1243,7 +3312,6 @@ export type SessionsEventsOutput =
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
@@ -1271,11 +3339,23 @@ export type SessionsEventsOutput =
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
}
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.execution.settled"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly outcome: "success" | "failure" | "interrupted"
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1300,6 +3380,22 @@ export type SessionsEventsOutput =
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
readonly description?: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.skill.activated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly name: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1392,6 +3488,20 @@ export type SessionsEventsOutput =
|
||||
readonly textID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.text.delta"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly delta: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1406,6 +3516,49 @@ export type SessionsEventsOutput =
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.delta"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly delta: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1420,6 +3573,20 @@ export type SessionsEventsOutput =
|
||||
readonly name: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.tool.input.delta"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly delta: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1514,35 +3681,6 @@ export type SessionsEventsOutput =
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.started"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.reasoning.ended"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1576,6 +3714,19 @@ export type SessionsEventsOutput =
|
||||
readonly reason: "auto" | "manual"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.next.compaction.delta"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly timestamp: number
|
||||
readonly sessionID: string
|
||||
readonly messageID: string
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
@@ -1631,965 +3782,255 @@ export type SessionsEventsOutput =
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
|
||||
}
|
||||
|
||||
export type SessionsInterruptInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type SessionsInterruptOutput = void
|
||||
|
||||
export type SessionsMessageInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly messageID: string }["sessionID"]
|
||||
readonly messageID: { readonly sessionID: string; readonly messageID: string }["messageID"]
|
||||
}
|
||||
|
||||
export type SessionsMessageOutput = {
|
||||
readonly data:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "agent-switched"
|
||||
readonly agent: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
readonly type: "user"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly sessionID: string
|
||||
readonly text: string
|
||||
readonly type: "synthetic"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "system"
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "shell"
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
readonly output: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "assistant"
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
readonly status: "running"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
| {
|
||||
readonly status: "completed"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly attachments?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly ran?: number
|
||||
readonly completed?: number
|
||||
readonly pruned?: number
|
||||
}
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type MessagesListInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly limit?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["limit"]
|
||||
readonly order?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["order"]
|
||||
readonly cursor?: {
|
||||
readonly limit?: number | undefined
|
||||
readonly order?: "asc" | "desc" | undefined
|
||||
readonly cursor?: string | undefined
|
||||
}["cursor"]
|
||||
}
|
||||
|
||||
export type MessagesListOutput = {
|
||||
readonly data: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "agent-switched"
|
||||
readonly agent: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly text: string
|
||||
readonly files?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly agents?: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly system?: string
|
||||
readonly type: "user"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly sessionID: string
|
||||
readonly text: string
|
||||
readonly type: "synthetic"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "system"
|
||||
readonly text: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "shell"
|
||||
readonly callID: string
|
||||
readonly command: string
|
||||
readonly output: string
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number; readonly completed?: number }
|
||||
readonly type: "assistant"
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
readonly status: "running"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
}
|
||||
| {
|
||||
readonly status: "completed"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly attachments?: ReadonlyArray<{
|
||||
readonly uri: string
|
||||
readonly mime: string
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
||||
}>
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
readonly created: number
|
||||
readonly ran?: number
|
||||
readonly completed?: number
|
||||
readonly pruned?: number
|
||||
}
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly time: { readonly created: number }
|
||||
}
|
||||
>
|
||||
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
|
||||
}
|
||||
|
||||
export type ModelsListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ModelsListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly providerID: string
|
||||
readonly family?: string
|
||||
readonly name: string
|
||||
readonly api:
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "native"
|
||||
readonly url?: string
|
||||
readonly settings: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
readonly capabilities: {
|
||||
readonly tools: boolean
|
||||
readonly input: ReadonlyArray<string>
|
||||
readonly output: ReadonlyArray<string>
|
||||
}
|
||||
readonly request: {
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
readonly variant?: string
|
||||
}
|
||||
readonly variants: ReadonlyArray<{
|
||||
| {
|
||||
readonly id: string
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
}>
|
||||
readonly time: { readonly released: number }
|
||||
readonly cost: ReadonlyArray<{
|
||||
readonly tier?: { readonly type: "context"; readonly size: number }
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}>
|
||||
readonly status: "alpha" | "beta" | "deprecated" | "active"
|
||||
readonly enabled: boolean
|
||||
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
||||
}>
|
||||
}
|
||||
|
||||
export type ProvidersListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProvidersListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly integrationID?: string
|
||||
readonly name: string
|
||||
readonly disabled?: boolean
|
||||
readonly api:
|
||||
| {
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
|
||||
readonly request: {
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "file.edited"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly file: string }
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export type ProvidersGetInput = {
|
||||
readonly providerID: { readonly providerID: string }["providerID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProvidersGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly integrationID?: string
|
||||
readonly name: string
|
||||
readonly disabled?: boolean
|
||||
readonly api:
|
||||
| {
|
||||
readonly type: "aisdk"
|
||||
readonly package: string
|
||||
readonly url?: string
|
||||
readonly settings?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
|
||||
readonly request: {
|
||||
readonly headers: { readonly [x: string]: string }
|
||||
readonly body: { readonly [x: string]: JsonValue }
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "reference.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationsListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationsListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly methods: ReadonlyArray<
|
||||
| {
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "permission.v2.asked"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "permission.v2.replied"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly requestID: string
|
||||
readonly reply: "once" | "always" | "reject"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "plugin.added"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly id: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "project.directories.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly projectID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "command.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "skill.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "file.watcher.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly file: string; readonly event: "add" | "change" | "unlink" }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "pty.created"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly type: "oauth"
|
||||
readonly label: string
|
||||
readonly prompts?: ReadonlyArray<
|
||||
| {
|
||||
readonly type: "text"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly placeholder?: string
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "select"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly options: ReadonlyArray<{
|
||||
readonly label: string
|
||||
readonly value: string
|
||||
readonly hint?: string
|
||||
}>
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
>
|
||||
readonly title: string
|
||||
readonly command: string
|
||||
readonly args: ReadonlyArray<string>
|
||||
readonly cwd: string
|
||||
readonly status: "running" | "exited"
|
||||
readonly pid: number
|
||||
readonly exitCode?: number
|
||||
}
|
||||
| { readonly type: "key"; readonly label?: string }
|
||||
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
|
||||
>
|
||||
readonly connections: ReadonlyArray<
|
||||
| { readonly type: "credential"; readonly id: string; readonly label: string }
|
||||
| { readonly type: "env"; readonly name: string }
|
||||
>
|
||||
}>
|
||||
}
|
||||
|
||||
export type IntegrationsGetInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationsGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly methods: ReadonlyArray<
|
||||
| {
|
||||
readonly id: string
|
||||
readonly type: "oauth"
|
||||
readonly label: string
|
||||
readonly prompts?: ReadonlyArray<
|
||||
| {
|
||||
readonly type: "text"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly placeholder?: string
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
| {
|
||||
readonly type: "select"
|
||||
readonly key: string
|
||||
readonly message: string
|
||||
readonly options: ReadonlyArray<{
|
||||
readonly label: string
|
||||
readonly value: string
|
||||
readonly hint?: string
|
||||
}>
|
||||
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
|
||||
}
|
||||
>
|
||||
}
|
||||
| { readonly type: "key"; readonly label?: string }
|
||||
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
|
||||
>
|
||||
readonly connections: ReadonlyArray<
|
||||
| { readonly type: "credential"; readonly id: string; readonly label: string }
|
||||
| { readonly type: "env"; readonly name: string }
|
||||
>
|
||||
} | null
|
||||
}
|
||||
|
||||
export type IntegrationsConnectKeyInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
|
||||
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
|
||||
}
|
||||
|
||||
export type IntegrationsConnectKeyOutput = void
|
||||
|
||||
export type IntegrationsConnectOauthInput = {
|
||||
readonly integrationID: { readonly integrationID: string }["integrationID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly methodID: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["methodID"]
|
||||
readonly inputs: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["inputs"]
|
||||
readonly label?: {
|
||||
readonly methodID: string
|
||||
readonly inputs: { readonly [x: string]: string }
|
||||
readonly label?: string | undefined
|
||||
}["label"]
|
||||
}
|
||||
|
||||
export type IntegrationsConnectOauthOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly attemptID: string
|
||||
readonly url: string
|
||||
readonly instructions: string
|
||||
readonly mode: "auto" | "code"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationsAttemptStatusInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationsAttemptStatusOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data:
|
||||
| {
|
||||
readonly status: "pending"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "pty.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly title: string
|
||||
readonly command: string
|
||||
readonly args: ReadonlyArray<string>
|
||||
readonly cwd: string
|
||||
readonly status: "running" | "exited"
|
||||
readonly pid: number
|
||||
readonly exitCode?: number
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly status: "complete"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "pty.exited"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly id: string; readonly exitCode: number }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "pty.deleted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly id: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "shell.created"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly info: {
|
||||
readonly id: string
|
||||
readonly status: "running" | "exited" | "timeout" | "killed"
|
||||
readonly command: string
|
||||
readonly cwd: string
|
||||
readonly shell: string
|
||||
readonly file: string
|
||||
readonly pid?: number
|
||||
readonly exit?: number
|
||||
readonly metadata: { readonly [x: string]: unknown }
|
||||
readonly time: { readonly started: number; readonly completed?: number }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly status: "failed"
|
||||
readonly message: string
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "shell.exited"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly exit?: number
|
||||
readonly status: "running" | "exited" | "timeout" | "killed"
|
||||
}
|
||||
| {
|
||||
readonly status: "expired"
|
||||
readonly time: {
|
||||
readonly created: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "shell.deleted"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly id: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "question.v2.asked"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly questions: ReadonlyArray<{
|
||||
readonly question: string
|
||||
readonly header: string
|
||||
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
|
||||
readonly multiple?: boolean
|
||||
readonly custom?: boolean
|
||||
}>
|
||||
readonly tool?: { readonly messageID: string; readonly callID: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "question.v2.replied"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly requestID: string
|
||||
readonly answers: ReadonlyArray<ReadonlyArray<string>>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "question.v2.rejected"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly requestID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "todo.updated"
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly todos: ReadonlyArray<{ readonly content: string; readonly status: string; readonly priority: string }>
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly metadata?: { readonly [x: string]: unknown } | undefined
|
||||
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number } | undefined
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | undefined
|
||||
readonly type: "server.connected"
|
||||
readonly data: {}
|
||||
}
|
||||
|
||||
export type IntegrationsAttemptCompleteInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly code?: { readonly code?: string | undefined }["code"]
|
||||
}
|
||||
export type EventChangesOutput =
|
||||
| { readonly type: "log.hint"; readonly aggregateID: string; readonly seq: number }
|
||||
| { readonly type: "log.sweep_required" }
|
||||
|
||||
export type IntegrationsAttemptCompleteOutput = void
|
||||
|
||||
export type IntegrationsAttemptCancelInput = {
|
||||
readonly attemptID: { readonly attemptID: string }["attemptID"]
|
||||
export type PtyListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type IntegrationsAttemptCancelOutput = void
|
||||
|
||||
export type CredentialsUpdateInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly label: { readonly label: string }["label"]
|
||||
}
|
||||
|
||||
export type CredentialsUpdateOutput = void
|
||||
|
||||
export type CredentialsRemoveInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CredentialsRemoveOutput = void
|
||||
|
||||
export type PermissionsListRequestsInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PermissionsListRequestsOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}>
|
||||
}
|
||||
|
||||
export type PermissionsListSavedInput = {
|
||||
readonly projectID?: { readonly projectID?: string | undefined }["projectID"]
|
||||
}
|
||||
|
||||
export type PermissionsListSavedOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly projectID: string
|
||||
readonly action: string
|
||||
readonly resource: string
|
||||
}>
|
||||
}["data"]
|
||||
|
||||
export type PermissionsRemoveSavedInput = { readonly id: { readonly id: string }["id"] }
|
||||
|
||||
export type PermissionsRemoveSavedOutput = void
|
||||
|
||||
export type PermissionsCreateInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["id"]
|
||||
readonly action: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["action"]
|
||||
readonly resources: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["resources"]
|
||||
readonly save?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["save"]
|
||||
readonly metadata?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["metadata"]
|
||||
readonly source?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["source"]
|
||||
readonly agent?: {
|
||||
readonly id?: string | null
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
readonly agent?: string | null
|
||||
}["agent"]
|
||||
}
|
||||
|
||||
export type PermissionsCreateOutput = {
|
||||
readonly data: { readonly id: string; readonly effect: "allow" | "deny" | "ask" }
|
||||
}["data"]
|
||||
|
||||
export type PermissionsListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type PermissionsListOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}>
|
||||
}["data"]
|
||||
|
||||
export type PermissionsGetInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
}
|
||||
|
||||
export type PermissionsGetOutput = {
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly action: string
|
||||
readonly resources: ReadonlyArray<string>
|
||||
readonly save?: ReadonlyArray<string>
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type PermissionsReplyInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
readonly reply: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["reply"]
|
||||
readonly message?: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["message"]
|
||||
}
|
||||
|
||||
export type PermissionsReplyOutput = void
|
||||
|
||||
export type FilesListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["location"]
|
||||
readonly path?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly path?: string | undefined
|
||||
}["path"]
|
||||
}
|
||||
|
||||
export type FilesListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
|
||||
}
|
||||
|
||||
export type FilesFindInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["location"]
|
||||
readonly query: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["query"]
|
||||
readonly type?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["type"]
|
||||
readonly limit?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly query: string
|
||||
readonly type?: "file" | "directory" | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["limit"]
|
||||
}
|
||||
|
||||
export type FilesFindOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
|
||||
}
|
||||
|
||||
export type CommandsListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type CommandsListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly template: string
|
||||
readonly description?: string
|
||||
readonly agent?: string
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly subtask?: boolean
|
||||
}>
|
||||
}
|
||||
|
||||
export type SkillsListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type SkillsListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly description?: string
|
||||
readonly slash?: boolean
|
||||
readonly location: string
|
||||
readonly content: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type EventsSubscribeOutput = OpenCodeEventEncoded
|
||||
|
||||
export type PtysListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtysListOutput = {
|
||||
export type PtyListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2607,7 +4048,7 @@ export type PtysListOutput = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type PtysCreateInput = {
|
||||
export type PtyCreateInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
@@ -2648,7 +4089,7 @@ export type PtysCreateInput = {
|
||||
}["env"]
|
||||
}
|
||||
|
||||
export type PtysCreateOutput = {
|
||||
export type PtyCreateOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2666,14 +4107,14 @@ export type PtysCreateOutput = {
|
||||
}
|
||||
}
|
||||
|
||||
export type PtysGetInput = {
|
||||
export type PtyGetInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtysGetOutput = {
|
||||
export type PtyGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2691,7 +4132,7 @@ export type PtysGetOutput = {
|
||||
}
|
||||
}
|
||||
|
||||
export type PtysUpdateInput = {
|
||||
export type PtyUpdateInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
@@ -2703,7 +4144,7 @@ export type PtysUpdateInput = {
|
||||
readonly size?: { readonly title?: string; readonly size?: { readonly rows: number; readonly cols: number } }["size"]
|
||||
}
|
||||
|
||||
export type PtysUpdateOutput = {
|
||||
export type PtyUpdateOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2721,22 +4162,176 @@ export type PtysUpdateOutput = {
|
||||
}
|
||||
}
|
||||
|
||||
export type PtysRemoveInput = {
|
||||
export type PtyRemoveInput = {
|
||||
readonly ptyID: { readonly ptyID: string }["ptyID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PtysRemoveOutput = void
|
||||
export type PtyRemoveOutput = void
|
||||
|
||||
export type QuestionsListRequestsInput = {
|
||||
export type ShellListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type QuestionsListRequestsOutput = {
|
||||
export type ShellListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly status: "running" | "exited" | "timeout" | "killed"
|
||||
readonly command: string
|
||||
readonly cwd: string
|
||||
readonly shell: string
|
||||
readonly file: string
|
||||
readonly pid?: number
|
||||
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly metadata: { readonly [x: string]: JsonValue }
|
||||
readonly time: {
|
||||
readonly started: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export type ShellCreateInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly command: {
|
||||
readonly command: string
|
||||
readonly cwd?: string
|
||||
readonly timeout?: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["command"]
|
||||
readonly cwd?: {
|
||||
readonly command: string
|
||||
readonly cwd?: string
|
||||
readonly timeout?: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["cwd"]
|
||||
readonly timeout?: {
|
||||
readonly command: string
|
||||
readonly cwd?: string
|
||||
readonly timeout?: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["timeout"]
|
||||
readonly metadata?: {
|
||||
readonly command: string
|
||||
readonly cwd?: string
|
||||
readonly timeout?: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}["metadata"]
|
||||
}
|
||||
|
||||
export type ShellCreateOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly status: "running" | "exited" | "timeout" | "killed"
|
||||
readonly command: string
|
||||
readonly cwd: string
|
||||
readonly shell: string
|
||||
readonly file: string
|
||||
readonly pid?: number
|
||||
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly metadata: { readonly [x: string]: JsonValue }
|
||||
readonly time: {
|
||||
readonly started: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellGetInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ShellGetOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly id: string
|
||||
readonly status: "running" | "exited" | "timeout" | "killed"
|
||||
readonly command: string
|
||||
readonly cwd: string
|
||||
readonly shell: string
|
||||
readonly file: string
|
||||
readonly pid?: number
|
||||
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly metadata: { readonly [x: string]: JsonValue }
|
||||
readonly time: {
|
||||
readonly started: number | "Infinity" | "-Infinity" | "NaN"
|
||||
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellOutputInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["location"]
|
||||
readonly cursor?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["cursor"]
|
||||
readonly limit?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
readonly cursor?: number | undefined
|
||||
readonly limit?: number | undefined
|
||||
}["limit"]
|
||||
}
|
||||
|
||||
export type ShellOutputOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: {
|
||||
readonly output: string
|
||||
readonly cursor: number
|
||||
readonly size: number
|
||||
readonly truncated: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellRemoveInput = {
|
||||
readonly id: { readonly id: string }["id"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ShellRemoveOutput = void
|
||||
|
||||
export type QuestionListRequestsInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type QuestionListRequestsOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2756,9 +4351,9 @@ export type QuestionsListRequestsOutput = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type QuestionsListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type QuestionListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
export type QuestionsListOutput = {
|
||||
export type QuestionListOutput = {
|
||||
readonly data: ReadonlyArray<{
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
@@ -2773,28 +4368,28 @@ export type QuestionsListOutput = {
|
||||
}>
|
||||
}["data"]
|
||||
|
||||
export type QuestionsReplyInput = {
|
||||
export type QuestionReplyInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
readonly answers: { readonly answers: ReadonlyArray<ReadonlyArray<string>> }["answers"]
|
||||
}
|
||||
|
||||
export type QuestionsReplyOutput = void
|
||||
export type QuestionReplyOutput = void
|
||||
|
||||
export type QuestionsRejectInput = {
|
||||
export type QuestionRejectInput = {
|
||||
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
|
||||
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
|
||||
}
|
||||
|
||||
export type QuestionsRejectOutput = void
|
||||
export type QuestionRejectOutput = void
|
||||
|
||||
export type ReferencesListInput = {
|
||||
export type ReferenceListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ReferencesListOutput = {
|
||||
export type ReferenceListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
@@ -2817,7 +4412,7 @@ export type ReferencesListOutput = {
|
||||
}>
|
||||
}
|
||||
|
||||
export type ProjectCopiesCreateInput = {
|
||||
export type ProjectCopyCreateInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
@@ -2827,9 +4422,9 @@ export type ProjectCopiesCreateInput = {
|
||||
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
|
||||
}
|
||||
|
||||
export type ProjectCopiesCreateOutput = { readonly directory: string }
|
||||
export type ProjectCopyCreateOutput = { readonly directory: string }
|
||||
|
||||
export type ProjectCopiesRemoveInput = {
|
||||
export type ProjectCopyRemoveInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
@@ -2838,13 +4433,13 @@ export type ProjectCopiesRemoveInput = {
|
||||
readonly force: { readonly directory: string; readonly force: boolean }["force"]
|
||||
}
|
||||
|
||||
export type ProjectCopiesRemoveOutput = void
|
||||
export type ProjectCopyRemoveOutput = void
|
||||
|
||||
export type ProjectCopiesRefreshInput = {
|
||||
export type ProjectCopyRefreshInput = {
|
||||
readonly projectID: { readonly projectID: string }["projectID"]
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type ProjectCopiesRefreshOutput = void
|
||||
export type ProjectCopyRefreshOutput = void
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./generated/index"
|
||||
export type { EventsSubscribeOutput as OpenCodeEvent } from "./generated/types"
|
||||
export type { EventSubscribeOutput as OpenCodeEvent } from "./generated/types"
|
||||
export type OpenCodeClient = ReturnType<typeof import("./generated/client").make>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Schema } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { Location as CoreLocation } from "@opencode-ai/core/location"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionInput as CoreSessionInput } from "@opencode-ai/core/session/input"
|
||||
import { SessionMessage as CoreSessionMessage } from "@opencode-ai/core/session/message"
|
||||
@@ -19,17 +20,21 @@ import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import { Api } from "@opencode-ai/server/api"
|
||||
import { compile, emitPromise } from "@opencode-ai/httpapi-codegen"
|
||||
import { ClientApi, endpointNames, groupNames, omitEndpoints } from "../src/contract"
|
||||
import { ClientApi, endpointNames, groupNames, promiseOmitEndpoints } from "../src/contract"
|
||||
|
||||
test("Core and Server reuse the authoritative Schema and Protocol values", () => {
|
||||
expect(AgentV2.ID).toBe(Agent.ID)
|
||||
expect(CoreLocation.Ref).toBe(Location.Ref)
|
||||
expect(ModelV2.Ref).toBe(Model.Ref)
|
||||
expect(SessionV2.Info).toBe(Session.Info)
|
||||
expect(ProjectV2.Current).toBe(Project.Current)
|
||||
expect(ProjectV2.Directory).toBe(Project.Directory)
|
||||
expect(ProjectV2.Directories).toBe(Project.Directories)
|
||||
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
|
||||
expect(CoreSessionMessage.Message).toBe(SessionMessage.Message)
|
||||
expect(CorePrompt).toBe(Prompt)
|
||||
expect(Api.groups["server.session"].identifier).toBe("server.session")
|
||||
expect(Api.groups["server.project"].identifier).toBe("server.project")
|
||||
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
|
||||
expect(Session.ID.create()).toStartWith("ses_")
|
||||
expect(Project.ID.global).toBe("global")
|
||||
@@ -38,8 +43,8 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
|
||||
})
|
||||
|
||||
test("client and Server contracts generate identically", () => {
|
||||
const server = compile(Api, { groupNames, endpointNames, omitEndpoints })
|
||||
const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints })
|
||||
const server = compile(Api, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
|
||||
const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
|
||||
|
||||
expect(emitPromise(client)).toEqual(emitPromise(server))
|
||||
})
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { DateTime, Effect, Stream } from "effect"
|
||||
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||
import { AbsolutePath, Agent, Location, Model, OpenCode, Prompt, Session, SessionMessage } from "../src/effect"
|
||||
import { AbsolutePath, Agent, Event, Location, Model, OpenCode, Prompt, Session, SessionMessage } from "../src/effect"
|
||||
|
||||
test("sessions.get returns the decoded Effect projection", async () => {
|
||||
const caughtUp = { type: "log.caught_up" as const, aggregateID: "ses_test", seq: Event.Seq.make(1) }
|
||||
|
||||
test("session.get returns the decoded Effect projection", async () => {
|
||||
const httpClient = HttpClient.make((request) =>
|
||||
Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(session))),
|
||||
)
|
||||
const result = await Effect.gen(function* () {
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
|
||||
return yield* client.sessions.get({ sessionID: Session.ID.make("ses_test") })
|
||||
return yield* client.session.get({ sessionID: Session.ID.make("ses_test") })
|
||||
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
|
||||
|
||||
expect(DateTime.toEpochMillis(result.time.created)).toBe(1_717_171_717_000)
|
||||
})
|
||||
|
||||
test("events.subscribe exposes and decodes the native Effect event stream", async () => {
|
||||
test("event.subscribe exposes and decodes the native Effect event stream", async () => {
|
||||
const httpClient = HttpClient.make((request) =>
|
||||
Effect.succeed(
|
||||
HttpClientResponse.fromWeb(
|
||||
@@ -30,7 +32,7 @@ test("events.subscribe exposes and decodes the native Effect event stream", asyn
|
||||
)
|
||||
const events = await Effect.gen(function* () {
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
|
||||
return yield* client.events.subscribe().pipe(Stream.runCollect)
|
||||
return yield* client.event.subscribe().pipe(Stream.runCollect)
|
||||
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
|
||||
|
||||
expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "session.next.model.switched"])
|
||||
@@ -40,7 +42,7 @@ test("events.subscribe exposes and decodes the native Effect event stream", asyn
|
||||
expect(durable.durable).toEqual({ aggregateID: "ses_test", seq: 1, version: 1 })
|
||||
})
|
||||
|
||||
test("events.subscribe terminates on Effect protocol decode failures", async () => {
|
||||
test("event.subscribe terminates on Effect protocol decode failures", async () => {
|
||||
const httpClient = HttpClient.make((request) =>
|
||||
Effect.succeed(
|
||||
HttpClientResponse.fromWeb(
|
||||
@@ -53,39 +55,27 @@ test("events.subscribe terminates on Effect protocol decode failures", async ()
|
||||
)
|
||||
const error = await Effect.gen(function* () {
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
|
||||
return yield* client.events.subscribe().pipe(Stream.runCollect, Effect.flip)
|
||||
return yield* client.event.subscribe().pipe(Stream.runCollect, Effect.flip)
|
||||
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
|
||||
|
||||
expect(error._tag).toBe("ClientError")
|
||||
})
|
||||
|
||||
test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
const historyQueries: Array<Record<string, string>> = []
|
||||
let historyPage = 0
|
||||
const logQueries: Array<Record<string, string>> = []
|
||||
const httpClient = HttpClient.make((request) => {
|
||||
const url = request.url
|
||||
if (url.includes("/event")) {
|
||||
if (url.includes("/log")) {
|
||||
logQueries.push(Object.fromEntries(request.urlParams.params))
|
||||
return Effect.succeed(
|
||||
HttpClientResponse.fromWeb(
|
||||
request,
|
||||
new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`, {
|
||||
new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\ndata: ${JSON.stringify(caughtUp)}\n\n`, {
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (url.includes("/history")) {
|
||||
historyPage++
|
||||
historyQueries.push(Object.fromEntries(request.urlParams.params))
|
||||
return Effect.succeed(
|
||||
HttpClientResponse.fromWeb(
|
||||
request,
|
||||
Response.json(
|
||||
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (url.includes("/prompt")) {
|
||||
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(admission)))
|
||||
}
|
||||
@@ -97,7 +87,10 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
}
|
||||
if (url.endsWith("/api/session/active")) {
|
||||
return Effect.succeed(
|
||||
HttpClientResponse.fromWeb(request, Response.json({ data: { ses_test: { type: "running" } } })),
|
||||
HttpClientResponse.fromWeb(
|
||||
request,
|
||||
Response.json({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } }),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (request.method === "POST" && url.endsWith("/api/session")) {
|
||||
@@ -107,54 +100,46 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 204 })))
|
||||
}
|
||||
return Effect.succeed(
|
||||
HttpClientResponse.fromWeb(request, Response.json({ data: [session.data], cursor: { next: "next" } })),
|
||||
HttpClientResponse.fromWeb(
|
||||
request,
|
||||
Response.json({ data: [session.data], watermarks: { ses_test: 3 }, cursor: { next: "next" } }),
|
||||
),
|
||||
)
|
||||
})
|
||||
const result = await Effect.gen(function* () {
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
|
||||
const page = yield* client.sessions.list({ limit: 10 })
|
||||
const active = yield* client.sessions.active()
|
||||
const created = yield* client.sessions.create({
|
||||
const page = yield* client.session.list({ limit: 10 })
|
||||
const active = yield* client.session.active()
|
||||
const created = yield* client.session.create({
|
||||
location: Location.Ref.make({ directory: AbsolutePath.make("/tmp/project") }),
|
||||
})
|
||||
yield* client.sessions.switchAgent({ sessionID: Session.ID.make("ses_test"), agent: Agent.ID.make("build") })
|
||||
yield* client.sessions.switchModel({
|
||||
yield* client.session.switchAgent({ sessionID: Session.ID.make("ses_test"), agent: Agent.ID.make("build") })
|
||||
yield* client.session.switchModel({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
model: Model.Ref.make({ id: "claude", providerID: "anthropic" }),
|
||||
})
|
||||
const admitted = yield* client.sessions.prompt({
|
||||
const admitted = yield* client.session.prompt({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
prompt: Prompt.make({ text: "Hello" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* client.sessions.compact({ sessionID: Session.ID.make("ses_test") })
|
||||
yield* client.sessions.wait({ sessionID: Session.ID.make("ses_test") })
|
||||
const context = yield* client.sessions.context({ sessionID: Session.ID.make("ses_test") })
|
||||
const history = yield* client.sessions.history({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
after: 0,
|
||||
limit: 1,
|
||||
})
|
||||
const historyNext = history.hasMore
|
||||
? yield* client.sessions.history({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
after: history.data.at(-1)?.durable?.seq,
|
||||
limit: 2,
|
||||
})
|
||||
: undefined
|
||||
const events = yield* client.sessions
|
||||
.events({ sessionID: Session.ID.make("ses_test"), after: 0 })
|
||||
yield* client.session.compact({ sessionID: Session.ID.make("ses_test") })
|
||||
yield* client.session.wait({ sessionID: Session.ID.make("ses_test") })
|
||||
const context = yield* client.session.context({ sessionID: Session.ID.make("ses_test") })
|
||||
const log = yield* client.session
|
||||
.log({ sessionID: Session.ID.make("ses_test"), after: Event.Seq.make(0) })
|
||||
.pipe(Stream.runCollect)
|
||||
yield* client.sessions.interrupt({ sessionID: Session.ID.make("ses_test") })
|
||||
const message = yield* client.sessions.message({
|
||||
yield* client.session.interrupt({ sessionID: Session.ID.make("ses_test") })
|
||||
const message = yield* client.session.message({
|
||||
sessionID: Session.ID.make("ses_test"),
|
||||
messageID: SessionMessage.ID.make("msg_model"),
|
||||
})
|
||||
return { page, active, created, admitted, context, history, historyNext, events, message }
|
||||
return { page, active, created, admitted, context, log, message }
|
||||
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
|
||||
|
||||
expect(DateTime.toEpochMillis(result.page.data[0].time.created)).toBe(1_717_171_717_000)
|
||||
expect(result.active).toEqual({ ses_test: { type: "running" } })
|
||||
expect(result.active).toEqual({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
|
||||
expect(result.page.watermarks).toEqual({ ses_test: 3 })
|
||||
expect(Object.getPrototypeOf(result.page.data[0])).toBe(Object.prototype)
|
||||
expect(Object.getPrototypeOf(result.created)).toBe(Object.prototype)
|
||||
expect(result.created.id).toBe("ses_test")
|
||||
@@ -162,16 +147,17 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
expect(Object.getPrototypeOf(result.admitted.prompt)).toBe(Object.prototype)
|
||||
expect(DateTime.toEpochMillis(result.admitted.timeCreated)).toBe(1_717_171_717_000)
|
||||
expect(result.context).toEqual([])
|
||||
expect(DateTime.toEpochMillis(result.history.data[0].data.timestamp)).toBe(1_717_171_717_000)
|
||||
expect(result.history).toEqual(expect.objectContaining({ hasMore: true }))
|
||||
expect(result.historyNext).toEqual({ data: [], hasMore: false })
|
||||
expect(historyQueries[0]).toEqual({ limit: "1", after: "0" })
|
||||
expect(historyQueries[1]).toEqual({ limit: "2", after: "1" })
|
||||
expect(DateTime.toEpochMillis(result.events[0].data.timestamp)).toBe(1_717_171_717_000)
|
||||
expect(logQueries[0]).toEqual({ after: "0" })
|
||||
const logged = Array.from(result.log)
|
||||
expect(logged.map((item) => item.type)).toEqual(["session.next.model.switched", "log.caught_up"])
|
||||
expect(logged[0]?.type === "session.next.model.switched" && DateTime.toEpochMillis(logged[0].data.timestamp)).toBe(
|
||||
1_717_171_717_000,
|
||||
)
|
||||
expect(logged.at(-1)).toEqual(caughtUp)
|
||||
expect(result.message).toEqual(expect.objectContaining({ id: "msg_model", type: "model-switched" }))
|
||||
})
|
||||
|
||||
test("sessions.history retains the typed SessionNotFoundError", async () => {
|
||||
test("session.log retains the typed SessionNotFoundError", async () => {
|
||||
const httpClient = HttpClient.make((request) =>
|
||||
Effect.succeed(
|
||||
HttpClientResponse.fromWeb(
|
||||
@@ -185,11 +171,7 @@ test("sessions.history retains the typed SessionNotFoundError", async () => {
|
||||
)
|
||||
const error = await Effect.gen(function* () {
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
|
||||
return yield* client.sessions
|
||||
.history({
|
||||
sessionID: Session.ID.make("ses_missing"),
|
||||
})
|
||||
.pipe(Effect.flip)
|
||||
return yield* client.session.log({ sessionID: Session.ID.make("ses_missing") }).pipe(Stream.runCollect, Effect.flip)
|
||||
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
|
||||
|
||||
expect(error._tag).toBe("SessionNotFoundError")
|
||||
|
||||
@@ -7,25 +7,30 @@ test("exposes every standard HTTP API group", () => {
|
||||
expect(Object.keys(client)).toEqual([
|
||||
"health",
|
||||
"location",
|
||||
"agents",
|
||||
"sessions",
|
||||
"messages",
|
||||
"models",
|
||||
"providers",
|
||||
"integrations",
|
||||
"credentials",
|
||||
"permissions",
|
||||
"files",
|
||||
"commands",
|
||||
"skills",
|
||||
"events",
|
||||
"ptys",
|
||||
"questions",
|
||||
"references",
|
||||
"projectCopies",
|
||||
"agent",
|
||||
"plugin",
|
||||
"session",
|
||||
"message",
|
||||
"model",
|
||||
"generate",
|
||||
"provider",
|
||||
"integration",
|
||||
"server.mcp",
|
||||
"credential",
|
||||
"project",
|
||||
"permission",
|
||||
"file",
|
||||
"command",
|
||||
"skill",
|
||||
"event",
|
||||
"pty",
|
||||
"shell",
|
||||
"question",
|
||||
"reference",
|
||||
"projectCopy",
|
||||
])
|
||||
expect(Object.keys(client.messages)).toEqual(["list"])
|
||||
expect(Object.keys(client.integrations)).toEqual([
|
||||
expect(Object.keys(client.message)).toEqual(["list"])
|
||||
expect(Object.keys(client.integration)).toEqual([
|
||||
"list",
|
||||
"get",
|
||||
"connectKey",
|
||||
@@ -34,11 +39,95 @@ test("exposes every standard HTTP API group", () => {
|
||||
"attemptComplete",
|
||||
"attemptCancel",
|
||||
])
|
||||
expect(Object.keys(client.files)).toEqual(["list", "find"])
|
||||
expect(Object.keys(client.ptys)).toEqual(["list", "create", "get", "update", "remove"])
|
||||
expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
|
||||
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
|
||||
expect(Object.keys(client.shell)).toEqual(["list", "create", "get", "output", "remove"])
|
||||
expect(Object.keys(client.project)).toEqual(["current", "directories"])
|
||||
})
|
||||
|
||||
test("sessions.get returns the wire projection", async () => {
|
||||
test("file.read returns binary content from the public HTTP contract", async () => {
|
||||
let request: Request | undefined
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input) => {
|
||||
request = input instanceof Request ? input : new Request(input)
|
||||
return new Response(new Uint8Array([104, 105]))
|
||||
},
|
||||
})
|
||||
|
||||
const content = await client.file.read({
|
||||
path: "src/a b#c.ts",
|
||||
location: { directory: "/tmp/project" },
|
||||
})
|
||||
|
||||
expect(Array.from(content)).toEqual([104, 105])
|
||||
expect(request?.url).toBe(
|
||||
"http://localhost:3000/api/fs/read/src/a%20b%23c.ts?location%5Bdirectory%5D=%2Ftmp%2Fproject",
|
||||
)
|
||||
})
|
||||
|
||||
test("project methods use the public HTTP contract", async () => {
|
||||
const requests: string[] = []
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
|
||||
requests.push(url)
|
||||
if (url.includes("/directories")) return Response.json([])
|
||||
return Response.json({ id: "proj_test", directory: "/tmp/project" })
|
||||
},
|
||||
})
|
||||
|
||||
const current = await client.project.current({ location: { workspace: "wrk_test" } })
|
||||
const directories = await client.project.directories({
|
||||
projectID: current.id,
|
||||
location: { directory: current.directory },
|
||||
})
|
||||
|
||||
expect(current).toEqual({ id: "proj_test", directory: "/tmp/project" })
|
||||
expect(directories).toEqual([])
|
||||
expect(requests).toEqual([
|
||||
"http://localhost:3000/api/project/current?location%5Bworkspace%5D=wrk_test",
|
||||
"http://localhost:3000/api/project/proj_test/directories?location%5Bdirectory%5D=%2Ftmp%2Fproject",
|
||||
])
|
||||
})
|
||||
|
||||
test("shell list and remove use the public HTTP contract", async () => {
|
||||
const requests: Array<{ method: string; url: string }> = []
|
||||
const shell = {
|
||||
id: "sh_test",
|
||||
status: "running",
|
||||
command: "pwd",
|
||||
cwd: "/tmp/project",
|
||||
shell: "/bin/zsh",
|
||||
file: "/tmp/opencode-shell",
|
||||
metadata: { sessionID: "ses_test" },
|
||||
time: { started: 1_717_171_717_000 },
|
||||
}
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input, init) => {
|
||||
const request = input instanceof Request ? input : new Request(input, init)
|
||||
requests.push({ method: request.method, url: request.url })
|
||||
if (request.method === "DELETE") return new Response(null, { status: 204 })
|
||||
return Response.json({
|
||||
location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
|
||||
data: [shell],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const result = await client.shell.list({ location: { directory: "/tmp/project" } })
|
||||
await client.shell.remove({ id: shell.id })
|
||||
|
||||
expect(result.data).toEqual([shell])
|
||||
expect(requests).toEqual([
|
||||
{ method: "GET", url: "http://localhost:3000/api/shell?location%5Bdirectory%5D=%2Ftmp%2Fproject" },
|
||||
{ method: "DELETE", url: "http://localhost:3000/api/shell/sh_test" },
|
||||
])
|
||||
})
|
||||
|
||||
test("session.get returns the wire projection", async () => {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input) => {
|
||||
@@ -49,12 +138,12 @@ test("sessions.get returns the wire projection", async () => {
|
||||
},
|
||||
})
|
||||
|
||||
const result = await client.sessions.get({ sessionID: "ses_test" })
|
||||
const result = await client.session.get({ sessionID: "ses_test" })
|
||||
|
||||
expect(result.time.created).toBe(1_717_171_717_000)
|
||||
})
|
||||
|
||||
test("events.subscribe exposes the Promise event stream wire projection", async () => {
|
||||
test("event.subscribe exposes the Promise event stream wire projection", async () => {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async () =>
|
||||
@@ -65,19 +154,19 @@ test("events.subscribe exposes the Promise event stream wire projection", async
|
||||
),
|
||||
})
|
||||
const events = []
|
||||
for await (const event of client.events.subscribe()) events.push(event)
|
||||
for await (const event of client.event.subscribe()) events.push(event)
|
||||
|
||||
expect(events).toEqual([{ id: "evt_connected", type: "server.connected", data: {} }, modelSwitchedEvent])
|
||||
expect(events[1]?.type === "session.next.model.switched" && events[1].data.timestamp).toBe(1_717_171_717_000)
|
||||
})
|
||||
|
||||
test("events.subscribe terminates on malformed Promise SSE data", async () => {
|
||||
test("event.subscribe terminates on malformed Promise SSE data", async () => {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async () => new Response("data: {not-json}\n\n", { headers: { "content-type": "text/event-stream" } }),
|
||||
})
|
||||
|
||||
await expect(client.events.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
|
||||
await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
|
||||
name: "ClientError",
|
||||
reason: "MalformedResponse",
|
||||
})
|
||||
@@ -85,7 +174,6 @@ test("events.subscribe terminates on malformed Promise SSE data", async () => {
|
||||
|
||||
test("session methods use the public HTTP contract", async () => {
|
||||
const requests: Array<{ url: string; init?: RequestInit }> = []
|
||||
let historyPage = 0
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input, init) => {
|
||||
@@ -96,56 +184,49 @@ test("session methods use the public HTTP contract", async () => {
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
})
|
||||
}
|
||||
if (url.includes("/history")) {
|
||||
historyPage++
|
||||
return Response.json(
|
||||
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
|
||||
)
|
||||
if (url.includes("/log")) {
|
||||
return new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\ndata: ${JSON.stringify(caughtUp)}\n\n`, {
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
})
|
||||
}
|
||||
if (url.includes("/prompt")) return Response.json(admission)
|
||||
if (url.includes("/context")) return Response.json({ data: [] })
|
||||
if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
|
||||
if (url.endsWith("/api/session/active")) return Response.json({ data: { ses_test: { type: "running" } } })
|
||||
if (url.endsWith("/api/session/active"))
|
||||
return Response.json({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
|
||||
if (init?.method === "POST" && url.endsWith("/api/session")) return Response.json(session)
|
||||
if (init?.method === "POST") return new Response(null, { status: 204 })
|
||||
return Response.json({ data: [session.data], cursor: { next: "next" } })
|
||||
},
|
||||
})
|
||||
|
||||
const page = await client.sessions.list({ limit: 10, order: "desc" })
|
||||
const active = await client.sessions.active()
|
||||
const created = await client.sessions.create({ location: { directory: "/tmp/project" } })
|
||||
await client.sessions.switchAgent({ sessionID: "ses_test", agent: "build" })
|
||||
await client.sessions.switchModel({
|
||||
const page = await client.session.list({ limit: 10, order: "desc" })
|
||||
const active = await client.session.active()
|
||||
const created = await client.session.create({ location: { directory: "/tmp/project" } })
|
||||
await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
|
||||
await client.session.switchModel({
|
||||
sessionID: "ses_test",
|
||||
model: { id: "claude", providerID: "anthropic" },
|
||||
})
|
||||
const admitted = await client.sessions.prompt({
|
||||
const admitted = await client.session.prompt({
|
||||
sessionID: "ses_test",
|
||||
prompt: { text: "Hello" },
|
||||
resume: false,
|
||||
})
|
||||
await client.sessions.compact({ sessionID: "ses_test" })
|
||||
await client.sessions.wait({ sessionID: "ses_test" })
|
||||
const context = await client.sessions.context({ sessionID: "ses_test" })
|
||||
const history = await client.sessions.history({ sessionID: "ses_test", after: 0, limit: 1 })
|
||||
const historyAfter = history.data.at(-1)?.durable?.seq
|
||||
const historyNext = history.hasMore
|
||||
? await client.sessions.history({ sessionID: "ses_test", after: historyAfter, limit: 2 })
|
||||
: undefined
|
||||
const events = []
|
||||
for await (const event of client.sessions.events({ sessionID: "ses_test", after: 0 })) events.push(event)
|
||||
await client.sessions.interrupt({ sessionID: "ses_test" })
|
||||
const message = await client.sessions.message({ sessionID: "ses_test", messageID: "msg_model" })
|
||||
await client.session.compact({ sessionID: "ses_test" })
|
||||
await client.session.wait({ sessionID: "ses_test" })
|
||||
const context = await client.session.context({ sessionID: "ses_test" })
|
||||
const log = []
|
||||
for await (const item of client.session.log({ sessionID: "ses_test", after: 0 })) log.push(item)
|
||||
await client.session.interrupt({ sessionID: "ses_test" })
|
||||
const message = await client.session.message({ sessionID: "ses_test", messageID: "msg_model" })
|
||||
|
||||
expect(page.cursor.next).toBe("next")
|
||||
expect(active).toEqual({ ses_test: { type: "running" } })
|
||||
expect(active).toEqual({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
|
||||
expect(created.id).toBe("ses_test")
|
||||
expect(admitted.id).toBe("msg_test")
|
||||
expect(context).toEqual([])
|
||||
expect(history).toEqual({ data: [modelSwitchedEvent], hasMore: true })
|
||||
expect(historyNext).toEqual({ data: [], hasMore: false })
|
||||
expect(events).toEqual([modelSwitchedEvent])
|
||||
expect(log).toEqual([modelSwitchedEvent, caughtUp])
|
||||
expect(message).toEqual(modelSwitchedMessage)
|
||||
expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
|
||||
["GET", "http://localhost:3000/api/session?limit=10&order=desc"],
|
||||
@@ -157,9 +238,7 @@ test("session methods use the public HTTP contract", async () => {
|
||||
["POST", "http://localhost:3000/api/session/ses_test/compact"],
|
||||
["POST", "http://localhost:3000/api/session/ses_test/wait"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/context"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/history?limit=1&after=0"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/history?limit=2&after=1"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/event?after=0"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/log?after=0"],
|
||||
["POST", "http://localhost:3000/api/session/ses_test/interrupt"],
|
||||
["GET", "http://localhost:3000/api/session/ses_test/message/msg_model"],
|
||||
])
|
||||
@@ -179,14 +258,14 @@ test("middleware errors remain declared client errors", async () => {
|
||||
})
|
||||
|
||||
try {
|
||||
await client.sessions.create({})
|
||||
await client.session.create({})
|
||||
throw new Error("Expected request to fail")
|
||||
} catch (error) {
|
||||
expect(isUnauthorizedError(error)).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
test("sessions.history decodes SessionNotFoundError", async () => {
|
||||
test("session.log decodes SessionNotFoundError", async () => {
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async () =>
|
||||
@@ -197,7 +276,7 @@ test("sessions.history decodes SessionNotFoundError", async () => {
|
||||
})
|
||||
|
||||
try {
|
||||
await client.sessions.history({ sessionID: "ses_missing" })
|
||||
await client.session.log({ sessionID: "ses_missing" })[Symbol.asyncIterator]().next()
|
||||
throw new Error("Expected request to fail")
|
||||
} catch (error) {
|
||||
expect(isSessionNotFoundError(error)).toBe(true)
|
||||
@@ -242,6 +321,8 @@ const modelSwitchedMessage = {
|
||||
model: { id: "claude", providerID: "anthropic" },
|
||||
}
|
||||
|
||||
const caughtUp = { type: "log.caught_up", aggregateID: "ses_test", seq: 1 }
|
||||
|
||||
const modelSwitchedEvent = {
|
||||
id: "evt_model",
|
||||
type: "session.next.model.switched",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -248,7 +248,6 @@ export const dict = {
|
||||
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
|
||||
|
||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||
"go.banner.text": "MiniMax M3: حد استخدام أكبر 3 مرات لفترة محدودة",
|
||||
"go.meta.description":
|
||||
"يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.2 وGLM-5.1 وKimi K2.7 Code وKimi K2.6 وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.7 Max وQwen3.7 Plus وQwen3.6 Plus وMiniMax M2.7 وMiniMax M3 وDeepSeek V4 Pro وDeepSeek V4 Flash.",
|
||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||
@@ -369,6 +368,8 @@ export const dict = {
|
||||
"zen.api.error.userMonthlyLimitReached":
|
||||
"لقد وصلت إلى حد الإنفاق الشهري البالغ ${{amount}}. إدارة حدودك هنا: {{membersUrl}}",
|
||||
"zen.api.error.modelDisabled": "النموذج معطل",
|
||||
"zen.api.error.regionNotAllowed":
|
||||
"هذا النموذج مستضاف في الصين. إذا كنت ترغب في استخدام هذا النموذج، فعّله في إعداداتك: {{consoleGoUrl}}",
|
||||
"zen.api.error.trialEnded":
|
||||
"انتهى العرض المجاني لـ {{model}}. يمكنك مواصلة استخدام النموذج بالاشتراك في OpenCode Go - {{link}}",
|
||||
|
||||
@@ -646,6 +647,9 @@ export const dict = {
|
||||
"workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
|
||||
"workspace.lite.subscription.selectProvider":
|
||||
'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.',
|
||||
"workspace.lite.providers.title": "المزودون",
|
||||
"workspace.lite.providers.description": "تحكم في المزودين المستخدمين للتوجيه.",
|
||||
"workspace.lite.providers.useChina": "تفعيل النماذج المستضافة في الصين",
|
||||
"workspace.lite.black.message":
|
||||
"أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.",
|
||||
"workspace.lite.other.message":
|
||||
|
||||
@@ -252,7 +252,6 @@ export const dict = {
|
||||
"zen.privacy.exceptionsLink": "seguintes exceções",
|
||||
|
||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||
"go.banner.text": "MiniMax M3 tem limite de uso 3x maior por tempo limitado",
|
||||
"go.meta.description":
|
||||
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro e DeepSeek V4 Flash.",
|
||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||
@@ -377,6 +376,8 @@ export const dict = {
|
||||
"zen.api.error.userMonthlyLimitReached":
|
||||
"Você atingiu seu limite de gastos mensais de ${{amount}}. Gerencie seus limites aqui: {{membersUrl}}",
|
||||
"zen.api.error.modelDisabled": "O modelo está desabilitado",
|
||||
"zen.api.error.regionNotAllowed":
|
||||
"Este modelo está hospedado na China. Se você quiser usar este modelo, ative-o nas suas configurações: {{consoleGoUrl}}",
|
||||
"zen.api.error.trialEnded":
|
||||
"A promoção gratuita do {{model}} terminou. Você pode continuar usando o modelo assinando o OpenCode Go - {{link}}",
|
||||
|
||||
@@ -656,6 +657,9 @@ export const dict = {
|
||||
"workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
|
||||
"workspace.lite.subscription.selectProvider":
|
||||
'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.',
|
||||
"workspace.lite.providers.title": "Provedores",
|
||||
"workspace.lite.providers.description": "Controle quais provedores são usados para roteamento.",
|
||||
"workspace.lite.providers.useChina": "Ativar modelos hospedados na China",
|
||||
"workspace.lite.black.message":
|
||||
"Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.",
|
||||
"workspace.lite.other.message":
|
||||
|
||||
@@ -250,7 +250,6 @@ export const dict = {
|
||||
"zen.privacy.exceptionsLink": "følgende undtagelser",
|
||||
|
||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||
"go.banner.text": "MiniMax M3 får tredoblet brugsgrænse i en begrænset periode",
|
||||
"go.meta.description":
|
||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.2, GLM-5.1, Kimi K2.7 Code, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus, MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro og DeepSeek V4 Flash.",
|
||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||
@@ -373,6 +372,8 @@ export const dict = {
|
||||
"zen.api.error.userMonthlyLimitReached":
|
||||
"Du har nået din månedlige forbrugsgrænse på ${{amount}}. Administrer dine grænser her: {{membersUrl}}",
|
||||
"zen.api.error.modelDisabled": "Modellen er deaktiveret",
|
||||
"zen.api.error.regionNotAllowed":
|
||||
"Denne model hostes i Kina. Hvis du vil bruge denne model, skal du aktivere den i dine indstillinger: {{consoleGoUrl}}",
|
||||
"zen.api.error.trialEnded":
|
||||
"Den gratis kampagne for {{model}} er afsluttet. Du kan fortsætte med at bruge modellen ved at abonnere på OpenCode Go - {{link}}",
|
||||
|
||||
@@ -652,6 +653,9 @@ export const dict = {
|
||||
"workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
|
||||
"workspace.lite.subscription.selectProvider":
|
||||
'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.',
|
||||
"workspace.lite.providers.title": "Udbydere",
|
||||
"workspace.lite.providers.description": "Styr, hvilke udbydere der bruges til routing.",
|
||||
"workspace.lite.providers.useChina": "Aktivér modeller hostet i Kina",
|
||||
"workspace.lite.black.message":
|
||||
"Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.",
|
||||
"workspace.lite.other.message":
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user