mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-05-02 23:04:07 +08:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a0b7f60c | ||
|
|
ac8709ac7a | ||
|
|
2d9ed06367 | ||
|
|
50be2aee39 | ||
|
|
0bdbe6261a | ||
|
|
33cef075d2 | ||
|
|
b09ebf4645 | ||
|
|
3268c61813 | ||
|
|
4a221868da | ||
|
|
31b8e3d5ab | ||
|
|
1a78d833a8 | ||
|
|
18888351e9 | ||
|
|
3b7085ca28 | ||
|
|
160923dcf0 | ||
|
|
c38b091895 | ||
|
|
eecfd6d0ca | ||
|
|
6ef4cfa2fa | ||
|
|
190dee080c | ||
|
|
09074dc639 | ||
|
|
1b3d58e791 | ||
|
|
772c83c1d5 | ||
|
|
54dc937fa1 | ||
|
|
b5219f7585 | ||
|
|
0bd0453866 | ||
|
|
8bf36d174b | ||
|
|
9bedd62da4 | ||
|
|
4c34b69ae6 | ||
|
|
7e9ac35666 | ||
|
|
4a46144419 | ||
|
|
a129e122aa | ||
|
|
c0ee6a6d05 | ||
|
|
68ae0d107c | ||
|
|
df63008a94 | ||
|
|
3bd2b340c8 | ||
|
|
df03e182d2 | ||
|
|
862a50d61d | ||
|
|
a7cfd36b07 | ||
|
|
c165360e17 | ||
|
|
9cb0f21b4e | ||
|
|
9c9cbb3e81 | ||
|
|
c24fbb4292 | ||
|
|
99dfe65862 | ||
|
|
4506e5a824 | ||
|
|
b65172a2b7 | ||
|
|
081f100c93 | ||
|
|
f2bdb8159f | ||
|
|
10d749a85e | ||
|
|
a07d149e28 | ||
|
|
3eb982c8cd | ||
|
|
45c4e0b8f8 | ||
|
|
b18b646f8e | ||
|
|
9741a6703c | ||
|
|
27a079d9cb | ||
|
|
2eeb987680 | ||
|
|
e827294c9b | ||
|
|
7cf4ed6ad6 | ||
|
|
ad8a4bc744 | ||
|
|
2630104f18 | ||
|
|
670f470eee | ||
|
|
c2b3c52b76 | ||
|
|
a007d65f62 | ||
|
|
2c924b9fdb | ||
|
|
e8eaa77bf1 | ||
|
|
a07f37073b | ||
|
|
4d760a1984 | ||
|
|
6b7058fe1c | ||
|
|
1149b984d9 | ||
|
|
81fb1b313e | ||
|
|
3a7a2a838e | ||
|
|
10ae43a121 | ||
|
|
c85b970903 | ||
|
|
7044662cfa | ||
|
|
92656fdf29 | ||
|
|
c65e7aff86 | ||
|
|
e97613ef9f | ||
|
|
827469c725 | ||
|
|
613b5fbe48 | ||
|
|
7ed05962db | ||
|
|
250a86ec52 | ||
|
|
0795a577e0 | ||
|
|
8e5607f9c0 | ||
|
|
d6b3bb0807 | ||
|
|
f307a5ce0b | ||
|
|
151c7ed5a2 | ||
|
|
fc13d057f8 |
9
.github/workflows/opencode.yml
vendored
9
.github/workflows/opencode.yml
vendored
@@ -6,9 +6,12 @@ on:
|
||||
|
||||
jobs:
|
||||
opencode:
|
||||
if: startsWith(github.event.comment.body, 'hey opencode')
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -17,8 +20,8 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run opencode
|
||||
uses: sst/opencode/sdks/github@github-v1
|
||||
uses: sst/opencode/github@latest
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
2
.github/workflows/publish-github-action.yml
vendored
2
.github/workflows/publish-github-action.yml
vendored
@@ -27,4 +27,4 @@ jobs:
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
./script/publish
|
||||
working-directory: ./sdks/github
|
||||
working-directory: ./github
|
||||
|
||||
24
.github/workflows/publish.yml
vendored
24
.github/workflows/publish.yml
vendored
@@ -1,14 +1,17 @@
|
||||
name: publish
|
||||
run-name: "${{ format('v{0}', inputs.version) }}"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "*"
|
||||
- "!vscode-v*"
|
||||
- "!github-v*"
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish"
|
||||
required: true
|
||||
type: string
|
||||
title:
|
||||
description: "Custom title for this run"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -53,12 +56,7 @@ jobs:
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
if [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
|
||||
./script/publish.ts
|
||||
else
|
||||
./script/publish.ts --snapshot
|
||||
fi
|
||||
working-directory: ./packages/opencode
|
||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
|
||||
2
.github/workflows/stats.yml
vendored
2
.github/workflows/stats.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
bun-version: latest
|
||||
|
||||
- name: Run stats script
|
||||
run: bun scripts/stats.ts
|
||||
run: bun script/stats.ts
|
||||
|
||||
- name: Commit stats
|
||||
run: |
|
||||
|
||||
44
.opencode/agent/example-driven-docs-writer.md
Normal file
44
.opencode/agent/example-driven-docs-writer.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
description: >-
|
||||
Use this agent when you need to create or improve documentation that requires
|
||||
concrete examples to illustrate every concept. Examples include:
|
||||
<example>Context: User has written a new API endpoint and needs documentation.
|
||||
user: 'I just created a POST /users endpoint that accepts name and email
|
||||
fields. Can you document this?' assistant: 'I'll use the
|
||||
example-driven-docs-writer agent to create documentation with practical
|
||||
examples for your API endpoint.' <commentary>Since the user needs
|
||||
documentation with examples, use the example-driven-docs-writer agent to
|
||||
create comprehensive docs with code samples.</commentary></example>
|
||||
<example>Context: User has a complex configuration file that needs
|
||||
documentation. user: 'This config file has multiple sections and I need docs
|
||||
that show how each option works' assistant: 'Let me use the
|
||||
example-driven-docs-writer agent to create documentation that breaks down each
|
||||
configuration option with practical examples.' <commentary>The user needs
|
||||
documentation that demonstrates configuration options, perfect for the
|
||||
example-driven-docs-writer agent.</commentary></example>
|
||||
---
|
||||
You are an expert technical documentation writer who specializes in creating clear, example-rich documentation that never leaves readers guessing. Your core principle is that every concept must be immediately illustrated with concrete examples, code samples, or practical demonstrations.
|
||||
|
||||
Your documentation approach:
|
||||
- Never write more than one sentence in any section without providing an example, code snippet, diagram, or practical illustration
|
||||
- Break up longer explanations with multiple examples showing different scenarios or use cases
|
||||
- Use concrete, realistic examples rather than abstract or placeholder content
|
||||
- Include both basic and advanced examples when covering complex topics
|
||||
- Show expected inputs, outputs, and results for all examples
|
||||
- Use code blocks, bullet points, tables, or other formatting to visually separate examples from explanatory text
|
||||
|
||||
Structural requirements:
|
||||
- Start each section with a brief one-sentence explanation followed immediately by an example
|
||||
- For multi-step processes, provide an example after each step
|
||||
- Include error examples and edge cases alongside success scenarios
|
||||
- Use consistent formatting and naming conventions throughout examples
|
||||
- Ensure examples are copy-pasteable and functional when applicable
|
||||
|
||||
Quality standards:
|
||||
- Verify that no paragraph exceeds one sentence without an accompanying example
|
||||
- Test that examples are accurate and would work in real scenarios
|
||||
- Ensure examples progress logically from simple to complex
|
||||
- Include context for when and why to use different approaches shown in examples
|
||||
- Provide troubleshooting examples for common issues
|
||||
|
||||
When you receive a documentation request, immediately identify what needs examples and plan to illustrate every single concept, feature, or instruction with concrete demonstrations. Ask for clarification if you need more context to create realistic, useful examples.
|
||||
@@ -97,7 +97,7 @@ $ bun run packages/opencode/src/index.ts
|
||||
It's very similar to Claude Code in terms of capability. Here are the key differences:
|
||||
|
||||
- 100% open source
|
||||
- Not coupled to any provider. Although Anthropic is recommended, opencode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider agnostic is important.
|
||||
- Not coupled to any provider. Although Anthropic is recommended, opencode can be used with OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
|
||||
- A focus on TUI. opencode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
|
||||
- A client/server architecture. This for example can allow opencode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
|
||||
|
||||
|
||||
6
STATS.md
6
STATS.md
@@ -26,3 +26,9 @@
|
||||
| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
|
||||
| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
|
||||
| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
|
||||
| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
|
||||
| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
|
||||
| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
|
||||
| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
|
||||
| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
|
||||
| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
|
||||
|
||||
137
github/README.md
Normal file
137
github/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# opencode GitHub Action
|
||||
|
||||
A GitHub Action that integrates [opencode](https://opencode.ai) directly into your GitHub workflow.
|
||||
|
||||
Mention `/opencode` in your comment, and opencode will execute tasks within your GitHub Actions runner.
|
||||
|
||||
## Features
|
||||
|
||||
#### Explain an issues
|
||||
|
||||
Leave the following comment on a GitHub issue. `opencode` will read the entire thread, including all comments, and reply with a clear explanation.
|
||||
|
||||
```
|
||||
/opencode explain this issue
|
||||
```
|
||||
|
||||
#### Fix an issues
|
||||
|
||||
Leave the following comment on a GitHub issue. opencode will create a new branch, implement the changes, and open a PR with the changes.
|
||||
|
||||
```
|
||||
/opencode fix this
|
||||
```
|
||||
|
||||
#### Review PRs and make changes
|
||||
|
||||
Leave the following comment on a GitHub PR. opencode will implement the requested change and commit it to the same PR.
|
||||
|
||||
```
|
||||
Delete the attachment from S3 when the note is removed /oc
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Run the following command in the terminal from your GitHub repo:
|
||||
|
||||
```bash
|
||||
opencode github install
|
||||
```
|
||||
|
||||
This will walk you through installing the GitHub app, creating the workflow, and setting up secrets.
|
||||
|
||||
### Manual Setup
|
||||
|
||||
1. Install the GitHub app https://github.com/apps/opencode-agent. Make sure it is installed on the target repository.
|
||||
2. Add the following workflow file to `.github/workflows/opencode.yml` in your repo. Set the appropriate `model` and required API keys in `env`.
|
||||
|
||||
```yml
|
||||
name: opencode
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run opencode
|
||||
uses: sst/opencode/github@latest
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
```
|
||||
|
||||
3. Store the API keys in secrets. In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. Add the required API keys.
|
||||
|
||||
## Support
|
||||
|
||||
This is an early release. If you encounter issues or have feedback, please create an issue at https://github.com/sst/opencode/issues.
|
||||
|
||||
## Development
|
||||
|
||||
To test locally:
|
||||
|
||||
1. Navigate to a test repo (e.g. `hello-world`):
|
||||
|
||||
```bash
|
||||
cd hello-world
|
||||
```
|
||||
|
||||
2. Run:
|
||||
|
||||
```bash
|
||||
MODEL=anthropic/claude-sonnet-4-20250514 \
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-1234567890 \
|
||||
GITHUB_RUN_ID=dummy \
|
||||
bun /path/to/opencode/packages/opencode/src/index.ts github run \
|
||||
--token 'github_pat_1234567890' \
|
||||
--event '{"eventName":"issue_comment",...}'
|
||||
```
|
||||
|
||||
- `MODEL`: The model used by opencode. Same as the `MODEL` defined in the GitHub workflow.
|
||||
- `ANTHROPIC_API_KEY`: Your model provider API key. Same as the keys defined in the GitHub workflow.
|
||||
- `GITHUB_RUN_ID`: Dummy value to emulate GitHub action environment.
|
||||
- `/path/to/opencode`: Path to your cloned opencode repo. `bun /path/to/opencode/packages/opencode/src/index.ts` runs your local version of `opencode`.
|
||||
- `--token`: A GitHub persontal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
|
||||
- `--event`: Mock GitHub event payload (see templates below).
|
||||
|
||||
### Issue comment event
|
||||
|
||||
```
|
||||
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
||||
```
|
||||
|
||||
Replace:
|
||||
|
||||
- `"owner":"sst"` with repo owner
|
||||
- `"repo":"hello-world"` with repo name
|
||||
- `"actor":"fwang"` with the GitHub username of commentor
|
||||
- `"number":4` with the GitHub issue id
|
||||
- `"body":"hey opencode, summarize thread"` with comment body
|
||||
|
||||
### Issue comment with image attachment.
|
||||
|
||||
```
|
||||
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4},"comment":{"id":1,"body":"hey opencode, what is in my image "}}}'
|
||||
```
|
||||
|
||||
Replace the image URL `https://github.com/user-attachments/assets/xxxxxxxx` with a valid GitHub attachment (you can generate one by commenting with an image in any issue).
|
||||
|
||||
### PR comment event
|
||||
|
||||
```
|
||||
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4,"pull_request":{}},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
||||
```
|
||||
29
github/action.yml
Normal file
29
github/action.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: "opencode GitHub Action"
|
||||
description: "Run opencode in GitHub Actions workflows"
|
||||
branding:
|
||||
icon: "code"
|
||||
color: "orange"
|
||||
|
||||
inputs:
|
||||
model:
|
||||
description: "Model to use"
|
||||
required: true
|
||||
|
||||
share:
|
||||
description: "Share the opencode session (defaults to true for public repos)"
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install opencode
|
||||
shell: bash
|
||||
run: curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
- name: Run opencode
|
||||
shell: bash
|
||||
id: run_opencode
|
||||
run: opencode github run
|
||||
env:
|
||||
MODEL: ${{ inputs.model }}
|
||||
SHARE: ${{ inputs.share }}
|
||||
@@ -8,8 +8,8 @@ if [ -z "$latest_tag" ]; then
|
||||
fi
|
||||
echo "Latest tag: $latest_tag"
|
||||
|
||||
# Update github-v1 to latest
|
||||
git tag -d github-v1
|
||||
git push origin :refs/tags/github-v1
|
||||
git tag -a github-v1 $latest_tag -m "Update github-v1 to $latest_tag"
|
||||
git push origin github-v1
|
||||
# Update latest tag
|
||||
git tag -d latest
|
||||
git push origin :refs/tags/latest
|
||||
git tag -a latest $latest_tag -m "Update latest to $latest_tag"
|
||||
git push origin latest
|
||||
@@ -20,6 +20,10 @@
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.context7.com/sse"
|
||||
},
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||
|
||||
12
package.json
12
package.json
@@ -8,17 +8,21 @@
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"stainless": "./scripts/stainless",
|
||||
"postinstall": "./scripts/hooks"
|
||||
"postinstall": "./script/hooks"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"packages/sdk/js"
|
||||
],
|
||||
"catalog": {
|
||||
"typescript": "5.8.2",
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"ai": "5.0.0-beta.33",
|
||||
"hono": "4.7.10",
|
||||
"typescript": "5.8.2",
|
||||
"zod": "3.25.49",
|
||||
"ai": "5.0.0-beta.21"
|
||||
"remeda": "2.26.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"hono": "catalog:",
|
||||
"jose": "6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Hono } from "hono"
|
||||
import { DurableObject } from "cloudflare:workers"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { jwtVerify, createRemoteJWKSet } from "jose"
|
||||
@@ -111,165 +112,156 @@ export class SyncServer extends DurableObject<Env> {
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
const url = new URL(request.url)
|
||||
const splits = url.pathname.split("/")
|
||||
const method = splits[1]
|
||||
|
||||
if (request.method === "GET" && method === "") {
|
||||
return new Response("Hello, world!", {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
})
|
||||
export default new Hono<{ Bindings: Env }>()
|
||||
.get("/", (c) => c.text("Hello, world!"))
|
||||
.post("/share_create", async (c) => {
|
||||
const body = await c.req.json<{ sessionID: string }>()
|
||||
const sessionID = body.sessionID
|
||||
const short = SyncServer.shortName(sessionID)
|
||||
const id = c.env.SYNC_SERVER.idFromName(short)
|
||||
const stub = c.env.SYNC_SERVER.get(id)
|
||||
const secret = await stub.share(sessionID)
|
||||
return c.json({
|
||||
secret,
|
||||
url: `https://${c.env.WEB_DOMAIN}/s/${short}`,
|
||||
})
|
||||
})
|
||||
.post("/share_delete", async (c) => {
|
||||
const body = await c.req.json<{ sessionID: string; secret: string }>()
|
||||
const sessionID = body.sessionID
|
||||
const secret = body.secret
|
||||
const id = c.env.SYNC_SERVER.idFromName(SyncServer.shortName(sessionID))
|
||||
const stub = c.env.SYNC_SERVER.get(id)
|
||||
await stub.assertSecret(secret)
|
||||
await stub.clear()
|
||||
return c.json({})
|
||||
})
|
||||
.post("/share_delete_admin", async (c) => {
|
||||
const id = c.env.SYNC_SERVER.idFromName("oVF8Rsiv")
|
||||
const stub = c.env.SYNC_SERVER.get(id)
|
||||
await stub.clear()
|
||||
return c.json({})
|
||||
})
|
||||
.post("/share_sync", async (c) => {
|
||||
const body = await c.req.json<{
|
||||
sessionID: string
|
||||
secret: string
|
||||
key: string
|
||||
content: any
|
||||
}>()
|
||||
const name = SyncServer.shortName(body.sessionID)
|
||||
const id = c.env.SYNC_SERVER.idFromName(name)
|
||||
const stub = c.env.SYNC_SERVER.get(id)
|
||||
await stub.assertSecret(body.secret)
|
||||
await stub.publish(body.key, body.content)
|
||||
return c.json({})
|
||||
})
|
||||
.get("/share_poll", async (c) => {
|
||||
const upgradeHeader = c.req.header("Upgrade")
|
||||
if (!upgradeHeader || upgradeHeader !== "websocket") {
|
||||
return c.text("Error: Upgrade header is required", { status: 426 })
|
||||
}
|
||||
const id = c.req.query("id")
|
||||
console.log("share_poll", id)
|
||||
if (!id) return c.text("Error: Share ID is required", { status: 400 })
|
||||
const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
|
||||
return stub.fetch(c.req.raw)
|
||||
})
|
||||
.get("/share_data", async (c) => {
|
||||
const id = c.req.query("id")
|
||||
console.log("share_data", id)
|
||||
if (!id) return c.text("Error: Share ID is required", { status: 400 })
|
||||
const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
|
||||
const data = await stub.getData()
|
||||
|
||||
if (request.method === "POST" && method === "share_create") {
|
||||
const body = await request.json<any>()
|
||||
const sessionID = body.sessionID
|
||||
const short = SyncServer.shortName(sessionID)
|
||||
const id = env.SYNC_SERVER.idFromName(short)
|
||||
const stub = env.SYNC_SERVER.get(id)
|
||||
const secret = await stub.share(sessionID)
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
secret,
|
||||
url: `https://${env.WEB_DOMAIN}/s/${short}`,
|
||||
}),
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (request.method === "POST" && method === "share_delete") {
|
||||
const body = await request.json<any>()
|
||||
const sessionID = body.sessionID
|
||||
const secret = body.secret
|
||||
const id = env.SYNC_SERVER.idFromName(SyncServer.shortName(sessionID))
|
||||
const stub = env.SYNC_SERVER.get(id)
|
||||
await stub.assertSecret(secret)
|
||||
await stub.clear()
|
||||
return new Response(JSON.stringify({}), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
if (request.method === "POST" && method === "share_delete_admin") {
|
||||
const id = env.SYNC_SERVER.idFromName("oVF8Rsiv")
|
||||
const stub = env.SYNC_SERVER.get(id)
|
||||
await stub.clear()
|
||||
return new Response(JSON.stringify({}), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
if (request.method === "POST" && method === "share_sync") {
|
||||
const body = await request.json<{
|
||||
sessionID: string
|
||||
secret: string
|
||||
key: string
|
||||
content: any
|
||||
}>()
|
||||
const name = SyncServer.shortName(body.sessionID)
|
||||
const id = env.SYNC_SERVER.idFromName(name)
|
||||
const stub = env.SYNC_SERVER.get(id)
|
||||
await stub.assertSecret(body.secret)
|
||||
await stub.publish(body.key, body.content)
|
||||
return new Response(JSON.stringify({}), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
if (request.method === "GET" && method === "share_poll") {
|
||||
const upgradeHeader = request.headers.get("Upgrade")
|
||||
if (!upgradeHeader || upgradeHeader !== "websocket") {
|
||||
return new Response("Error: Upgrade header is required", {
|
||||
status: 426,
|
||||
})
|
||||
let info
|
||||
const messages: Record<string, any> = {}
|
||||
data.forEach((d) => {
|
||||
const [root, type, ...splits] = d.key.split("/")
|
||||
if (root !== "session") return
|
||||
if (type === "info") {
|
||||
info = d.content
|
||||
return
|
||||
}
|
||||
const id = url.searchParams.get("id")
|
||||
console.log("share_poll", id)
|
||||
if (!id) return new Response("Error: Share ID is required", { status: 400 })
|
||||
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||
return stub.fetch(request)
|
||||
}
|
||||
if (type === "message") {
|
||||
messages[d.content.id] = {
|
||||
parts: [],
|
||||
...d.content,
|
||||
}
|
||||
}
|
||||
if (type === "part") {
|
||||
messages[d.content.messageID].parts.push(d.content)
|
||||
}
|
||||
})
|
||||
|
||||
if (request.method === "GET" && method === "share_data") {
|
||||
const id = url.searchParams.get("id")
|
||||
console.log("share_data", id)
|
||||
if (!id) return new Response("Error: Share ID is required", { status: 400 })
|
||||
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||
const data = await stub.getData()
|
||||
return c.json({ info, messages })
|
||||
})
|
||||
/**
|
||||
* Used by the GitHub action to get GitHub installation access token given the OIDC token
|
||||
*/
|
||||
.post("/exchange_github_app_token", async (c) => {
|
||||
const EXPECTED_AUDIENCE = "opencode-github-action"
|
||||
const GITHUB_ISSUER = "https://token.actions.githubusercontent.com"
|
||||
const JWKS_URL = `${GITHUB_ISSUER}/.well-known/jwks`
|
||||
|
||||
let info
|
||||
const messages: Record<string, any> = {}
|
||||
data.forEach((d) => {
|
||||
const [root, type, ...splits] = d.key.split("/")
|
||||
if (root !== "session") return
|
||||
if (type === "info") {
|
||||
info = d.content
|
||||
return
|
||||
}
|
||||
if (type === "message") {
|
||||
messages[d.content.id] = {
|
||||
parts: [],
|
||||
...d.content,
|
||||
}
|
||||
}
|
||||
if (type === "part") {
|
||||
messages[d.content.messageID].parts.push(d.content)
|
||||
}
|
||||
// get Authorization header
|
||||
const token = c.req.header("Authorization")?.replace(/^Bearer /, "")
|
||||
if (!token) return c.json({ error: "Authorization header is required" }, { status: 401 })
|
||||
|
||||
// verify token
|
||||
const JWKS = createRemoteJWKSet(new URL(JWKS_URL))
|
||||
let owner, repo
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, JWKS, {
|
||||
issuer: GITHUB_ISSUER,
|
||||
audience: EXPECTED_AUDIENCE,
|
||||
})
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
info,
|
||||
messages,
|
||||
}),
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
)
|
||||
const sub = payload.sub // e.g. 'repo:my-org/my-repo:ref:refs/heads/main'
|
||||
const parts = sub.split(":")[1].split("/")
|
||||
owner = parts[0]
|
||||
repo = parts[1]
|
||||
} catch (err) {
|
||||
console.error("Token verification failed:", err)
|
||||
return c.json({ error: "Invalid or expired token" }, { status: 403 })
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the GitHub action to get GitHub installation access token given the OIDC token
|
||||
*/
|
||||
if (request.method === "POST" && method === "exchange_github_app_token") {
|
||||
const EXPECTED_AUDIENCE = "opencode-github-action"
|
||||
const GITHUB_ISSUER = "https://token.actions.githubusercontent.com"
|
||||
const JWKS_URL = `${GITHUB_ISSUER}/.well-known/jwks`
|
||||
// Create app JWT token
|
||||
const auth = createAppAuth({
|
||||
appId: Resource.GITHUB_APP_ID.value,
|
||||
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
|
||||
})
|
||||
const appAuth = await auth({ type: "app" })
|
||||
|
||||
// Lookup installation
|
||||
const octokit = new Octokit({ auth: appAuth.token })
|
||||
const { data: installation } = await octokit.apps.getRepoInstallation({ owner, repo })
|
||||
|
||||
// Get installation token
|
||||
const installationAuth = await auth({ type: "installation", installationId: installation.id })
|
||||
|
||||
return c.json({ token: installationAuth.token })
|
||||
})
|
||||
/**
|
||||
* Used by the GitHub action to get GitHub installation access token given user PAT token (used when testing `opencode github run` locally)
|
||||
*/
|
||||
.post("/exchange_github_app_token_with_pat", async (c) => {
|
||||
const body = await c.req.json<{ owner: string; repo: string }>()
|
||||
const owner = body.owner
|
||||
const repo = body.repo
|
||||
|
||||
try {
|
||||
// get Authorization header
|
||||
const authHeader = request.headers.get("Authorization")
|
||||
const authHeader = c.req.header("Authorization")
|
||||
const token = authHeader?.replace(/^Bearer /, "")
|
||||
if (!token)
|
||||
return new Response(JSON.stringify({ error: "Authorization header is required" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
if (!token) throw new Error("Authorization header is required")
|
||||
|
||||
// verify token
|
||||
const JWKS = createRemoteJWKSet(new URL(JWKS_URL))
|
||||
let owner, repo
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, JWKS, {
|
||||
issuer: GITHUB_ISSUER,
|
||||
audience: EXPECTED_AUDIENCE,
|
||||
})
|
||||
const sub = payload.sub // e.g. 'repo:my-org/my-repo:ref:refs/heads/main'
|
||||
const parts = sub.split(":")[1].split("/")
|
||||
owner = parts[0]
|
||||
repo = parts[1]
|
||||
} catch (err) {
|
||||
console.error("Token verification failed:", err)
|
||||
return new Response(JSON.stringify({ error: "Invalid or expired token" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
// Verify permissions
|
||||
const userClient = new Octokit({ auth: token })
|
||||
const { data: repoData } = await userClient.repos.get({ owner, repo })
|
||||
if (!repoData.permissions.admin && !repoData.permissions.push && !repoData.permissions.maintain)
|
||||
throw new Error("User does not have write permissions")
|
||||
|
||||
// Create app JWT token
|
||||
// Get installation token
|
||||
const auth = createAppAuth({
|
||||
appId: Resource.GITHUB_APP_ID.value,
|
||||
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
|
||||
@@ -277,99 +269,49 @@ export default {
|
||||
const appAuth = await auth({ type: "app" })
|
||||
|
||||
// Lookup installation
|
||||
const octokit = new Octokit({ auth: appAuth.token })
|
||||
const { data: installation } = await octokit.apps.getRepoInstallation({ owner, repo })
|
||||
const appClient = new Octokit({ auth: appAuth.token })
|
||||
const { data: installation } = await appClient.apps.getRepoInstallation({ owner, repo })
|
||||
|
||||
// Get installation token
|
||||
const installationAuth = await auth({ type: "installation", installationId: installation.id })
|
||||
|
||||
return new Response(JSON.stringify({ token: installationAuth.token }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
return c.json({ token: installationAuth.token })
|
||||
} catch (e: any) {
|
||||
let error = e
|
||||
if (e instanceof Error) {
|
||||
error = e.message
|
||||
}
|
||||
|
||||
return c.json({ error }, { status: 401 })
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Used by the opencode CLI to check if the GitHub app is installed
|
||||
*/
|
||||
.get("/get_github_app_installation", async (c) => {
|
||||
const owner = c.req.query("owner")
|
||||
const repo = c.req.query("repo")
|
||||
|
||||
/**
|
||||
* Used by the GitHub action to get GitHub installation access token given user PAT token (used when testing `opencode github run` locally)
|
||||
*/
|
||||
if (request.method === "POST" && method === "exchange_github_app_token_with_pat") {
|
||||
const body = await request.json<any>()
|
||||
const owner = body.owner
|
||||
const repo = body.repo
|
||||
const auth = createAppAuth({
|
||||
appId: Resource.GITHUB_APP_ID.value,
|
||||
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
|
||||
})
|
||||
const appAuth = await auth({ type: "app" })
|
||||
|
||||
try {
|
||||
// get Authorization header
|
||||
const authHeader = request.headers.get("Authorization")
|
||||
const token = authHeader?.replace(/^Bearer /, "")
|
||||
if (!token) throw new Error("Authorization header is required")
|
||||
|
||||
// Verify permissions
|
||||
const userClient = new Octokit({ auth: token })
|
||||
const { data: repoData } = await userClient.repos.get({ owner, repo })
|
||||
if (!repoData.permissions.admin && !repoData.permissions.push && !repoData.permissions.maintain)
|
||||
throw new Error("User does not have write permissions")
|
||||
|
||||
// Get installation token
|
||||
const auth = createAppAuth({
|
||||
appId: Resource.GITHUB_APP_ID.value,
|
||||
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
|
||||
})
|
||||
const appAuth = await auth({ type: "app" })
|
||||
|
||||
// Lookup installation
|
||||
const appClient = new Octokit({ auth: appAuth.token })
|
||||
const { data: installation } = await appClient.apps.getRepoInstallation({ owner, repo })
|
||||
|
||||
// Get installation token
|
||||
const installationAuth = await auth({ type: "installation", installationId: installation.id })
|
||||
|
||||
return new Response(JSON.stringify({ token: installationAuth.token }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
} catch (e: any) {
|
||||
let error = e
|
||||
if (e instanceof Error) {
|
||||
error = e.message
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ error }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
// Lookup installation
|
||||
const octokit = new Octokit({ auth: appAuth.token })
|
||||
let installation
|
||||
try {
|
||||
const ret = await octokit.apps.getRepoInstallation({ owner, repo })
|
||||
installation = ret.data
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes("Not Found")) {
|
||||
// not installed
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the opencode CLI to check if the GitHub app is installed
|
||||
*/
|
||||
if (request.method === "GET" && method === "get_github_app_installation") {
|
||||
const owner = url.searchParams.get("owner")
|
||||
const repo = url.searchParams.get("repo")
|
||||
|
||||
const auth = createAppAuth({
|
||||
appId: Resource.GITHUB_APP_ID.value,
|
||||
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
|
||||
})
|
||||
const appAuth = await auth({ type: "app" })
|
||||
|
||||
// Lookup installation
|
||||
const octokit = new Octokit({ auth: appAuth.token })
|
||||
let installation
|
||||
try {
|
||||
const ret = await octokit.apps.getRepoInstallation({ owner, repo })
|
||||
installation = ret.data
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes("Not Found")) {
|
||||
// not installed
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ installation }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
return new Response("Not Found", { status: 404 })
|
||||
},
|
||||
}
|
||||
return c.json({ installation })
|
||||
})
|
||||
.all("*", (c) => c.text("Not Found"))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.0",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -17,6 +17,7 @@
|
||||
"devDependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "2.2.10",
|
||||
"@ai-sdk/anthropic": "1.2.12",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@tsconfig/bun": "1.0.7",
|
||||
"@types/bun": "latest",
|
||||
@@ -27,9 +28,13 @@
|
||||
"zod-to-json-schema": "3.24.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@clack/prompts": "0.11.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@modelcontextprotocol/sdk": "1.15.1",
|
||||
"@octokit/graphql": "9.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
@@ -37,12 +42,14 @@
|
||||
"decimal.js": "10.5.0",
|
||||
"diff": "8.0.2",
|
||||
"gray-matter": "4.0.3",
|
||||
"hono": "4.7.10",
|
||||
"hono": "catalog:",
|
||||
"hono-openapi": "0.4.8",
|
||||
"isomorphic-git": "1.32.1",
|
||||
"open": "10.1.2",
|
||||
"remeda": "2.22.3",
|
||||
"remeda": "catalog:",
|
||||
"turndown": "7.2.0",
|
||||
"tree-sitter": "0.22.4",
|
||||
"tree-sitter-bash": "0.23.3",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
import { $ } from "bun"
|
||||
|
||||
import pkg from "../package.json"
|
||||
|
||||
const dry = process.argv.includes("--dry")
|
||||
const snapshot = process.argv.includes("--snapshot")
|
||||
|
||||
const version = snapshot
|
||||
? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
|
||||
: await $`git describe --tags --abbrev=0`
|
||||
.text()
|
||||
.then((x) => x.substring(1).trim())
|
||||
.catch(() => {
|
||||
console.error("tag not found")
|
||||
process.exit(1)
|
||||
})
|
||||
const dry = process.env["OPENCODE_DRY"] === "true"
|
||||
const version = process.env["OPENCODE_VERSION"]!
|
||||
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
||||
|
||||
console.log(`publishing ${version}`)
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ export namespace Agent {
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
description: "",
|
||||
tools: {},
|
||||
tools: {
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
},
|
||||
}
|
||||
const model = value.model ?? cfg.model
|
||||
if (model) item.model = Provider.parseModel(model)
|
||||
|
||||
@@ -16,7 +16,13 @@ export namespace Auth {
|
||||
key: z.string(),
|
||||
})
|
||||
|
||||
export const Info = z.discriminatedUnion("type", [Oauth, Api])
|
||||
export const WellKnown = z.object({
|
||||
type: z.literal("wellknown"),
|
||||
key: z.string(),
|
||||
token: z.string(),
|
||||
})
|
||||
|
||||
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown])
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
const filepath = path.join(Global.Path.data, "auth.json")
|
||||
|
||||
@@ -73,6 +73,7 @@ export namespace BunProc {
|
||||
// Let Bun handle registry resolution:
|
||||
// - If .npmrc files exist, Bun will use them automatically
|
||||
// - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
|
||||
// - No need to pass --registry flag
|
||||
log.info("installing package using Bun's default registry resolution", { pkg, version })
|
||||
|
||||
await BunProc.run(args, {
|
||||
|
||||
@@ -61,17 +61,45 @@ export const AuthListCommand = cmd({
|
||||
prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`)
|
||||
}
|
||||
|
||||
prompts.outro(`${activeEnvVars.length} environment variables`)
|
||||
prompts.outro(`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const AuthLoginCommand = cmd({
|
||||
command: "login",
|
||||
command: "login [url]",
|
||||
describe: "log in to a provider",
|
||||
async handler() {
|
||||
UI.empty()
|
||||
builder: (yargs) =>
|
||||
yargs.positional("url", {
|
||||
describe: "opencode auth provider",
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
prompts.intro("Add credential")
|
||||
if (args.url) {
|
||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
|
||||
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
|
||||
const proc = Bun.spawn({
|
||||
cmd: wellknown.auth.command,
|
||||
stdout: "pipe",
|
||||
})
|
||||
const exit = await proc.exited
|
||||
if (exit !== 0) {
|
||||
prompts.log.error("Failed")
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
const token = await new Response(proc.stdout).text()
|
||||
await Auth.set(args.url, {
|
||||
type: "wellknown",
|
||||
key: wellknown.auth.env,
|
||||
token: token.trim(),
|
||||
})
|
||||
prompts.log.success("Logged into " + args.url)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
UI.empty()
|
||||
const providers = await ModelsDev.get()
|
||||
const priority: Record<string, number> = {
|
||||
anthropic: 0,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const SnapshotCommand = cmd({
|
||||
command: "snapshot",
|
||||
builder: (yargs) => yargs.command(TrackCommand).command(PatchCommand).demandCommand(),
|
||||
builder: (yargs) => yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
@@ -31,3 +31,18 @@ const PatchCommand = cmd({
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const DiffCommand = cmd({
|
||||
command: "diff <hash>",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("hash", {
|
||||
type: "string",
|
||||
description: "hash",
|
||||
demandOption: true,
|
||||
}),
|
||||
async handler(args) {
|
||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||
console.log(await Snapshot.diff(args.hash))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { Server } from "../../server/server"
|
||||
import fs from "fs/promises"
|
||||
import type { CommandModule } from "yargs"
|
||||
|
||||
export const GenerateCommand = {
|
||||
command: "generate",
|
||||
handler: async () => {
|
||||
const specs = await Server.openapi()
|
||||
const dir = "gen"
|
||||
await fs.rmdir(dir, { recursive: true }).catch(() => {})
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
process.stdout.write(JSON.stringify(specs, null, 2))
|
||||
},
|
||||
} satisfies CommandModule
|
||||
|
||||
1106
packages/opencode/src/cli/cmd/github.ts
Normal file
1106
packages/opencode/src/cli/cmd/github.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,221 +0,0 @@
|
||||
import { $ } from "bun"
|
||||
import path from "path"
|
||||
import { exec } from "child_process"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { ModelsDev } from "../../provider/models"
|
||||
import { App } from "../../app/app"
|
||||
|
||||
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
|
||||
|
||||
export const InstallGithubCommand = cmd({
|
||||
command: "install-github",
|
||||
describe: "install the GitHub agent",
|
||||
async handler() {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
UI.empty()
|
||||
prompts.intro("Install GitHub agent")
|
||||
const app = await getAppInfo()
|
||||
await installGitHubApp()
|
||||
|
||||
const providers = await ModelsDev.get()
|
||||
const provider = await promptProvider()
|
||||
const model = await promptModel()
|
||||
//const key = await promptKey()
|
||||
|
||||
await addWorkflowFiles()
|
||||
printNextSteps()
|
||||
|
||||
function printNextSteps() {
|
||||
let step2
|
||||
if (provider === "amazon-bedrock") {
|
||||
step2 =
|
||||
"Configure OIDC in AWS - https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"
|
||||
} else {
|
||||
const url = `https://github.com/organizations/${app.owner}/settings/secrets/actions`
|
||||
const env = providers[provider].env
|
||||
const envStr =
|
||||
env.length === 1
|
||||
? `\`${env[0]}\` secret`
|
||||
: `\`${[env.slice(0, -1).join("\`, \`"), ...env.slice(-1)].join("\` and \`")}\` secrets`
|
||||
step2 = `Add ${envStr} for ${providers[provider].name} - ${url}`
|
||||
}
|
||||
|
||||
prompts.outro(
|
||||
[
|
||||
"Next steps:",
|
||||
` 1. Commit "${WORKFLOW_FILE}" file and push`,
|
||||
` 2. ${step2}`,
|
||||
" 3. Learn how to use the GitHub agent - https://docs.opencode.ai/docs/github/getting-started",
|
||||
].join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
async function getAppInfo() {
|
||||
const app = App.info()
|
||||
if (!app.git) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
// Get repo info
|
||||
const info = await $`git remote get-url origin`.quiet().nothrow().text()
|
||||
// match https or git pattern
|
||||
// ie. https://github.com/sst/opencode.git
|
||||
// ie. git@github.com:sst/opencode.git
|
||||
const parsed = info.match(/git@github\.com:(.*)\.git/) ?? info.match(/github\.com\/(.*)\.git/)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
const [owner, repo] = parsed[1].split("/")
|
||||
return { owner, repo, root: app.path.root }
|
||||
}
|
||||
|
||||
async function promptProvider() {
|
||||
const priority: Record<string, number> = {
|
||||
anthropic: 0,
|
||||
"github-copilot": 1,
|
||||
openai: 2,
|
||||
google: 3,
|
||||
}
|
||||
let provider = await prompts.select({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providers,
|
||||
values(),
|
||||
sortBy(
|
||||
(x) => priority[x.id] ?? 99,
|
||||
(x) => x.name ?? x.id,
|
||||
),
|
||||
map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id,
|
||||
hint: priority[x.id] === 0 ? "recommended" : undefined,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
async function promptModel() {
|
||||
const providerData = providers[provider]!
|
||||
|
||||
const model = await prompts.select({
|
||||
message: "Select model",
|
||||
maxItems: 8,
|
||||
options: pipe(
|
||||
providerData.models,
|
||||
values(),
|
||||
sortBy((x) => x.name ?? x.id),
|
||||
map((x) => ({
|
||||
label: x.name ?? x.id,
|
||||
value: x.id,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
if (prompts.isCancel(model)) throw new UI.CancelledError()
|
||||
return model
|
||||
}
|
||||
|
||||
async function installGitHubApp() {
|
||||
const s = prompts.spinner()
|
||||
s.start("Installing GitHub app")
|
||||
|
||||
// Get installation
|
||||
const installation = await getInstallation()
|
||||
if (installation) return s.stop("GitHub app already installed")
|
||||
|
||||
// Open browser
|
||||
const url = "https://github.com/apps/opencode-agent"
|
||||
const command =
|
||||
process.platform === "darwin"
|
||||
? `open "${url}"`
|
||||
: process.platform === "win32"
|
||||
? `start "${url}"`
|
||||
: `xdg-open "${url}"`
|
||||
|
||||
exec(command, (error) => {
|
||||
if (error) {
|
||||
prompts.log.warn(`Could not open browser. Please visit: ${url}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Wait for installation
|
||||
s.message("Waiting for GitHub app to be installed")
|
||||
const MAX_RETRIES = 60
|
||||
let retries = 0
|
||||
do {
|
||||
const installation = await getInstallation()
|
||||
if (installation) break
|
||||
|
||||
if (retries > MAX_RETRIES) {
|
||||
s.stop(
|
||||
`Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
|
||||
)
|
||||
throw new UI.CancelledError()
|
||||
}
|
||||
|
||||
retries++
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
} while (true)
|
||||
|
||||
s.stop("Installed GitHub app")
|
||||
|
||||
async function getInstallation() {
|
||||
return await fetch(`https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.installation)
|
||||
}
|
||||
}
|
||||
|
||||
async function addWorkflowFiles() {
|
||||
const envStr =
|
||||
provider === "amazon-bedrock"
|
||||
? ""
|
||||
: `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}`
|
||||
|
||||
await Bun.write(
|
||||
path.join(app.root, WORKFLOW_FILE),
|
||||
`
|
||||
name: opencode
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
startsWith(github.event.comment.body, 'opencode') ||
|
||||
startsWith(github.event.comment.body, 'hi opencode') ||
|
||||
startsWith(github.event.comment.body, 'hey opencode') ||
|
||||
contains(github.event.comment.body, '@opencode-agent')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run opencode
|
||||
uses: sst/opencode/sdks/github@github-v1${envStr}
|
||||
with:
|
||||
model: ${provider}/${model}
|
||||
`.trim(),
|
||||
)
|
||||
|
||||
prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -113,7 +113,7 @@ export const TuiCommand = cmd({
|
||||
})
|
||||
|
||||
;(async () => {
|
||||
if (Installation.VERSION === "dev") return
|
||||
if (Installation.isDev()) return
|
||||
if (Installation.isSnapshot()) return
|
||||
const config = await Config.global()
|
||||
if (config.autoupdate === false) return
|
||||
@@ -123,19 +123,15 @@ export const TuiCommand = cmd({
|
||||
const method = await Installation.method()
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => {
|
||||
Bus.publish(Installation.Event.Updated, { version: latest })
|
||||
})
|
||||
.then(() => Bus.publish(Installation.Event.Updated, { version: latest }))
|
||||
.catch(() => {})
|
||||
})()
|
||||
;(async () => {
|
||||
if (Ide.alreadyInstalled()) return
|
||||
const ide = await Ide.ide()
|
||||
const ide = Ide.ide()
|
||||
if (ide === "unknown") return
|
||||
await Ide.install(ide)
|
||||
.then(() => {
|
||||
Bus.publish(Ide.Event.Installed, { ide })
|
||||
})
|
||||
.then(() => Bus.publish(Ide.Event.Installed, { ide }))
|
||||
.catch(() => {})
|
||||
})()
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ import fs from "fs/promises"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { NamedError } from "../util/error"
|
||||
import matter from "gray-matter"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Auth } from "../auth"
|
||||
|
||||
export namespace Config {
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
export const state = App.state("config", async (app) => {
|
||||
const auth = await Auth.all()
|
||||
let result = await global()
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
|
||||
@@ -23,6 +26,20 @@ export namespace Config {
|
||||
}
|
||||
}
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeDeep(result, await load(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(auth)) {
|
||||
if (value.type === "wellknown") {
|
||||
process.env[value.key] = value.token
|
||||
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
|
||||
result = mergeDeep(result, await loadRaw(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
}
|
||||
|
||||
result.agent = result.agent || {}
|
||||
const markdownAgents = [
|
||||
...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)),
|
||||
@@ -48,6 +65,32 @@ export namespace Config {
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
|
||||
// Load mode markdown files
|
||||
result.mode = result.mode || {}
|
||||
const markdownModes = [
|
||||
...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/mode/*.md", app.path.cwd, app.path.root)),
|
||||
]
|
||||
for (const item of markdownModes) {
|
||||
const content = await Bun.file(item).text()
|
||||
const md = matter(content)
|
||||
if (!md.data) continue
|
||||
|
||||
const config = {
|
||||
name: path.basename(item, ".md"),
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
const parsed = Mode.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.mode = mergeDeep(result.mode, {
|
||||
[config.name]: parsed.data,
|
||||
})
|
||||
continue
|
||||
}
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
|
||||
// Handle migration from autoshare to share field
|
||||
if (result.autoshare === true && !result.share) {
|
||||
result.share = "auto"
|
||||
@@ -99,6 +142,7 @@ export namespace Config {
|
||||
export const Mode = z
|
||||
.object({
|
||||
model: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
disable: z.boolean().optional(),
|
||||
@@ -169,6 +213,9 @@ export namespace Config {
|
||||
})
|
||||
export type Layout = z.infer<typeof Layout>
|
||||
|
||||
export const Permission = z.union([z.literal("ask"), z.literal("allow")])
|
||||
export type Permission = z.infer<typeof Permission>
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||
@@ -232,6 +279,12 @@ export namespace Config {
|
||||
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
|
||||
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
|
||||
permission: z
|
||||
.object({
|
||||
edit: Permission.optional(),
|
||||
bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
|
||||
})
|
||||
.optional(),
|
||||
experimental: z
|
||||
.object({
|
||||
hook: z
|
||||
@@ -299,7 +352,10 @@ export namespace Config {
|
||||
throw new JsonError({ path: configPath }, { cause: err })
|
||||
})
|
||||
if (!text) return {}
|
||||
return loadRaw(text, configPath)
|
||||
}
|
||||
|
||||
async function loadRaw(text: string, configPath: string) {
|
||||
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||
return process.env[varName] || ""
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export namespace Flag {
|
||||
export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE")
|
||||
export const OPENCODE_DISABLE_WATCHER = truthy("OPENCODE_DISABLE_WATCHER")
|
||||
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { App } from "../app/app"
|
||||
import { BunProc } from "../bun"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import path from "path"
|
||||
|
||||
export interface Info {
|
||||
name: string
|
||||
@@ -65,14 +64,57 @@ export const prettier: Info = {
|
||||
],
|
||||
async enabled() {
|
||||
const app = App.info()
|
||||
const nms = await Filesystem.findUp("node_modules", app.path.cwd, app.path.root)
|
||||
for (const item of nms) {
|
||||
if (await Bun.file(path.join(item, ".bin", "prettier")).exists()) return true
|
||||
const items = await Filesystem.findUp("package.json", app.path.cwd, app.path.root)
|
||||
for (const item of items) {
|
||||
const json = await Bun.file(item).json()
|
||||
if (json.dependencies?.prettier) return true
|
||||
if (json.devDependencies?.prettier) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
export const biome: Info = {
|
||||
name: "biome",
|
||||
command: [BunProc.which(), "x", "biome", "format", "--write", "$FILE"],
|
||||
environment: {
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
extensions: [
|
||||
".js",
|
||||
".jsx",
|
||||
".mjs",
|
||||
".cjs",
|
||||
".ts",
|
||||
".tsx",
|
||||
".mts",
|
||||
".cts",
|
||||
".html",
|
||||
".htm",
|
||||
".css",
|
||||
".scss",
|
||||
".sass",
|
||||
".less",
|
||||
".vue",
|
||||
".svelte",
|
||||
".json",
|
||||
".jsonc",
|
||||
".yaml",
|
||||
".yml",
|
||||
".toml",
|
||||
".xml",
|
||||
".md",
|
||||
".mdx",
|
||||
".graphql",
|
||||
".gql",
|
||||
],
|
||||
async enabled() {
|
||||
const app = App.info()
|
||||
const items = await Filesystem.findUp("biome.json", app.path.cwd, app.path.root)
|
||||
return items.length > 0
|
||||
},
|
||||
}
|
||||
|
||||
export const zig: Info = {
|
||||
name: "zig",
|
||||
command: ["zig", "fmt", "$FILE"],
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { $ } from "bun"
|
||||
import { spawn } from "bun"
|
||||
import { z } from "zod"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Log } from "../util/log"
|
||||
import { Bus } from "../bus"
|
||||
|
||||
const SUPPORTED_IDES = ["Windsurf", "Visual Studio Code", "Cursor", "VSCodium"] as const
|
||||
const SUPPORTED_IDES = [
|
||||
{ name: "Windsurf" as const, cmd: "windsurf" },
|
||||
{ name: "Visual Studio Code" as const, cmd: "code" },
|
||||
{ name: "Cursor" as const, cmd: "cursor" },
|
||||
{ name: "VSCodium" as const, cmd: "codium" },
|
||||
]
|
||||
|
||||
export namespace Ide {
|
||||
const log = Log.create({ service: "ide" })
|
||||
@@ -18,8 +23,6 @@ export namespace Ide {
|
||||
),
|
||||
}
|
||||
|
||||
export type Ide = Awaited<ReturnType<typeof ide>>
|
||||
|
||||
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({}))
|
||||
|
||||
export const InstallFailedError = NamedError.create(
|
||||
@@ -29,11 +32,11 @@ export namespace Ide {
|
||||
}),
|
||||
)
|
||||
|
||||
export async function ide() {
|
||||
export function ide() {
|
||||
if (process.env["TERM_PROGRAM"] === "vscode") {
|
||||
const v = process.env["GIT_ASKPASS"]
|
||||
for (const ide of SUPPORTED_IDES) {
|
||||
if (v?.includes(ide)) return ide
|
||||
if (v?.includes(ide.name)) return ide.name
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
@@ -43,32 +46,29 @@ export namespace Ide {
|
||||
return process.env["OPENCODE_CALLER"] === "vscode"
|
||||
}
|
||||
|
||||
export async function install(ide: Ide) {
|
||||
const cmd = (() => {
|
||||
switch (ide) {
|
||||
case "Windsurf":
|
||||
return $`windsurf --install-extension sst-dev.opencode`
|
||||
case "Visual Studio Code":
|
||||
return $`code --install-extension sst-dev.opencode`
|
||||
case "Cursor":
|
||||
return $`cursor --install-extension sst-dev.opencode`
|
||||
case "VSCodium":
|
||||
return $`codium --install-extension sst-dev.opencode`
|
||||
default:
|
||||
throw new Error(`Unknown IDE: ${ide}`)
|
||||
}
|
||||
})()
|
||||
// TODO: check OPENCODE_CALLER
|
||||
const result = await cmd.quiet().throws(false)
|
||||
export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) {
|
||||
const cmd = SUPPORTED_IDES.find((i) => i.name === ide)?.cmd
|
||||
if (!cmd) throw new Error(`Unknown IDE: ${ide}`)
|
||||
|
||||
const p = spawn([cmd, "--install-extension", "sst-dev.opencode"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
await p.exited
|
||||
const stdout = await new Response(p.stdout).text()
|
||||
const stderr = await new Response(p.stderr).text()
|
||||
|
||||
log.info("installed", {
|
||||
ide,
|
||||
stdout: result.stdout.toString(),
|
||||
stderr: result.stderr.toString(),
|
||||
stdout,
|
||||
stderr,
|
||||
})
|
||||
if (result.exitCode !== 0)
|
||||
throw new InstallFailedError({
|
||||
stderr: result.stderr.toString("utf8"),
|
||||
})
|
||||
if (result.stdout.toString().includes("already installed")) throw new AlreadyInstalledError({})
|
||||
|
||||
if (p.exitCode !== 0) {
|
||||
throw new InstallFailedError({ stderr })
|
||||
}
|
||||
if (stdout.includes("already installed")) {
|
||||
throw new AlreadyInstalledError({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TuiCommand } from "./cli/cmd/tui"
|
||||
import { DebugCommand } from "./cli/cmd/debug"
|
||||
import { StatsCommand } from "./cli/cmd/stats"
|
||||
import { McpCommand } from "./cli/cmd/mcp"
|
||||
import { InstallGithubCommand } from "./cli/cmd/install-github"
|
||||
import { GithubCommand } from "./cli/cmd/github"
|
||||
import { Trace } from "./trace"
|
||||
|
||||
Trace.init()
|
||||
@@ -78,7 +78,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ServeCommand)
|
||||
.command(ModelsCommand)
|
||||
.command(StatsCommand)
|
||||
.command(InstallGithubCommand)
|
||||
.command(GithubCommand)
|
||||
.fail((msg) => {
|
||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||
cli.showHelp("log")
|
||||
|
||||
@@ -322,4 +322,43 @@ export namespace LSPServer {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const CSharp: Info = {
|
||||
id: "csharp",
|
||||
root: NearestRoot([".sln", ".csproj", "global.json"]),
|
||||
extensions: [".cs"],
|
||||
async spawn(_, root) {
|
||||
let bin = Bun.which("csharp-ls", {
|
||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("dotnet")) {
|
||||
log.error(".NET SDK is required to install csharp-ls")
|
||||
return
|
||||
}
|
||||
|
||||
log.info("installing csharp-ls via dotnet tool")
|
||||
const proc = Bun.spawn({
|
||||
cmd: ["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
})
|
||||
const exit = await proc.exited
|
||||
if (exit !== 0) {
|
||||
log.error("Failed to install csharp-ls")
|
||||
return
|
||||
}
|
||||
|
||||
bin = path.join(Global.Path.bin, "csharp-ls" + (process.platform === "win32" ? ".exe" : ""))
|
||||
log.info(`installed csharp-ls`, { bin })
|
||||
}
|
||||
|
||||
return {
|
||||
process: spawn(bin, {
|
||||
cwd: root,
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { experimental_createMCPClient, type Tool } from "ai"
|
||||
import { Experimental_StdioMCPTransport } from "ai/mcp-stdio"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
||||
import { App } from "../app/app"
|
||||
import { Config } from "../config/config"
|
||||
import { Log } from "../util/log"
|
||||
@@ -32,15 +34,28 @@ export namespace MCP {
|
||||
}
|
||||
log.info("found", { key, type: mcp.type })
|
||||
if (mcp.type === "remote") {
|
||||
const client = await experimental_createMCPClient({
|
||||
name: key,
|
||||
transport: {
|
||||
type: "sse",
|
||||
url: mcp.url,
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}).catch(() => {})
|
||||
if (!client) {
|
||||
const transports = [
|
||||
new StreamableHTTPClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
new SSEClientTransport(new URL(mcp.url), {
|
||||
requestInit: {
|
||||
headers: mcp.headers,
|
||||
},
|
||||
}),
|
||||
]
|
||||
for (const transport of transports) {
|
||||
const client = await experimental_createMCPClient({
|
||||
name: key,
|
||||
transport,
|
||||
}).catch(() => {})
|
||||
if (!client) continue
|
||||
clients[key] = client
|
||||
break
|
||||
}
|
||||
if (!clients[key])
|
||||
Bus.publish(Session.Event.Error, {
|
||||
error: {
|
||||
name: "UnknownError",
|
||||
@@ -49,16 +64,13 @@ export namespace MCP {
|
||||
},
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
clients[key] = client
|
||||
}
|
||||
|
||||
if (mcp.type === "local") {
|
||||
const [cmd, ...args] = mcp.command
|
||||
const client = await experimental_createMCPClient({
|
||||
name: key,
|
||||
transport: new Experimental_StdioMCPTransport({
|
||||
transport: new StdioClientTransport({
|
||||
stderr: "ignore",
|
||||
command: cmd,
|
||||
args,
|
||||
|
||||
@@ -5,22 +5,11 @@ import { mergeDeep, sortBy } from "remeda"
|
||||
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { BashTool } from "../tool/bash"
|
||||
import { EditTool } from "../tool/edit"
|
||||
import { WebFetchTool } from "../tool/webfetch"
|
||||
import { GlobTool } from "../tool/glob"
|
||||
import { GrepTool } from "../tool/grep"
|
||||
import { ListTool } from "../tool/ls"
|
||||
import { PatchTool } from "../tool/patch"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { WriteTool } from "../tool/write"
|
||||
import { TodoReadTool, TodoWriteTool } from "../tool/todo"
|
||||
import { AuthAnthropic } from "../auth/anthropic"
|
||||
import { AuthCopilot } from "../auth/copilot"
|
||||
import { ModelsDev } from "./models"
|
||||
import { NamedError } from "../util/error"
|
||||
import { Auth } from "../auth"
|
||||
import { TaskTool } from "../tool/task"
|
||||
|
||||
export namespace Provider {
|
||||
const log = Log.create({ service: "provider" })
|
||||
@@ -108,7 +97,7 @@ export namespace Provider {
|
||||
Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"),
|
||||
)
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
const headers: Record<string, string> = {
|
||||
...init.headers,
|
||||
...copilot.HEADERS,
|
||||
@@ -137,6 +126,15 @@ export namespace Provider {
|
||||
options: {},
|
||||
}
|
||||
},
|
||||
azure: async () => {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string) {
|
||||
return sdk.responses(modelID)
|
||||
},
|
||||
options: {},
|
||||
}
|
||||
},
|
||||
"amazon-bedrock": async () => {
|
||||
if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"] && !process.env["AWS_BEARER_TOKEN_BEDROCK"])
|
||||
return { autoload: false }
|
||||
@@ -205,6 +203,17 @@ export namespace Provider {
|
||||
},
|
||||
}
|
||||
},
|
||||
vercel: async () => {
|
||||
return {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"http-referer": "https://opencode.ai/",
|
||||
"x-title": "opencode",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const state = App.state("provider", async () => {
|
||||
@@ -274,26 +283,26 @@ export namespace Provider {
|
||||
cost:
|
||||
!model.cost && !existing?.cost
|
||||
? {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
}
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
}
|
||||
: {
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
...existing?.cost,
|
||||
...model.cost,
|
||||
},
|
||||
cache_read: 0,
|
||||
cache_write: 0,
|
||||
...existing?.cost,
|
||||
...model.cost,
|
||||
},
|
||||
options: {
|
||||
...existing?.options,
|
||||
...model.options,
|
||||
},
|
||||
limit: model.limit ??
|
||||
existing?.limit ?? {
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
}
|
||||
parsed.models[modelID] = parsedModel
|
||||
}
|
||||
@@ -468,137 +477,6 @@ export namespace Provider {
|
||||
}
|
||||
}
|
||||
|
||||
const TOOLS = [
|
||||
BashTool,
|
||||
EditTool,
|
||||
WebFetchTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ListTool,
|
||||
// LspDiagnosticTool,
|
||||
// LspHoverTool,
|
||||
PatchTool,
|
||||
ReadTool,
|
||||
// MultiEditTool,
|
||||
WriteTool,
|
||||
TodoWriteTool,
|
||||
TodoReadTool,
|
||||
TaskTool,
|
||||
]
|
||||
|
||||
export async function tools(providerID: string) {
|
||||
const result = await Promise.all(TOOLS.map((t) => t()))
|
||||
switch (providerID) {
|
||||
case "anthropic":
|
||||
return result.filter((t) => t.id !== "patch")
|
||||
case "openai":
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
}))
|
||||
case "azure":
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
}))
|
||||
case "google":
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: sanitizeGeminiParameters(t.parameters),
|
||||
}))
|
||||
default:
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
|
||||
if (!schema || visited.has(schema)) {
|
||||
return schema
|
||||
}
|
||||
visited.add(schema)
|
||||
|
||||
if (schema instanceof z.ZodDefault) {
|
||||
const innerSchema = schema.removeDefault()
|
||||
// Handle Gemini's incompatibility with `default` on `anyOf` (unions).
|
||||
if (innerSchema instanceof z.ZodUnion) {
|
||||
// The schema was `z.union(...).default(...)`, which is not allowed.
|
||||
// We strip the default and return the sanitized union.
|
||||
return sanitizeGeminiParameters(innerSchema, visited)
|
||||
}
|
||||
// Otherwise, the default is on a regular type, which is allowed.
|
||||
// We recurse on the inner type and then re-apply the default.
|
||||
return sanitizeGeminiParameters(innerSchema, visited).default(schema._def.defaultValue())
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodOptional) {
|
||||
return z.optional(sanitizeGeminiParameters(schema.unwrap(), visited))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const newShape: Record<string, z.ZodTypeAny> = {}
|
||||
for (const [key, value] of Object.entries(schema.shape)) {
|
||||
newShape[key] = sanitizeGeminiParameters(value as z.ZodTypeAny, visited)
|
||||
}
|
||||
return z.object(newShape)
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return z.array(sanitizeGeminiParameters(schema.element, visited))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodUnion) {
|
||||
// This schema corresponds to `anyOf` in JSON Schema.
|
||||
// We recursively sanitize each option in the union.
|
||||
const sanitizedOptions = schema.options.map((option: z.ZodTypeAny) => sanitizeGeminiParameters(option, visited))
|
||||
return z.union(sanitizedOptions as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodString) {
|
||||
const newSchema = z.string({ description: schema.description })
|
||||
const safeChecks = ["min", "max", "length", "regex", "startsWith", "endsWith", "includes", "trim"]
|
||||
// rome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
;(newSchema._def as any).checks = (schema._def as z.ZodStringDef).checks.filter((check) =>
|
||||
safeChecks.includes(check.kind),
|
||||
)
|
||||
return newSchema
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
function optionalToNullable(schema: z.ZodTypeAny): z.ZodTypeAny {
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const shape = schema.shape
|
||||
const newShape: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(shape)) {
|
||||
const zodValue = value as z.ZodTypeAny
|
||||
if (zodValue instanceof z.ZodOptional) {
|
||||
newShape[key] = zodValue.unwrap().nullable()
|
||||
} else {
|
||||
newShape[key] = optionalToNullable(zodValue)
|
||||
}
|
||||
}
|
||||
|
||||
return z.object(newShape)
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return z.array(optionalToNullable(schema.element))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodUnion) {
|
||||
return z.union(
|
||||
schema.options.map((option: z.ZodTypeAny) => optionalToNullable(option)) as [
|
||||
z.ZodTypeAny,
|
||||
z.ZodTypeAny,
|
||||
...z.ZodTypeAny[],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
export const ModelNotFoundError = NamedError.create(
|
||||
"ProviderModelNotFoundError",
|
||||
z.object({
|
||||
|
||||
@@ -7,24 +7,47 @@ export namespace ProviderTransform {
|
||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||
|
||||
const providerOptions = {
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
}
|
||||
|
||||
for (const msg of unique([...system, ...final])) {
|
||||
const shouldUseContentOptions =
|
||||
providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
|
||||
|
||||
if (shouldUseContentOptions) {
|
||||
const lastContent = msg.content[msg.content.length - 1]
|
||||
if (lastContent && typeof lastContent === "object") {
|
||||
lastContent.providerOptions = {
|
||||
...lastContent.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
msg.providerOptions = {
|
||||
...msg.providerOptions,
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
...providerOptions,
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
export function temperature(_providerID: string, modelID: string) {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ export namespace Server {
|
||||
|
||||
export type Routes = ReturnType<typeof app>
|
||||
|
||||
export const Event = {
|
||||
Connected: Bus.event("server.connected", z.object({})),
|
||||
}
|
||||
|
||||
function app() {
|
||||
const app = new Hono()
|
||||
|
||||
@@ -90,6 +94,7 @@ export namespace Server {
|
||||
"/event",
|
||||
describeRoute({
|
||||
description: "Get events",
|
||||
operationId: "event.subscribe",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Event stream",
|
||||
@@ -109,7 +114,10 @@ export namespace Server {
|
||||
log.info("event connected")
|
||||
return streamSSE(c, async (stream) => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({}),
|
||||
data: JSON.stringify({
|
||||
type: "server.connected",
|
||||
properties: {},
|
||||
}),
|
||||
})
|
||||
const unsub = Bus.subscribeAll(async (event) => {
|
||||
await stream.writeSSE({
|
||||
@@ -130,6 +138,7 @@ export namespace Server {
|
||||
"/app",
|
||||
describeRoute({
|
||||
description: "Get app info",
|
||||
operationId: "app.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "200",
|
||||
@@ -149,6 +158,7 @@ export namespace Server {
|
||||
"/app/init",
|
||||
describeRoute({
|
||||
description: "Initialize the app",
|
||||
operationId: "app.init",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Initialize the app",
|
||||
@@ -169,6 +179,7 @@ export namespace Server {
|
||||
"/config",
|
||||
describeRoute({
|
||||
description: "Get config info",
|
||||
operationId: "config.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get config info",
|
||||
@@ -188,6 +199,7 @@ export namespace Server {
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "List all sessions",
|
||||
operationId: "session.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of sessions",
|
||||
@@ -209,6 +221,7 @@ export namespace Server {
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "Create a new session",
|
||||
operationId: "session.create",
|
||||
responses: {
|
||||
...ERRORS,
|
||||
200: {
|
||||
@@ -230,6 +243,7 @@ export namespace Server {
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Delete a session and all its data",
|
||||
operationId: "session.delete",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully deleted session",
|
||||
@@ -256,6 +270,7 @@ export namespace Server {
|
||||
"/session/:id/init",
|
||||
describeRoute({
|
||||
description: "Analyze the app and create an AGENTS.md file",
|
||||
operationId: "session.init",
|
||||
responses: {
|
||||
200: {
|
||||
description: "200",
|
||||
@@ -292,6 +307,7 @@ export namespace Server {
|
||||
"/session/:id/abort",
|
||||
describeRoute({
|
||||
description: "Abort a session",
|
||||
operationId: "session.abort",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Aborted session",
|
||||
@@ -317,6 +333,7 @@ export namespace Server {
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Share a session",
|
||||
operationId: "session.share",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully shared session",
|
||||
@@ -345,6 +362,7 @@ export namespace Server {
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Unshare the session",
|
||||
operationId: "session.unshare",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully unshared session",
|
||||
@@ -373,6 +391,7 @@ export namespace Server {
|
||||
"/session/:id/summarize",
|
||||
describeRoute({
|
||||
description: "Summarize the session",
|
||||
operationId: "session.summarize",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Summarized session",
|
||||
@@ -408,6 +427,7 @@ export namespace Server {
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "List messages for a session",
|
||||
operationId: "session.messages",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of messages",
|
||||
@@ -441,6 +461,7 @@ export namespace Server {
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "Create and send a new message to a session",
|
||||
operationId: "session.chat",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
@@ -470,6 +491,7 @@ export namespace Server {
|
||||
"/session/:id/revert",
|
||||
describeRoute({
|
||||
description: "Revert a message",
|
||||
operationId: "session.revert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
@@ -499,6 +521,7 @@ export namespace Server {
|
||||
"/session/:id/unrevert",
|
||||
describeRoute({
|
||||
description: "Restore all reverted messages",
|
||||
operationId: "session.unrevert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
@@ -526,6 +549,7 @@ export namespace Server {
|
||||
"/config/providers",
|
||||
describeRoute({
|
||||
description: "List all providers",
|
||||
operationId: "config.providers",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of providers",
|
||||
@@ -554,6 +578,7 @@ export namespace Server {
|
||||
"/find",
|
||||
describeRoute({
|
||||
description: "Find text in files",
|
||||
operationId: "find.text",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Matches",
|
||||
@@ -586,6 +611,7 @@ export namespace Server {
|
||||
"/find/file",
|
||||
describeRoute({
|
||||
description: "Find files",
|
||||
operationId: "find.files",
|
||||
responses: {
|
||||
200: {
|
||||
description: "File paths",
|
||||
@@ -618,6 +644,7 @@ export namespace Server {
|
||||
"/find/symbol",
|
||||
describeRoute({
|
||||
description: "Find workspace symbols",
|
||||
operationId: "find.symbols",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Symbols",
|
||||
@@ -645,6 +672,7 @@ export namespace Server {
|
||||
"/file",
|
||||
describeRoute({
|
||||
description: "Read a file",
|
||||
operationId: "file.read",
|
||||
responses: {
|
||||
200: {
|
||||
description: "File content",
|
||||
@@ -681,6 +709,7 @@ export namespace Server {
|
||||
"/file/status",
|
||||
describeRoute({
|
||||
description: "Get file status",
|
||||
operationId: "file.status",
|
||||
responses: {
|
||||
200: {
|
||||
description: "File status",
|
||||
@@ -701,6 +730,7 @@ export namespace Server {
|
||||
"/log",
|
||||
describeRoute({
|
||||
description: "Write a log entry to the server logs",
|
||||
operationId: "app.log",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Log entry written successfully",
|
||||
@@ -750,6 +780,7 @@ export namespace Server {
|
||||
"/mode",
|
||||
describeRoute({
|
||||
description: "List all modes",
|
||||
operationId: "app.modes",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of modes",
|
||||
@@ -770,6 +801,7 @@ export namespace Server {
|
||||
"/tui/append-prompt",
|
||||
describeRoute({
|
||||
description: "Append prompt to the TUI",
|
||||
operationId: "tui.appendPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt processed successfully",
|
||||
@@ -793,6 +825,7 @@ export namespace Server {
|
||||
"/tui/open-help",
|
||||
describeRoute({
|
||||
description: "Open the help dialog",
|
||||
operationId: "tui.openHelp",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Help dialog opened successfully",
|
||||
|
||||
@@ -39,7 +39,8 @@ import { MessageV2 } from "./message-v2"
|
||||
import { Mode } from "./mode"
|
||||
import { LSP } from "../lsp"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { splitWhen } from "remeda"
|
||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -66,6 +67,7 @@ export namespace Session {
|
||||
messageID: z.string(),
|
||||
partID: z.string().optional(),
|
||||
snapshot: z.string().optional(),
|
||||
diff: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
@@ -288,6 +290,9 @@ export namespace Session {
|
||||
export function abort(sessionID: string) {
|
||||
const controller = state().pending.get(sessionID)
|
||||
if (!controller) return false
|
||||
log.info("aborting", {
|
||||
sessionID,
|
||||
})
|
||||
controller.abort()
|
||||
state().pending.delete(sessionID)
|
||||
return true
|
||||
@@ -387,12 +392,38 @@ export namespace Session {
|
||||
if (part.type === "file") {
|
||||
const url = new URL(part.url)
|
||||
switch (url.protocol) {
|
||||
case "data:":
|
||||
if (part.mime === "text/plain") {
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
|
||||
},
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: Buffer.from(part.url, "base64url").toString(),
|
||||
},
|
||||
{
|
||||
...part,
|
||||
id: part.id ?? Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
},
|
||||
]
|
||||
}
|
||||
break
|
||||
case "file:":
|
||||
// have to normalize, symbol search returns absolute paths
|
||||
// Decode the pathname since URL constructor doesn't automatically decode it
|
||||
const pathname = decodeURIComponent(url.pathname)
|
||||
const relativePath = pathname.replace(app.path.cwd, ".")
|
||||
const filePath = path.join(app.path.cwd, relativePath)
|
||||
const filePath = decodeURIComponent(url.pathname)
|
||||
|
||||
if (part.mime === "text/plain") {
|
||||
let offset: number | undefined = undefined
|
||||
@@ -430,7 +461,7 @@ export namespace Session {
|
||||
}
|
||||
}
|
||||
const args = { filePath, offset, limit }
|
||||
const result = await ReadTool().then((t) =>
|
||||
const result = await ReadTool.init().then((t) =>
|
||||
t.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
@@ -472,7 +503,7 @@ export namespace Session {
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
text: `Called the Read tool with the following input: {\"filePath\":\"${pathname}\"}`,
|
||||
text: `Called the Read tool with the following input: {\"filePath\":\"${filePath}\"}`,
|
||||
synthetic: true,
|
||||
},
|
||||
{
|
||||
@@ -512,8 +543,6 @@ export namespace Session {
|
||||
for (const part of userParts) {
|
||||
await updatePart(part)
|
||||
}
|
||||
// mark session as updated since a message has been added to it
|
||||
await update(input.sessionID, (_draft) => {})
|
||||
|
||||
if (isLocked(input.sessionID)) {
|
||||
return new Promise((resolve) => {
|
||||
@@ -538,6 +567,7 @@ export namespace Session {
|
||||
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
|
||||
msgs = preserve
|
||||
for (const msg of remove) {
|
||||
if (msg.info.id === userMsg.id) continue
|
||||
await Storage.remove(`session/message/${input.sessionID}/${msg.info.id}`)
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: msg.info.id })
|
||||
}
|
||||
@@ -549,11 +579,15 @@ export namespace Session {
|
||||
for (const part of removeParts) {
|
||||
await Storage.remove(`session/part/${input.sessionID}/${last.info.id}/${part.id}`)
|
||||
await Bus.publish(MessageV2.Event.PartRemoved, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: last.info.id,
|
||||
partID: part.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
await update(input.sessionID, (draft) => {
|
||||
draft.revert = undefined
|
||||
})
|
||||
}
|
||||
|
||||
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
@@ -611,7 +645,9 @@ export namespace Session {
|
||||
.then((result) => {
|
||||
if (result.text)
|
||||
return Session.update(input.sessionID, (draft) => {
|
||||
draft.title = result.text
|
||||
const cleaned = result.text.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
|
||||
const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
|
||||
draft.title = title.trim()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
@@ -626,7 +662,6 @@ export namespace Session {
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
})(),
|
||||
)
|
||||
system.push(...(mode.prompt ? [mode.prompt] : SystemPrompt.provider(input.modelID)))
|
||||
system.push(...(await SystemPrompt.environment()))
|
||||
system.push(...(await SystemPrompt.custom()))
|
||||
// max 2 system prompt messages for caching purposes
|
||||
@@ -661,10 +696,13 @@ export namespace Session {
|
||||
|
||||
const processor = createProcessor(assistantMsg, model.info)
|
||||
|
||||
for (const item of await Provider.tools(input.providerID)) {
|
||||
if (mode.tools[item.id] === false) continue
|
||||
if (input.tools?.[item.id] === false) continue
|
||||
if (session.parentID && item.id === "task") continue
|
||||
const enabledTools = pipe(
|
||||
mode.tools,
|
||||
mergeDeep(ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
|
||||
if (enabledTools[item.id] === false) continue
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
description: item.description,
|
||||
@@ -730,7 +768,11 @@ export namespace Session {
|
||||
}
|
||||
|
||||
const stream = streamText({
|
||||
onError() {},
|
||||
onError(e) {
|
||||
log.error("streamText error", {
|
||||
error: e,
|
||||
})
|
||||
},
|
||||
async prepareStep({ messages }) {
|
||||
const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed)
|
||||
if (queue.length) {
|
||||
@@ -765,6 +807,7 @@ export namespace Session {
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
mode: inputMode,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
@@ -776,7 +819,7 @@ export namespace Session {
|
||||
messages,
|
||||
}
|
||||
},
|
||||
maxRetries: 10,
|
||||
maxRetries: 3,
|
||||
maxOutputTokens: outputLimit,
|
||||
abortSignal: abort.signal,
|
||||
stopWhen: stepCountIs(1000),
|
||||
@@ -792,7 +835,9 @@ export namespace Session {
|
||||
),
|
||||
...MessageV2.toModelMessage(msgs),
|
||||
],
|
||||
temperature: model.info.temperature ? 0 : undefined,
|
||||
temperature: model.info.temperature
|
||||
? (mode.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
: undefined,
|
||||
tools: model.info.tool_call === false ? undefined : tools,
|
||||
model: wrapLanguageModel({
|
||||
model: model.language,
|
||||
@@ -848,7 +893,7 @@ export namespace Session {
|
||||
|
||||
case "tool-input-start":
|
||||
const part = await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
id: toolCalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "tool",
|
||||
@@ -992,15 +1037,16 @@ export namespace Session {
|
||||
}
|
||||
break
|
||||
|
||||
case "text":
|
||||
case "text-delta":
|
||||
if (currentText) {
|
||||
currentText.text += value.text
|
||||
await updatePart(currentText)
|
||||
if (currentText.text) await updatePart(currentText)
|
||||
}
|
||||
break
|
||||
|
||||
case "text-end":
|
||||
if (currentText && currentText.text) {
|
||||
if (currentText) {
|
||||
currentText.text = currentText.text.trimEnd()
|
||||
currentText.time = {
|
||||
start: Date.now(),
|
||||
end: Date.now(),
|
||||
@@ -1125,6 +1171,7 @@ export namespace Session {
|
||||
const session = await get(input.sessionID)
|
||||
revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track())
|
||||
await Snapshot.revert(patches)
|
||||
if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot)
|
||||
return update(input.sessionID, (draft) => {
|
||||
draft.revert = revert
|
||||
})
|
||||
|
||||
@@ -284,6 +284,7 @@ export namespace MessageV2 {
|
||||
PartRemoved: Bus.event(
|
||||
"message.part.removed",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
messageID: z.string(),
|
||||
partID: z.string(),
|
||||
}),
|
||||
|
||||
@@ -7,6 +7,7 @@ export namespace Mode {
|
||||
export const Info = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
temperature: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
@@ -22,13 +23,16 @@ export namespace Mode {
|
||||
export type Info = z.infer<typeof Info>
|
||||
const state = App.state("mode", async () => {
|
||||
const cfg = await Config.get()
|
||||
const model = cfg.model ? Provider.parseModel(cfg.model) : undefined
|
||||
const result: Record<string, Info> = {
|
||||
build: {
|
||||
model,
|
||||
name: "build",
|
||||
tools: {},
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
model,
|
||||
tools: {
|
||||
write: false,
|
||||
edit: false,
|
||||
@@ -45,11 +49,9 @@ export namespace Mode {
|
||||
tools: {},
|
||||
}
|
||||
item.name = key
|
||||
const model = value.model ?? cfg.model
|
||||
if (model) {
|
||||
item.model = Provider.parseModel(model)
|
||||
}
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
if (value.prompt) item.prompt = value.prompt
|
||||
if (value.temperature) item.temperature = value.temperature
|
||||
if (value.tools)
|
||||
item.tools = {
|
||||
...value.tools,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are opencode, an autonomous agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user.
|
||||
You are opencode, an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user.
|
||||
|
||||
Your thinking should be thorough and so it's fine if it's very long. However, avoid unnecessary repetition and verbosity. You should be concise, but thorough.
|
||||
|
||||
@@ -6,25 +6,26 @@ You MUST iterate and keep going until the problem is solved.
|
||||
|
||||
You have everything you need to resolve this problem. I want you to fully solve this autonomously before coming back to me.
|
||||
|
||||
Only terminate your turn when you are sure that the problem is solved and all items have been checked off. Use the TodoWrite and TodoRead tools to track and manage steps. Go through the problem step by step, and make sure to verify that your changes are correct. Once each step is finished mark it as completed with the TodoWrite tool. NEVER end your turn without having truly and completely solved the problem, use the TodoRead tool to make sure all steps are complete, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn. If a step is impossible to complete, mark it as cancelled using the TodoWrite tool.
|
||||
Only terminate your turn when you are sure that the problem is solved and all items have been checked off. Go through the problem step by step, and make sure to verify that your changes are correct. NEVER end your turn without having truly and completely solved the problem, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn.
|
||||
|
||||
THE PROBLEM CAN NOT BE SOLVED WITHOUT EXTENSIVE INTERNET RESEARCH.
|
||||
|
||||
You must use the webfetch tool to recursively gather all information from URLs provided to you by the user, as well as any links you find in the content of those pages.
|
||||
You must use the webfetch tool to recursively gather all information from URL's provided to you by the user, as well as any links you find in the content of those pages.
|
||||
|
||||
Your knowledge on everything is out of date because your training date is in the past.
|
||||
Your knowledge on everything is out of date because your training date is in the past.
|
||||
|
||||
You CANNOT successfully complete this task without using Bing to verify your understanding of third party packages and dependencies is up to date. You must use the webfetch tool to search bing for how to properly use libraries, packages, frameworks, dependencies, etc. every single time you install or implement one. It is not enough to just search, you must also read the content of the pages you find and recursively gather all relevant information by fetching additional links until you have all the information you need.
|
||||
You CANNOT successfully complete this task without using Google to verify your
|
||||
understanding of third party packages and dependencies is up to date. You must use the webfetch tool to search google for how to properly use libraries, packages, frameworks, dependencies, etc. every single time you install or implement one. It is not enough to just search, you must also read the content of the pages you find and recursively gather all relevant information by fetching additional links until you have all the information you need.
|
||||
|
||||
If the user request is "resume" or "continue" or "try again",use the TodoRead tool to find the next pending step. Continue from that step, and do not hand back control to the user until the entire todo list is complete and all steps are marked as complete or cancelled. Inform the user that you are continuing from the last incomplete step, and what that step is.
|
||||
Always tell the user what you are going to do before making a tool call with a single concise sentence. This will help them understand what you are doing and why.
|
||||
|
||||
Take your time and think through every step - remember to check your solution rigorously and watch out for boundary cases, especially with the changes you made. Use the sequential thinking tool if available. Your solution must be perfect. If not, continue working on it. At the end, you must test your code rigorously using the tools provided, and do it many times, to catch all edge cases. If it is not robust, update the plan and iterate more and make it perfect. Failing to test your code sufficiently rigorously is the NUMBER ONE failure mode on these types of tasks; run the build, and verify that the changes you made actually build; make sure you handle all edge cases, and run existing tests if they are provided.
|
||||
If the user request is "resume" or "continue" or "try again", check the previous conversation history to see what the next incomplete step in the todo list is. Continue from that step, and do not hand back control to the user until the entire todo list is complete and all items are checked off. Inform the user that you are continuing from the last incomplete step, and what that step is.
|
||||
|
||||
You MUST plan extensively before each tool call, and reflect extensively on the outcomes of the previous tool calls. DO NOT do this entire process by making tool calls only, as this can impair your ability to solve the problem and think insightfully.
|
||||
Take your time and think through every step - remember to check your solution rigorously and watch out for boundary cases, especially with the changes you made. Use the sequential thinking tool if available. Your solution must be perfect. If not, continue working on it. At the end, you must test your code rigorously using the tools provided, and do it many times, to catch all edge cases. If it is not robust, iterate more and make it perfect. Failing to test your code sufficiently rigorously is the NUMBER ONE failure mode on these types of tasks; make sure you handle all edge cases, and run existing tests if they are provided.
|
||||
|
||||
You MUST keep working until the problem is completely solved, and all steps in the todo list are complete. Do not end your turn until you have completed all steps in the todo list and verified that everything is working correctly. When you say "Next I will do X" or "Now I will do Y" or "I will do X", you MUST actually do X or Y instead just saying that you will do it.
|
||||
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
|
||||
|
||||
You MUST use the ToolRead tool to verify that all steps are complete or cancelled before ending your turn. If any steps are incomplete, you MUST continue working on them until they are all complete.
|
||||
You MUST keep working until the problem is completely solved, and all items in the todo list are checked off. Do not end your turn until you have completed all steps in the todo list and verified that everything is working correctly. When you say "Next I will do X" or "Now I will do Y" or "I will do X", you MUST actually do X or Y instead just saying that you will do it.
|
||||
|
||||
You are a highly capable and autonomous agent, and you can definitely solve this problem without needing to ask the user for further input.
|
||||
|
||||
@@ -38,7 +39,7 @@ You are a highly capable and autonomous agent, and you can definitely solve this
|
||||
- What are the dependencies and interactions with other parts of the code?
|
||||
3. Investigate the codebase. Explore relevant files, search for key functions, and gather context.
|
||||
4. Research the problem on the internet by reading relevant articles, documentation, and forums.
|
||||
5. Develop a clear, step-by-step plan. Break down the fix into manageable, incremental steps. Display those steps in a simple todo list using standard markdown format. Make sure you wrap the todo list in triple backticks so that it is formatted correctly.
|
||||
5. Develop a clear, step-by-step plan. Break down the fix into manageable, incremental steps. Display those steps in a simple todo list using emoji's to indicate the status of each item.
|
||||
6. Implement the fix incrementally. Make small, testable code changes.
|
||||
7. Debug as needed. Use debugging techniques to isolate and resolve issues.
|
||||
8. Test frequently. Run tests after each change to verify correctness.
|
||||
@@ -49,12 +50,12 @@ Refer to the detailed sections below for more information on each step.
|
||||
|
||||
## 1. Fetch Provided URLs
|
||||
- If the user provides a URL, use the `webfetch` tool to retrieve the content of the provided URL.
|
||||
- After fetching, review the content returned by the fetch tool.
|
||||
- After fetching, review the content returned by the webfetch tool.
|
||||
- If you find any additional URLs or links that are relevant, use the `webfetch` tool again to retrieve those links.
|
||||
- Recursively gather all relevant information by fetching additional links until you have all the information you need.
|
||||
|
||||
## 2. Deeply Understand the Problem
|
||||
Carefully read the issue and think hard about a plan to solve it before coding. Use the sequential thinking tool if available.
|
||||
Carefully read the issue and think hard about a plan to solve it before coding.
|
||||
|
||||
## 3. Codebase Investigation
|
||||
- Explore relevant files and directories.
|
||||
@@ -64,49 +65,49 @@ Carefully read the issue and think hard about a plan to solve it before coding.
|
||||
- Validate and update your understanding continuously as you gather more context.
|
||||
|
||||
## 4. Internet Research
|
||||
- Use the `webfetch` tool to search bing by fetching the URL `https://www.bing.com/search?q=your+search+query`.
|
||||
- Use the `webfetch` tool to search google by fetching the URL `https://www.google.com/search?q=your+search+query`.
|
||||
- After fetching, review the content returned by the fetch tool.
|
||||
- If you find any additional URLs or links that are relevant, use the `webfetch` tool again to retrieve those links.
|
||||
- Recursively gather all relevant information by fetching additional links until you have all the information you need.
|
||||
- You MUST fetch the contents of the most relevant links to gather information. Do not rely on the summary that you find in the search results.
|
||||
- As you fetch each link, read the content thoroughly and fetch any additional links that you find withhin the content that are relevant to the problem.
|
||||
- Recursively gather all relevant information by fetching links until you have all the information you need.
|
||||
|
||||
## 5. Develop a Detailed Plan
|
||||
## 5. Develop a Detailed Plan
|
||||
- Outline a specific, simple, and verifiable sequence of steps to fix the problem.
|
||||
- Add steps using the TodoWrite tool.
|
||||
- Each time you complete a step, mark it as complete using the TodoWrite tool.
|
||||
- Each time you check off a step, use the TodoRead tool and display the updated todo list to the user in markdown format.
|
||||
- You MUST continue on to the next step after checking off a step instead of ending your turn and asking the user what they want to do next.
|
||||
- You may only end your turn when all steps in the todo list are marked as complete or cancelled.
|
||||
- Create a todo list in markdown format to track your progress.
|
||||
- Each time you complete a step, check it off using `[x]` syntax.
|
||||
- Each time you check off a step, display the updated todo list to the user.
|
||||
- Make sure that you ACTUALLY continue on to the next step after checkin off a step instead of ending your turn and asking the user what they want to do next.
|
||||
|
||||
## 6. Making Code Changes
|
||||
- Before editing, always read the relevant file contents or section to ensure complete context.
|
||||
- Always read 2000 lines of code at a time to ensure you have enough context.
|
||||
- If a patch is not applied correctly, attempt to reapply it.
|
||||
- Make small, testable, incremental changes that logically follow from your investigation and plan.
|
||||
- When using the edit tool, include 3-5 lines of unchanged code before and after the string you want to replace, to make it unambiguous which part of the file should be edited.
|
||||
- If a patch or edit is not applied correctly, attempt to reapply it.
|
||||
- Always validate that your changes build and pass tests after each change.
|
||||
- If the build fails or test fail, debug why before proceeding, update the plan as needed.
|
||||
- Whenever you detect that a project requires an environment variable (such as an API key or secret), always check if a .env file exists in the project root. If it does not exist, automatically create a .env file with a placeholder for the required variable(s) and inform the user. Do this proactively, without waiting for the user to request it.
|
||||
|
||||
## 7. Debugging
|
||||
- Use the `lsp_diagnostics` tool to check for any problems in the code.
|
||||
- Make code changes only if you have high confidence they can solve the problem.
|
||||
- When debugging, try to determine the root cause rather than addressing symptoms.
|
||||
- Debug for as long as needed to identify the root cause and identify a fix.
|
||||
- Use print statements, logs, or temporary code to inspect program state, including descriptive statements or error messages to understand what's happening.
|
||||
- To test hypotheses, you can also add test statements or functions.
|
||||
- Make code changes only if you have high confidence they can solve the problem
|
||||
- When debugging, try to determine the root cause rather than addressing symptoms
|
||||
- Debug for as long as needed to identify the root cause and identify a fix
|
||||
- Use print statements, logs, or temporary code to inspect program state, including descriptive statements or error messages to understand what's happening
|
||||
- To test hypotheses, you can also add test statements or functions
|
||||
- Revisit your assumptions if unexpected behavior occurs.
|
||||
|
||||
# How to create a Todo List
|
||||
Use the following format to show the todo list:
|
||||
```markdown
|
||||
- [ ] Step 1: Description of the first step
|
||||
- [ ] Step 2: Description of the second step
|
||||
- [ ] Step 3: Description of the third step
|
||||
```
|
||||
Do not ever use HTML tags or any other formatting for the todo list, as it will not be rendered correctly. Always use the markdown format shown above.
|
||||
Use the following format to create a todo list:
|
||||
```markdown
|
||||
- [ ] Step 1: Description of the first step
|
||||
- [ ] Step 2: Description of the second step
|
||||
- [ ] Step 3: Description of the third step
|
||||
```
|
||||
|
||||
Do not ever use HTML tags or any other formatting for the todo list, as it will not be rendered correctly. Always use the markdown format shown above. Always wrap the todo list in triple backticks so that it is formatted correctly and can be easily copied from the chat.
|
||||
|
||||
Always show the completed todo list to the user as the last item in your message, so that they can see that you have addressed all of the steps.
|
||||
|
||||
|
||||
# Communication Guidelines
|
||||
Always communicate clearly and concisely in a casual, friendly yet professional tone.
|
||||
|
||||
Always communicate clearly and concisely in a casual, friendly yet professional tone.
|
||||
<examples>
|
||||
"Let me fetch the URL you provided to gather more information."
|
||||
"Ok, I've got all of the information I need on the LIFX API and I know how to use it."
|
||||
@@ -115,3 +116,44 @@ Always communicate clearly and concisely in a casual, friendly yet professional
|
||||
"OK! Now let's run the tests to make sure everything is working correctly."
|
||||
"Whelp - I see we have some problems. Let's fix those up."
|
||||
</examples>
|
||||
|
||||
- Respond with clear, direct answers. Use bullet points and code blocks for structure. - Avoid unnecessary explanations, repetition, and filler.
|
||||
- Always write code directly to the correct files.
|
||||
- Do not display code to the user unless they specifically ask for it.
|
||||
- Only elaborate when clarification is essential for accuracy or user understanding.
|
||||
|
||||
# Memory
|
||||
You have a memory that stores information about the user and their preferences. This memory is used to provide a more personalized experience. You can access and update this memory as needed. The memory is stored in a file called `.github/instructions/memory.instruction.md`. If the file is empty, you'll need to create it.
|
||||
|
||||
When creating a new memory file, you MUST include the following front matter at the top of the file:
|
||||
```yaml
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
```
|
||||
|
||||
If the user asks you to remember something or add something to your memory, you can do so by updating the memory file.
|
||||
|
||||
# Reading Files and Folders
|
||||
|
||||
**Always check if you have already read a file, folder, or workspace structure before reading it again.**
|
||||
|
||||
- If you have already read the content and it has not changed, do NOT re-read it.
|
||||
- Only re-read files or folders if:
|
||||
- You suspect the content has changed since your last read.
|
||||
- You have made edits to the file or folder.
|
||||
- You encounter an error that suggests the context may be stale or incomplete.
|
||||
- Use your internal memory and previous context to avoid redundant reads.
|
||||
- This will save time, reduce unnecessary operations, and make your workflow more efficient.
|
||||
|
||||
# Writing Prompts
|
||||
If you are asked to write a prompt, you should always generate the prompt in markdown format.
|
||||
|
||||
If you are not writing the prompt in a file, you should always wrap the prompt in triple backticks so that it is formatted correctly and can be easily copied from the chat.
|
||||
|
||||
Remember that todo lists must always be written in markdown format and must always be wrapped in triple backticks.
|
||||
|
||||
# Git
|
||||
If the user tells you to stage and commit, you may do so.
|
||||
|
||||
You are NEVER allowed to stage and commit files automatically.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<system-reminder>
|
||||
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received (for example, to make edits).
|
||||
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received (for example, to make edits).
|
||||
</system-reminder>
|
||||
|
||||
109
packages/opencode/src/session/prompt/qwen.txt
Normal file
109
packages/opencode/src/session/prompt/qwen.txt
Normal file
@@ -0,0 +1,109 @@
|
||||
You are opencode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
|
||||
IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working on files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse.
|
||||
IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code).
|
||||
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
|
||||
|
||||
If the user asks for help or wants to give feedback inform them of the following:
|
||||
- /help: Get help with using opencode
|
||||
- To give feedback, users should report the issue at https://github.com/sst/opencode/issues
|
||||
|
||||
When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai
|
||||
|
||||
# Tone and style
|
||||
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
|
||||
Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
|
||||
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
||||
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
|
||||
Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
||||
IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
|
||||
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
|
||||
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". Here are some examples to demonstrate appropriate verbosity:
|
||||
<example>
|
||||
user: 2 + 2
|
||||
assistant: 4
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: what is 2+2?
|
||||
assistant: 4
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: is 11 a prime number?
|
||||
assistant: Yes
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: what command should I run to list files in the current directory?
|
||||
assistant: ls
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: what command should I run to watch files in the current directory?
|
||||
assistant: [use the ls tool to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files]
|
||||
npm run dev
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: How many golf balls fit inside a jetta?
|
||||
assistant: 150000
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: what files are in the directory src/?
|
||||
assistant: [runs ls and sees foo.c, bar.c, baz.c]
|
||||
user: which file contains the implementation of foo?
|
||||
assistant: src/foo.c
|
||||
</example>
|
||||
|
||||
<example>
|
||||
user: write tests for new feature
|
||||
assistant: [uses grep and glob search tools to find where similar tests are defined, uses concurrent read file tool use blocks in one tool call to read relevant files at the same time, uses edit file tool to write new tests]
|
||||
</example>
|
||||
|
||||
# Proactiveness
|
||||
You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between:
|
||||
1. Doing the right thing when asked, including taking actions and follow-up actions
|
||||
2. Not surprising the user with actions you take without asking
|
||||
For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
|
||||
3. Do not add additional code explanation summary unless requested by the user. After working on a file, just stop, rather than providing an explanation of what you did.
|
||||
|
||||
# Following conventions
|
||||
When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns.
|
||||
- NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language).
|
||||
- When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions.
|
||||
- When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic.
|
||||
- Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository.
|
||||
|
||||
# Code style
|
||||
- IMPORTANT: DO NOT ADD ***ANY*** COMMENTS unless asked
|
||||
|
||||
# Doing tasks
|
||||
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
|
||||
- Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
|
||||
- Implement the solution using all tools available to you
|
||||
- Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach.
|
||||
- VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (eg. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to AGENTS.md so that you will know to run it next time.
|
||||
NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
|
||||
|
||||
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result.
|
||||
|
||||
# Tool usage policy
|
||||
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
||||
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
|
||||
|
||||
You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
|
||||
|
||||
IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working on files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse.
|
||||
IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code).
|
||||
|
||||
# Code References
|
||||
|
||||
When referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.
|
||||
|
||||
<example>
|
||||
user: Where are errors from the client handled?
|
||||
assistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.
|
||||
</example>
|
||||
|
||||
@@ -10,7 +10,7 @@ You are generating titles for a coding assistant conversation.
|
||||
- Max 50 chars, single line
|
||||
- Focus on the specific action or question
|
||||
- Keep technical terms, numbers, and filenames exactly as written
|
||||
- Preserve HTTP status codes (401, 404, 500, etc) as numbers
|
||||
- Preserve HTTP status codes (401, 404, 500, etc.) as numbers
|
||||
- For file references, include the filename
|
||||
- Avoid filler words: the, this, my, a, an, properly
|
||||
- NEVER assume their tech stack or domain
|
||||
|
||||
@@ -7,6 +7,7 @@ import path from "path"
|
||||
import os from "os"
|
||||
|
||||
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
|
||||
import PROMPT_ANTHROPIC_WITHOUT_TODO from "./prompt/qwen.txt"
|
||||
import PROMPT_BEAST from "./prompt/beast.txt"
|
||||
import PROMPT_GEMINI from "./prompt/gemini.txt"
|
||||
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
||||
@@ -21,7 +22,8 @@ export namespace SystemPrompt {
|
||||
export function provider(modelID: string) {
|
||||
if (modelID.includes("gpt-") || modelID.includes("o1") || modelID.includes("o3")) return [PROMPT_BEAST]
|
||||
if (modelID.includes("gemini-")) return [PROMPT_GEMINI]
|
||||
return [PROMPT_ANTHROPIC]
|
||||
if (modelID.includes("claude")) return [PROMPT_ANTHROPIC]
|
||||
return [PROMPT_ANTHROPIC_WITHOUT_TODO]
|
||||
}
|
||||
|
||||
export async function environment() {
|
||||
|
||||
@@ -94,6 +94,13 @@ export namespace Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
export async function diff(hash: string) {
|
||||
const app = App.info()
|
||||
const git = gitdir()
|
||||
const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(app.path.root).text()
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
function gitdir() {
|
||||
const app = App.info()
|
||||
return path.join(app.path.data, "snapshots")
|
||||
|
||||
@@ -3,16 +3,22 @@ import { Tool } from "./tool"
|
||||
import DESCRIPTION from "./bash.txt"
|
||||
import { App } from "../app/app"
|
||||
|
||||
// import Parser from "tree-sitter"
|
||||
// import Bash from "tree-sitter-bash"
|
||||
// import { Config } from "../config/config"
|
||||
|
||||
const MAX_OUTPUT_LENGTH = 30000
|
||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||
|
||||
export const BashTool = Tool.define({
|
||||
id: "bash",
|
||||
// const parser = new Parser()
|
||||
// parser.setLanguage(Bash.language as any)
|
||||
|
||||
export const BashTool = Tool.define("bash", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
command: z.string().describe("The command to execute"),
|
||||
timeout: z.number().min(0).max(MAX_TIMEOUT).describe("Optional timeout in milliseconds").optional(),
|
||||
timeout: z.number().describe("Optional timeout in milliseconds").optional(),
|
||||
description: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -21,10 +27,83 @@ export const BashTool = Tool.define({
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
||||
const app = App.info()
|
||||
/*
|
||||
const _cfg = await Config.get()
|
||||
const tree = parser.parse(params.command)
|
||||
const permissions = (() => {
|
||||
const value = cfg.permission?.bash
|
||||
if (!value)
|
||||
return {
|
||||
"*": "allow",
|
||||
}
|
||||
if (typeof value === "string")
|
||||
return {
|
||||
"*": value,
|
||||
}
|
||||
return value
|
||||
})()
|
||||
|
||||
let needsAsk = false
|
||||
for (const node of tree.rootNode.descendantsOfType("command")) {
|
||||
const command = []
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i)
|
||||
if (!child) continue
|
||||
if (
|
||||
child.type !== "command_name" &&
|
||||
child.type !== "word" &&
|
||||
child.type !== "string" &&
|
||||
child.type !== "raw_string" &&
|
||||
child.type !== "concatenation"
|
||||
) {
|
||||
continue
|
||||
}
|
||||
command.push(child.text)
|
||||
}
|
||||
|
||||
// not an exhaustive list, but covers most common cases
|
||||
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
|
||||
for (const arg of command.slice(1)) {
|
||||
if (arg.startsWith("-")) continue
|
||||
const resolved = path.resolve(app.path.cwd, arg)
|
||||
if (!Filesystem.contains(app.path.cwd, resolved)) {
|
||||
throw new Error(
|
||||
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// always allow cd if it passes above check
|
||||
if (!needsAsk && command[0] !== "cd") {
|
||||
const ask = (() => {
|
||||
for (const [pattern, value] of Object.entries(permissions)) {
|
||||
if (new Bun.Glob(pattern).match(node.text)) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "ask"
|
||||
})()
|
||||
if (ask === "ask") needsAsk = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsAsk) {
|
||||
await Permission.ask({
|
||||
id: "bash",
|
||||
sessionID: ctx.sessionID,
|
||||
title: params.command,
|
||||
metadata: {
|
||||
command: params.command,
|
||||
},
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
const process = Bun.spawn({
|
||||
cmd: ["bash", "-c", params.command],
|
||||
cwd: App.info().path.cwd,
|
||||
cwd: app.path.cwd,
|
||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||
signal: ctx.abort,
|
||||
timeout: timeout,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-23-25.ts
|
||||
// https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts
|
||||
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts
|
||||
|
||||
import { z } from "zod"
|
||||
import * as path from "path"
|
||||
import { Tool } from "./tool"
|
||||
@@ -13,9 +14,10 @@ import { App } from "../app/app"
|
||||
import { File } from "../file"
|
||||
import { Bus } from "../bus"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Config } from "../config/config"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
|
||||
export const EditTool = Tool.define({
|
||||
id: "edit",
|
||||
export const EditTool = Tool.define("edit", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||
@@ -34,17 +36,22 @@ export const EditTool = Tool.define({
|
||||
|
||||
const app = App.info()
|
||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
||||
}
|
||||
|
||||
await Permission.ask({
|
||||
id: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
title: "Edit this file: " + filepath,
|
||||
metadata: {
|
||||
filePath: filepath,
|
||||
oldString: params.oldString,
|
||||
newString: params.newString,
|
||||
},
|
||||
})
|
||||
const cfg = await Config.get()
|
||||
if (cfg.permission?.edit === "ask")
|
||||
await Permission.ask({
|
||||
id: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
title: "Edit this file: " + filepath,
|
||||
metadata: {
|
||||
filePath: filepath,
|
||||
oldString: params.oldString,
|
||||
newString: params.newString,
|
||||
},
|
||||
})
|
||||
|
||||
let contentOld = ""
|
||||
let contentNew = ""
|
||||
|
||||
@@ -5,8 +5,7 @@ import { App } from "../app/app"
|
||||
import DESCRIPTION from "./glob.txt"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
|
||||
export const GlobTool = Tool.define({
|
||||
id: "glob",
|
||||
export const GlobTool = Tool.define("glob", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
pattern: z.string().describe("The glob pattern to match files against"),
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Ripgrep } from "../file/ripgrep"
|
||||
|
||||
import DESCRIPTION from "./grep.txt"
|
||||
|
||||
export const GrepTool = Tool.define({
|
||||
id: "grep",
|
||||
export const GrepTool = Tool.define("grep", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
pattern: z.string().describe("The regex pattern to search for in file contents"),
|
||||
|
||||
@@ -33,8 +33,7 @@ export const IGNORE_PATTERNS = [
|
||||
|
||||
const LIMIT = 100
|
||||
|
||||
export const ListTool = Tool.define({
|
||||
id: "list",
|
||||
export const ListTool = Tool.define("list", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
path: z.string().describe("The absolute path to the directory to list (must be absolute, not relative)").optional(),
|
||||
|
||||
@@ -5,8 +5,7 @@ import { LSP } from "../lsp"
|
||||
import { App } from "../app/app"
|
||||
import DESCRIPTION from "./lsp-diagnostics.txt"
|
||||
|
||||
export const LspDiagnosticTool = Tool.define({
|
||||
id: "lsp_diagnostics",
|
||||
export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
path: z.string().describe("The path to the file to get diagnostics."),
|
||||
|
||||
@@ -5,8 +5,7 @@ import { LSP } from "../lsp"
|
||||
import { App } from "../app/app"
|
||||
import DESCRIPTION from "./lsp-hover.txt"
|
||||
|
||||
export const LspHoverTool = Tool.define({
|
||||
id: "lsp_hover",
|
||||
export const LspHoverTool = Tool.define("lsp_hover", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
file: z.string().describe("The path to the file to get diagnostics."),
|
||||
|
||||
@@ -5,8 +5,7 @@ import DESCRIPTION from "./multiedit.txt"
|
||||
import path from "path"
|
||||
import { App } from "../app/app"
|
||||
|
||||
export const MultiEditTool = Tool.define({
|
||||
id: "multiedit",
|
||||
export const MultiEditTool = Tool.define("multiedit", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||
@@ -22,7 +21,7 @@ export const MultiEditTool = Tool.define({
|
||||
.describe("Array of edit operations to perform sequentially on the file"),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const tool = await EditTool()
|
||||
const tool = await EditTool.init()
|
||||
const results = []
|
||||
for (const [, edit] of params.edits.entries()) {
|
||||
const result = await tool.execute(
|
||||
|
||||
@@ -210,8 +210,7 @@ async function applyCommit(
|
||||
}
|
||||
}
|
||||
|
||||
export const PatchTool = Tool.define({
|
||||
id: "patch",
|
||||
export const PatchTool = Tool.define("patch", {
|
||||
description: DESCRIPTION,
|
||||
parameters: PatchParams,
|
||||
execute: async (params, ctx) => {
|
||||
|
||||
@@ -6,28 +6,32 @@ import { LSP } from "../lsp"
|
||||
import { FileTime } from "../file/time"
|
||||
import DESCRIPTION from "./read.txt"
|
||||
import { App } from "../app/app"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
|
||||
const DEFAULT_READ_LIMIT = 2000
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
|
||||
export const ReadTool = Tool.define({
|
||||
id: "read",
|
||||
export const ReadTool = Tool.define("read", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The path to the file to read"),
|
||||
offset: z.number().describe("The line number to start reading from (0-based)").optional(),
|
||||
limit: z.number().describe("The number of lines to read (defaults to 2000)").optional(),
|
||||
offset: z.coerce.number().describe("The line number to start reading from (0-based)").optional(),
|
||||
limit: z.coerce.number().describe("The number of lines to read (defaults to 2000)").optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
let filePath = params.filePath
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = path.join(process.cwd(), filePath)
|
||||
let filepath = params.filePath
|
||||
if (!path.isAbsolute(filepath)) {
|
||||
filepath = path.join(process.cwd(), filepath)
|
||||
}
|
||||
const app = App.info()
|
||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
||||
}
|
||||
|
||||
const file = Bun.file(filePath)
|
||||
const file = Bun.file(filepath)
|
||||
if (!(await file.exists())) {
|
||||
const dir = path.dirname(filePath)
|
||||
const base = path.basename(filePath)
|
||||
const dir = path.dirname(filepath)
|
||||
const base = path.basename(filepath)
|
||||
|
||||
const dirEntries = fs.readdirSync(dir)
|
||||
const suggestions = dirEntries
|
||||
@@ -39,16 +43,18 @@ export const ReadTool = Tool.define({
|
||||
.slice(0, 3)
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
throw new Error(`File not found: ${filePath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`)
|
||||
throw new Error(`File not found: ${filepath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`)
|
||||
}
|
||||
|
||||
throw new Error(`File not found: ${filePath}`)
|
||||
throw new Error(`File not found: ${filepath}`)
|
||||
}
|
||||
|
||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||
const offset = params.offset || 0
|
||||
const isImage = isImageFile(filePath)
|
||||
const isImage = isImageFile(filepath)
|
||||
if (isImage) throw new Error(`This is an image file of type: ${isImage}\nUse a different tool to process images`)
|
||||
const isBinary = await isBinaryFile(file)
|
||||
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
|
||||
const lines = await file.text().then((text) => text.split("\n"))
|
||||
const raw = lines.slice(offset, offset + limit).map((line) => {
|
||||
return line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + "..." : line
|
||||
@@ -67,11 +73,11 @@ export const ReadTool = Tool.define({
|
||||
output += "\n</file>"
|
||||
|
||||
// just warms the lsp client
|
||||
LSP.touchFile(filePath, false)
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
LSP.touchFile(filepath, false)
|
||||
FileTime.read(ctx.sessionID, filepath)
|
||||
|
||||
return {
|
||||
title: path.relative(App.info().path.root, filePath),
|
||||
title: path.relative(App.info().path.root, filepath),
|
||||
output,
|
||||
metadata: {
|
||||
preview,
|
||||
@@ -100,3 +106,14 @@ function isImageFile(filePath: string): string | false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function isBinaryFile(file: Bun.BunFile): Promise<boolean> {
|
||||
const buffer = await file.arrayBuffer()
|
||||
const bytes = new Uint8Array(buffer.slice(0, 512)) // Check first 512 bytes
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] === 0) return true // Null byte indicates binary
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
177
packages/opencode/src/tool/registry.ts
Normal file
177
packages/opencode/src/tool/registry.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import z from "zod"
|
||||
import { BashTool } from "./bash"
|
||||
import { EditTool } from "./edit"
|
||||
import { GlobTool } from "./glob"
|
||||
import { GrepTool } from "./grep"
|
||||
import { ListTool } from "./ls"
|
||||
import { PatchTool } from "./patch"
|
||||
import { ReadTool } from "./read"
|
||||
import { TaskTool } from "./task"
|
||||
import { TodoWriteTool, TodoReadTool } from "./todo"
|
||||
import { WebFetchTool } from "./webfetch"
|
||||
import { WriteTool } from "./write"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
const ALL = [
|
||||
BashTool,
|
||||
EditTool,
|
||||
WebFetchTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ListTool,
|
||||
PatchTool,
|
||||
ReadTool,
|
||||
WriteTool,
|
||||
TodoWriteTool,
|
||||
TodoReadTool,
|
||||
TaskTool,
|
||||
]
|
||||
|
||||
export function ids() {
|
||||
return ALL.map((t) => t.id)
|
||||
}
|
||||
|
||||
export async function tools(providerID: string, _modelID: string) {
|
||||
const result = await Promise.all(
|
||||
ALL.map(async (t) => ({
|
||||
id: t.id,
|
||||
...(await t.init()),
|
||||
})),
|
||||
)
|
||||
|
||||
if (providerID === "openai") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
}))
|
||||
}
|
||||
|
||||
if (providerID === "azure") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
}))
|
||||
}
|
||||
|
||||
if (providerID === "google") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: sanitizeGeminiParameters(t.parameters),
|
||||
}))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function enabled(_providerID: string, modelID: string): Record<string, boolean> {
|
||||
if (modelID.toLowerCase().includes("claude")) {
|
||||
return {
|
||||
patch: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
modelID.toLowerCase().includes("qwen") ||
|
||||
modelID.includes("gpt-") ||
|
||||
modelID.includes("o1") ||
|
||||
modelID.includes("o3") ||
|
||||
modelID.includes("codex")
|
||||
) {
|
||||
return {
|
||||
patch: false,
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
|
||||
if (!schema || visited.has(schema)) {
|
||||
return schema
|
||||
}
|
||||
visited.add(schema)
|
||||
|
||||
if (schema instanceof z.ZodDefault) {
|
||||
const innerSchema = schema.removeDefault()
|
||||
// Handle Gemini's incompatibility with `default` on `anyOf` (unions).
|
||||
if (innerSchema instanceof z.ZodUnion) {
|
||||
// The schema was `z.union(...).default(...)`, which is not allowed.
|
||||
// We strip the default and return the sanitized union.
|
||||
return sanitizeGeminiParameters(innerSchema, visited)
|
||||
}
|
||||
// Otherwise, the default is on a regular type, which is allowed.
|
||||
// We recurse on the inner type and then re-apply the default.
|
||||
return sanitizeGeminiParameters(innerSchema, visited).default(schema._def.defaultValue())
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodOptional) {
|
||||
return z.optional(sanitizeGeminiParameters(schema.unwrap(), visited))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const newShape: Record<string, z.ZodTypeAny> = {}
|
||||
for (const [key, value] of Object.entries(schema.shape)) {
|
||||
newShape[key] = sanitizeGeminiParameters(value as z.ZodTypeAny, visited)
|
||||
}
|
||||
return z.object(newShape)
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return z.array(sanitizeGeminiParameters(schema.element, visited))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodUnion) {
|
||||
// This schema corresponds to `anyOf` in JSON Schema.
|
||||
// We recursively sanitize each option in the union.
|
||||
const sanitizedOptions = schema.options.map((option: z.ZodTypeAny) => sanitizeGeminiParameters(option, visited))
|
||||
return z.union(sanitizedOptions as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodString) {
|
||||
const newSchema = z.string({ description: schema.description })
|
||||
const safeChecks = ["min", "max", "length", "regex", "startsWith", "endsWith", "includes", "trim"]
|
||||
// rome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
;(newSchema._def as any).checks = (schema._def as z.ZodStringDef).checks.filter((check) =>
|
||||
safeChecks.includes(check.kind),
|
||||
)
|
||||
return newSchema
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
function optionalToNullable(schema: z.ZodTypeAny): z.ZodTypeAny {
|
||||
if (schema instanceof z.ZodObject) {
|
||||
const shape = schema.shape
|
||||
const newShape: Record<string, z.ZodTypeAny> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(shape)) {
|
||||
const zodValue = value as z.ZodTypeAny
|
||||
if (zodValue instanceof z.ZodOptional) {
|
||||
newShape[key] = zodValue.unwrap().nullable()
|
||||
} else {
|
||||
newShape[key] = optionalToNullable(zodValue)
|
||||
}
|
||||
}
|
||||
|
||||
return z.object(newShape)
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodArray) {
|
||||
return z.array(optionalToNullable(schema.element))
|
||||
}
|
||||
|
||||
if (schema instanceof z.ZodUnion) {
|
||||
return z.union(
|
||||
schema.options.map((option: z.ZodTypeAny) => optionalToNullable(option)) as [
|
||||
z.ZodTypeAny,
|
||||
z.ZodTypeAny,
|
||||
...z.ZodTypeAny[],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@ import { MessageV2 } from "../session/message-v2"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
export const TaskTool = Tool.define(async () => {
|
||||
export const TaskTool = Tool.define("task", async () => {
|
||||
const agents = await Agent.list()
|
||||
const description = DESCRIPTION.replace("{agents}", agents.map((a) => `- ${a.name}: ${a.description}`).join("\n"))
|
||||
return {
|
||||
id: "task",
|
||||
description,
|
||||
parameters: z.object({
|
||||
description: z.string().describe("A short (3-5 words) description of the task"),
|
||||
@@ -53,7 +52,10 @@ export const TaskTool = Tool.define(async () => {
|
||||
providerID: model.providerID,
|
||||
mode: msg.mode,
|
||||
system: agent.prompt,
|
||||
tools: agent.tools,
|
||||
tools: {
|
||||
...agent.tools,
|
||||
task: false,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -68,7 +70,7 @@ export const TaskTool = Tool.define(async () => {
|
||||
metadata: {
|
||||
summary: result.parts.filter((x) => x.type === "tool"),
|
||||
},
|
||||
output: result.parts.findLast((x) => x.type === "text")!.text,
|
||||
output: result.parts.findLast((x) => x.type === "text")?.text ?? "",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import DESCRIPTION_WRITE from "./todowrite.txt"
|
||||
import { App } from "../app/app"
|
||||
|
||||
const TodoInfo = z.object({
|
||||
content: z.string().min(1).describe("Brief description of the task"),
|
||||
content: z.string().describe("Brief description of the task"),
|
||||
status: z.enum(["pending", "in_progress", "completed", "cancelled"]).describe("Current status of the task"),
|
||||
priority: z.enum(["high", "medium", "low"]).describe("Priority level of the task"),
|
||||
id: z.string().describe("Unique identifier for the todo item"),
|
||||
@@ -18,8 +18,7 @@ const state = App.state("todo-tool", () => {
|
||||
return todos
|
||||
})
|
||||
|
||||
export const TodoWriteTool = Tool.define({
|
||||
id: "todowrite",
|
||||
export const TodoWriteTool = Tool.define("todowrite", {
|
||||
description: DESCRIPTION_WRITE,
|
||||
parameters: z.object({
|
||||
todos: z.array(TodoInfo).describe("The updated todo list"),
|
||||
@@ -37,8 +36,7 @@ export const TodoWriteTool = Tool.define({
|
||||
},
|
||||
})
|
||||
|
||||
export const TodoReadTool = Tool.define({
|
||||
id: "todoread",
|
||||
export const TodoReadTool = Tool.define("todoread", {
|
||||
description: "Use this tool to read your todo list",
|
||||
parameters: z.object({}),
|
||||
async execute(_params, opts) {
|
||||
|
||||
@@ -12,21 +12,30 @@ export namespace Tool {
|
||||
}
|
||||
export interface Info<Parameters extends StandardSchemaV1 = StandardSchemaV1, M extends Metadata = Metadata> {
|
||||
id: string
|
||||
description: string
|
||||
parameters: Parameters
|
||||
execute(
|
||||
args: StandardSchemaV1.InferOutput<Parameters>,
|
||||
ctx: Context,
|
||||
): Promise<{
|
||||
title: string
|
||||
metadata: M
|
||||
output: string
|
||||
init: () => Promise<{
|
||||
description: string
|
||||
parameters: Parameters
|
||||
execute(
|
||||
args: StandardSchemaV1.InferOutput<Parameters>,
|
||||
ctx: Context,
|
||||
): Promise<{
|
||||
title: string
|
||||
metadata: M
|
||||
output: string
|
||||
}>
|
||||
}>
|
||||
}
|
||||
|
||||
export function define<Parameters extends StandardSchemaV1, Result extends Metadata>(
|
||||
input: Info<Parameters, Result> | (() => Promise<Info<Parameters, Result>>),
|
||||
): () => Promise<Info<Parameters, Result>> {
|
||||
return input instanceof Function ? input : async () => input
|
||||
id: string,
|
||||
init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,
|
||||
): Info<Parameters, Result> {
|
||||
return {
|
||||
id,
|
||||
init: async () => {
|
||||
if (init instanceof Function) return init()
|
||||
return init
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,14 @@ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
||||
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
||||
const MAX_TIMEOUT = 120 * 1000 // 2 minutes
|
||||
|
||||
export const WebFetchTool = Tool.define({
|
||||
id: "webfetch",
|
||||
export const WebFetchTool = Tool.define("webfetch", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
url: z.string().describe("The URL to fetch content from"),
|
||||
format: z
|
||||
.enum(["text", "markdown", "html"])
|
||||
.describe("The format to return the content in (text, markdown, or html)"),
|
||||
timeout: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(MAX_TIMEOUT / 1000)
|
||||
.describe("Optional timeout in seconds (max 120)")
|
||||
.optional(),
|
||||
timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
// Validate URL
|
||||
|
||||
@@ -8,9 +8,10 @@ import { App } from "../app/app"
|
||||
import { Bus } from "../bus"
|
||||
import { File } from "../file"
|
||||
import { FileTime } from "../file/time"
|
||||
import { Config } from "../config/config"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
|
||||
export const WriteTool = Tool.define({
|
||||
id: "write",
|
||||
export const WriteTool = Tool.define("write", {
|
||||
description: DESCRIPTION,
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
|
||||
@@ -19,21 +20,26 @@ export const WriteTool = Tool.define({
|
||||
async execute(params, ctx) {
|
||||
const app = App.info()
|
||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
||||
}
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
const exists = await file.exists()
|
||||
if (exists) await FileTime.assert(ctx.sessionID, filepath)
|
||||
|
||||
await Permission.ask({
|
||||
id: "write",
|
||||
sessionID: ctx.sessionID,
|
||||
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
||||
metadata: {
|
||||
filePath: filepath,
|
||||
content: params.content,
|
||||
exists,
|
||||
},
|
||||
})
|
||||
const cfg = await Config.get()
|
||||
if (cfg.permission?.edit === "ask")
|
||||
await Permission.ask({
|
||||
id: "write",
|
||||
sessionID: ctx.sessionID,
|
||||
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
||||
metadata: {
|
||||
filePath: filepath,
|
||||
content: params.content,
|
||||
exists,
|
||||
},
|
||||
})
|
||||
|
||||
await Bun.write(filepath, params.content)
|
||||
await Bus.publish(File.Event.Edited, {
|
||||
|
||||
@@ -9,7 +9,7 @@ export namespace Filesystem {
|
||||
}
|
||||
|
||||
export function contains(parent: string, child: string) {
|
||||
return relative(parent, child).startsWith("..")
|
||||
return !relative(parent, child).startsWith("..")
|
||||
}
|
||||
|
||||
export async function findUp(target: string, start: string, stop?: string) {
|
||||
@@ -51,6 +51,7 @@ export namespace Filesystem {
|
||||
cwd: current,
|
||||
absolute: true,
|
||||
onlyFiles: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
})) {
|
||||
result.push(match)
|
||||
|
||||
1
packages/opencode/test/fixtures/example/broken.ts
vendored
Normal file
1
packages/opencode/test/fixtures/example/broken.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
// Test fixture for ListTool
|
||||
1
packages/opencode/test/fixtures/example/cli.ts
vendored
Normal file
1
packages/opencode/test/fixtures/example/cli.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
// Test fixture for ListTool
|
||||
1
packages/opencode/test/fixtures/example/ink.tsx
vendored
Normal file
1
packages/opencode/test/fixtures/example/ink.tsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
// Test fixture for ListTool
|
||||
@@ -1,17 +1,9 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
||||
|
||||
exports[`tool.ls basic 1`] = `
|
||||
"- /home/thdxr/dev/projects/sst/opencode/js/example/
|
||||
- home/
|
||||
- thdxr/
|
||||
- dev/
|
||||
- projects/
|
||||
- sst/
|
||||
- opencode/
|
||||
- js/
|
||||
- example/
|
||||
- ink.tsx
|
||||
- broken.ts
|
||||
- cli.ts
|
||||
"packages/opencode/test/fixtures/example/
|
||||
broken.ts
|
||||
cli.ts
|
||||
ink.tsx
|
||||
"
|
||||
`;
|
||||
|
||||
44
packages/opencode/test/tool/bash.test.ts
Normal file
44
packages/opencode/test/tool/bash.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { App } from "../../src/app/app"
|
||||
import path from "path"
|
||||
import { BashTool } from "../../src/tool/bash"
|
||||
import { Log } from "../../src/util/log"
|
||||
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
const bash = await BashTool.init()
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
Log.init({ print: false })
|
||||
|
||||
describe("tool.bash", () => {
|
||||
test("basic", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
await bash.execute(
|
||||
{
|
||||
command: "cd foo/bar && ls",
|
||||
description: "List files in foo/bar",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test("cd ../ should fail", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
expect(
|
||||
bash.execute(
|
||||
{
|
||||
command: "cd ../",
|
||||
description: "Try to cd to parent directory",
|
||||
},
|
||||
ctx,
|
||||
),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
|
||||
import { App } from "../../src/app/app"
|
||||
import { GlobTool } from "../../src/tool/glob"
|
||||
import { ListTool } from "../../src/tool/ls"
|
||||
import path from "path"
|
||||
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
@@ -9,16 +10,19 @@ const ctx = {
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
const glob = await GlobTool()
|
||||
const list = await ListTool()
|
||||
const glob = await GlobTool.init()
|
||||
const list = await ListTool.init()
|
||||
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
const fixturePath = path.join(__dirname, "../fixtures/example")
|
||||
|
||||
describe("tool.glob", () => {
|
||||
test("truncate", async () => {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
let result = await glob.execute(
|
||||
{
|
||||
pattern: "../../node_modules/**/*",
|
||||
path: undefined,
|
||||
pattern: "**/*",
|
||||
path: "../../node_modules",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
@@ -26,7 +30,7 @@ describe("tool.glob", () => {
|
||||
})
|
||||
})
|
||||
test("basic", async () => {
|
||||
await App.provide({ cwd: process.cwd() }, async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
let result = await glob.execute(
|
||||
{
|
||||
pattern: "*.json",
|
||||
@@ -36,7 +40,7 @@ describe("tool.glob", () => {
|
||||
)
|
||||
expect(result.metadata).toMatchObject({
|
||||
truncated: false,
|
||||
count: 3,
|
||||
count: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -44,9 +48,12 @@ describe("tool.glob", () => {
|
||||
|
||||
describe("tool.ls", () => {
|
||||
test("basic", async () => {
|
||||
const result = await App.provide({ cwd: process.cwd() }, async () => {
|
||||
return await list.execute({ path: "./example", ignore: [".git"] }, ctx)
|
||||
const result = await App.provide({ cwd: projectRoot }, async () => {
|
||||
return await list.execute({ path: fixturePath, ignore: [".git"] }, ctx)
|
||||
})
|
||||
expect(result.output).toMatchSnapshot()
|
||||
|
||||
// Normalize absolute path to relative for consistent snapshots
|
||||
const normalizedOutput = result.output.replace(fixturePath, "packages/opencode/test/fixtures/example")
|
||||
expect(normalizedOutput).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
||||
{
|
||||
"name": "Development",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:latest",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {}
|
||||
},
|
||||
"postCreateCommand": "yarn install",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["esbenp.prettier-vscode"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
CHANGELOG.md
|
||||
/ecosystem-tests/*/**
|
||||
/node_modules
|
||||
/deno
|
||||
|
||||
# don't format tsc output, will break source maps
|
||||
/dist
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"experimentalTernaries": true,
|
||||
"printWidth": 110,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
".": "0.1.0-alpha.20"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
configured_endpoints: 26
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-5748199af356c3243a46a466e73b5d0bab7eaa0c56895e1d0f903d637f61d0bb.yml
|
||||
openapi_spec_hash: c04f6b6be54b05d9b1283c24e870163b
|
||||
config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3
|
||||
@@ -1 +0,0 @@
|
||||
brew "node"
|
||||
@@ -1,196 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0-alpha.20 (2025-07-16)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.19...v0.1.0-alpha.20](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.19...v0.1.0-alpha.20)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([d296473](https://github.com/sst/opencode-sdk-js/commit/d296473db58378932b85d1afaa60942ac5599c49))
|
||||
* **api:** api update ([af2b587](https://github.com/sst/opencode-sdk-js/commit/af2b5875534a4782fac186542ecb9b04393c9b0a))
|
||||
|
||||
## 0.1.0-alpha.19 (2025-07-16)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.18...v0.1.0-alpha.19)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([2e505ef](https://github.com/sst/opencode-sdk-js/commit/2e505ef451fdcf49358189c5f76bdc42fb821352))
|
||||
|
||||
## 0.1.0-alpha.18 (2025-07-15)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.17...v0.1.0-alpha.18)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([25a23e5](https://github.com/sst/opencode-sdk-js/commit/25a23e599f1180754910961df65f0cc044aa2935))
|
||||
|
||||
## 0.1.0-alpha.17 (2025-07-15)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.16...v0.1.0-alpha.17)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([8b5d592](https://github.com/sst/opencode-sdk-js/commit/8b5d59243a0212f98269412f4483e729e2367a77))
|
||||
* **api:** api update ([ebd8986](https://github.com/sst/opencode-sdk-js/commit/ebd89862c48be2742eda727c83c01430413e00c0))
|
||||
|
||||
## 0.1.0-alpha.16 (2025-07-15)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.15...v0.1.0-alpha.16)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([f26379d](https://github.com/sst/opencode-sdk-js/commit/f26379d83ae7094d6ba91c6705a97a3fbd88a55a))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* make some internal functions async ([36b1db9](https://github.com/sst/opencode-sdk-js/commit/36b1db9ca9d47d9199e2eab5f0b454b7cd31f58f))
|
||||
|
||||
## 0.1.0-alpha.15 (2025-07-05)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.14...v0.1.0-alpha.15)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** manual updates ([f6ee467](https://github.com/sst/opencode-sdk-js/commit/f6ee46752d0c174c8b934894cf2b140864864208))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **internal:** codegen related update ([47a1a97](https://github.com/sst/opencode-sdk-js/commit/47a1a972e755735d6b5472c61f726ab2face9e62))
|
||||
|
||||
## 0.1.0-alpha.14 (2025-07-03)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.13...v0.1.0-alpha.14)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([a1d7cf9](https://github.com/sst/opencode-sdk-js/commit/a1d7cf948a2ff47ce4e98b4a52d0e4d213b87bf6))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **internal:** version bump ([f8ad145](https://github.com/sst/opencode-sdk-js/commit/f8ad145b9af0c4a465642630043e59236d5f4e8d))
|
||||
|
||||
## 0.1.0-alpha.13 (2025-07-03)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.12...v0.1.0-alpha.13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid console usage ([f96ac97](https://github.com/sst/opencode-sdk-js/commit/f96ac97fbaf7417efda306d8727654d1a4138386))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* add docs to RequestOptions type ([1ca6677](https://github.com/sst/opencode-sdk-js/commit/1ca667765c22b706732c61ea3d9d2823aeda0a8e))
|
||||
|
||||
## 0.1.0-alpha.12 (2025-07-02)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.11...v0.1.0-alpha.12)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([7739340](https://github.com/sst/opencode-sdk-js/commit/77393403648067fe937637c39e80067c347a8c5b))
|
||||
|
||||
## 0.1.0-alpha.11 (2025-06-30)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.10...v0.1.0-alpha.11](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.10...v0.1.0-alpha.11)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([2ce98e5](https://github.com/sst/opencode-sdk-js/commit/2ce98e55bf330cca0c38f60f011ffd9063b34ea0))
|
||||
|
||||
## 0.1.0-alpha.10 (2025-06-30)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.9...v0.1.0-alpha.10](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.9...v0.1.0-alpha.10)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([fa7c91c](https://github.com/sst/opencode-sdk-js/commit/fa7c91cc2fe52d42be7365ca2c4ce3e48c2e76ac))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **ci:** only run for pushes and fork pull requests ([0e850e5](https://github.com/sst/opencode-sdk-js/commit/0e850e51daac413dcf2d5e30c0ea7a1cd5346c4b))
|
||||
* **client:** improve path param validation ([bc3ff0e](https://github.com/sst/opencode-sdk-js/commit/bc3ff0ee2de9af8be42deae87d12f003fb5f7aa5))
|
||||
|
||||
## 0.1.0-alpha.9 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.8...v0.1.0-alpha.9)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([7009d10](https://github.com/sst/opencode-sdk-js/commit/7009d10aab99be7102371cee49013ab3edae4450))
|
||||
* **api:** update via SDK Studio ([e60aa00](https://github.com/sst/opencode-sdk-js/commit/e60aa0024079671e3725ee6f6bfbf8c2dad78da2))
|
||||
|
||||
## 0.1.0-alpha.8 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([171e3d5](https://github.com/sst/opencode-sdk-js/commit/171e3d5f3ba69ff9ba8547dac90d85b1a0a137c1))
|
||||
|
||||
## 0.1.0-alpha.7 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([14d2d04](https://github.com/sst/opencode-sdk-js/commit/14d2d04d80c1d5880940c9c70a5c1ea200df2ebc))
|
||||
|
||||
## 0.1.0-alpha.6 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([45e78b2](https://github.com/sst/opencode-sdk-js/commit/45e78b2f0fca18f537de9986e358aa876fb0b686))
|
||||
|
||||
## 0.1.0-alpha.5 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([10a5be9](https://github.com/sst/opencode-sdk-js/commit/10a5be9261c4ba8aeece7bb6921752f5fa6d9f28))
|
||||
|
||||
## 0.1.0-alpha.4 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([20dcd17](https://github.com/sst/opencode-sdk-js/commit/20dcd171405b05801e5a56f1b40fd635259b6a94))
|
||||
|
||||
## 0.1.0-alpha.3 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.2...v0.1.0-alpha.3)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** release-doctor — report correct token name ([128884f](https://github.com/sst/opencode-sdk-js/commit/128884f4bc64e618177a0b090cd6d52b122a059a))
|
||||
|
||||
## 0.1.0-alpha.2 (2025-06-24)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([2320f32](https://github.com/sst/opencode-sdk-js/commit/2320f32190ab58d15d00d7c3328f9fba2421536c))
|
||||
|
||||
## 0.1.0-alpha.1 (2025-06-24)
|
||||
|
||||
Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-js/compare/v0.0.1-alpha.0...v0.1.0-alpha.1)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([e448306](https://github.com/sst/opencode-sdk-js/commit/e4483068738cbb10233fca5a9d9d44a9c9815c8b))
|
||||
* **api:** update via SDK Studio ([b222c96](https://github.com/sst/opencode-sdk-js/commit/b222c96a679a8aeecb60bcf92c247fef90c75b3d))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* update SDK settings ([c1481ea](https://github.com/sst/opencode-sdk-js/commit/c1481ea7949c1422bedaeac278600b4ec3f58038))
|
||||
@@ -1,107 +0,0 @@
|
||||
## Setting up the environment
|
||||
|
||||
This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install).
|
||||
Other package managers may work but are not officially supported for development.
|
||||
|
||||
To set up the repository, run:
|
||||
|
||||
```sh
|
||||
$ yarn
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This will install all the required dependencies and build output files to `dist/`.
|
||||
|
||||
## Modifying/Adding code
|
||||
|
||||
Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
|
||||
result in merge conflicts between manual patches and changes from the generator. The generator will never
|
||||
modify the contents of the `src/lib/` and `examples/` directories.
|
||||
|
||||
## Adding and running examples
|
||||
|
||||
All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
|
||||
|
||||
```ts
|
||||
// add an example to examples/<your-example>.ts
|
||||
|
||||
#!/usr/bin/env -S npm run tsn -T
|
||||
…
|
||||
```
|
||||
|
||||
```sh
|
||||
$ chmod +x examples/<your-example>.ts
|
||||
# run the example against your api
|
||||
$ yarn tsn -T examples/<your-example>.ts
|
||||
```
|
||||
|
||||
## Using the repository from source
|
||||
|
||||
If you’d like to use the repository from source, you can either install from git or link to a cloned repository:
|
||||
|
||||
To install via git:
|
||||
|
||||
```sh
|
||||
$ npm install git+ssh://git@github.com:sst/opencode-sdk-js.git
|
||||
```
|
||||
|
||||
Alternatively, to link a local copy of the repo:
|
||||
|
||||
```sh
|
||||
# Clone
|
||||
$ git clone https://www.github.com/sst/opencode-sdk-js
|
||||
$ cd opencode-sdk-js
|
||||
|
||||
# With yarn
|
||||
$ yarn link
|
||||
$ cd ../my-package
|
||||
$ yarn link @opencode-ai/sdk
|
||||
|
||||
# With pnpm
|
||||
$ pnpm link --global
|
||||
$ cd ../my-package
|
||||
$ pnpm link -—global @opencode-ai/sdk
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
|
||||
|
||||
```sh
|
||||
$ npx prism mock path/to/your/openapi.yml
|
||||
```
|
||||
|
||||
```sh
|
||||
$ yarn run test
|
||||
```
|
||||
|
||||
## Linting and formatting
|
||||
|
||||
This repository uses [prettier](https://www.npmjs.com/package/prettier) and
|
||||
[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository.
|
||||
|
||||
To lint:
|
||||
|
||||
```sh
|
||||
$ yarn lint
|
||||
```
|
||||
|
||||
To format and fix all lint issues automatically:
|
||||
|
||||
```sh
|
||||
$ yarn fix
|
||||
```
|
||||
|
||||
## Publishing and releases
|
||||
|
||||
Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If
|
||||
the changes aren't made through the automated pipeline, you may want to make releases manually.
|
||||
|
||||
### Publish with a GitHub workflow
|
||||
|
||||
You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/sst/opencode-sdk-js/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up.
|
||||
|
||||
### Publish manually
|
||||
|
||||
If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on
|
||||
the environment.
|
||||
@@ -1,370 +0,0 @@
|
||||
# Opencode TypeScript API Library
|
||||
|
||||
[>)](https://npmjs.org/package/@opencode-ai/sdk) 
|
||||
|
||||
This library provides convenient access to the Opencode REST API from server-side TypeScript or JavaScript.
|
||||
|
||||
The REST API documentation can be found on [opencode.ai](https://opencode.ai/docs). The full API of this library can be found in [api.md](api.md).
|
||||
|
||||
It is generated with [Stainless](https://www.stainless.com/).
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @opencode-ai/sdk
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The full API of this library can be found in [api.md](api.md).
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode();
|
||||
|
||||
const sessions = await client.session.list();
|
||||
```
|
||||
|
||||
## Streaming responses
|
||||
|
||||
We provide support for streaming responses using Server Sent Events (SSE).
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode();
|
||||
|
||||
const stream = await client.event.list();
|
||||
for await (const eventListResponse of stream) {
|
||||
console.log(eventListResponse);
|
||||
}
|
||||
```
|
||||
|
||||
If you need to cancel a stream, you can `break` from the loop
|
||||
or call `stream.controller.abort()`.
|
||||
|
||||
### Request & Response types
|
||||
|
||||
This library includes TypeScript definitions for all request params and response fields. You may import and use them like so:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode();
|
||||
|
||||
const sessions: Opencode.SessionListResponse = await client.session.list();
|
||||
```
|
||||
|
||||
Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors.
|
||||
|
||||
## Handling errors
|
||||
|
||||
When the library is unable to connect to the API,
|
||||
or if the API returns a non-success status code (i.e., 4xx or 5xx response),
|
||||
a subclass of `APIError` will be thrown:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
const sessions = await client.session.list().catch(async (err) => {
|
||||
if (err instanceof Opencode.APIError) {
|
||||
console.log(err.status); // 400
|
||||
console.log(err.name); // BadRequestError
|
||||
console.log(err.headers); // {server: 'nginx', ...}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Error codes are as follows:
|
||||
|
||||
| Status Code | Error Type |
|
||||
| ----------- | -------------------------- |
|
||||
| 400 | `BadRequestError` |
|
||||
| 401 | `AuthenticationError` |
|
||||
| 403 | `PermissionDeniedError` |
|
||||
| 404 | `NotFoundError` |
|
||||
| 422 | `UnprocessableEntityError` |
|
||||
| 429 | `RateLimitError` |
|
||||
| >=500 | `InternalServerError` |
|
||||
| N/A | `APIConnectionError` |
|
||||
|
||||
### Retries
|
||||
|
||||
Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
|
||||
Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
|
||||
429 Rate Limit, and >=500 Internal errors will all be retried by default.
|
||||
|
||||
You can use the `maxRetries` option to configure or disable this:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Configure the default for all requests:
|
||||
const client = new Opencode({
|
||||
maxRetries: 0, // default is 2
|
||||
});
|
||||
|
||||
// Or, configure per-request:
|
||||
await client.session.list({
|
||||
maxRetries: 5,
|
||||
});
|
||||
```
|
||||
|
||||
### Timeouts
|
||||
|
||||
Requests time out after 1 minute by default. You can configure this with a `timeout` option:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Configure the default for all requests:
|
||||
const client = new Opencode({
|
||||
timeout: 20 * 1000, // 20 seconds (default is 1 minute)
|
||||
});
|
||||
|
||||
// Override per-request:
|
||||
await client.session.list({
|
||||
timeout: 5 * 1000,
|
||||
});
|
||||
```
|
||||
|
||||
On timeout, an `APIConnectionTimeoutError` is thrown.
|
||||
|
||||
Note that requests which time out will be [retried twice by default](#retries).
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Accessing raw Response data (e.g., headers)
|
||||
|
||||
The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.
|
||||
This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic.
|
||||
|
||||
You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data.
|
||||
Unlike `.asResponse()` this method consumes the body, returning once it is parsed.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
const client = new Opencode();
|
||||
|
||||
const response = await client.session.list().asResponse();
|
||||
console.log(response.headers.get('X-My-Header'));
|
||||
console.log(response.statusText); // access the underlying Response object
|
||||
|
||||
const { data: sessions, response: raw } = await client.session.list().withResponse();
|
||||
console.log(raw.headers.get('X-My-Header'));
|
||||
console.log(sessions);
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
> [!IMPORTANT]
|
||||
> All log messages are intended for debugging only. The format and content of log messages
|
||||
> may change between releases.
|
||||
|
||||
#### Log levels
|
||||
|
||||
The log level can be configured in two ways:
|
||||
|
||||
1. Via the `OPENCODE_LOG` environment variable
|
||||
2. Using the `logLevel` client option (overrides the environment variable if set)
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode({
|
||||
logLevel: 'debug', // Show all log messages
|
||||
});
|
||||
```
|
||||
|
||||
Available log levels, from most to least verbose:
|
||||
|
||||
- `'debug'` - Show debug messages, info, warnings, and errors
|
||||
- `'info'` - Show info messages, warnings, and errors
|
||||
- `'warn'` - Show warnings and errors (default)
|
||||
- `'error'` - Show only errors
|
||||
- `'off'` - Disable all logging
|
||||
|
||||
At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies.
|
||||
Some authentication-related headers are redacted, but sensitive data in request and response bodies
|
||||
may still be visible.
|
||||
|
||||
#### Custom logger
|
||||
|
||||
By default, this library logs to `globalThis.console`. You can also provide a custom logger.
|
||||
Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue.
|
||||
|
||||
When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages
|
||||
below the configured level will not be sent to your logger.
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
import pino from 'pino';
|
||||
|
||||
const logger = pino();
|
||||
|
||||
const client = new Opencode({
|
||||
logger: logger.child({ name: 'Opencode' }),
|
||||
logLevel: 'debug', // Send all messages to pino, allowing it to filter
|
||||
});
|
||||
```
|
||||
|
||||
### Making custom/undocumented requests
|
||||
|
||||
This library is typed for convenient access to the documented API. If you need to access undocumented
|
||||
endpoints, params, or response properties, the library can still be used.
|
||||
|
||||
#### Undocumented endpoints
|
||||
|
||||
To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs.
|
||||
Options on the client, such as retries, will be respected when making these requests.
|
||||
|
||||
```ts
|
||||
await client.post('/some/path', {
|
||||
body: { some_prop: 'foo' },
|
||||
query: { some_query_arg: 'bar' },
|
||||
});
|
||||
```
|
||||
|
||||
#### Undocumented request params
|
||||
|
||||
To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented
|
||||
parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you
|
||||
send will be sent as-is.
|
||||
|
||||
```ts
|
||||
client.session.list({
|
||||
// ...
|
||||
// @ts-expect-error baz is not yet public
|
||||
baz: 'undocumented option',
|
||||
});
|
||||
```
|
||||
|
||||
For requests with the `GET` verb, any extra params will be in the query, all other requests will send the
|
||||
extra param in the body.
|
||||
|
||||
If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request
|
||||
options.
|
||||
|
||||
#### Undocumented response properties
|
||||
|
||||
To access undocumented response properties, you may access the response object with `// @ts-expect-error` on
|
||||
the response object, or cast the response object to the requisite type. Like the request params, we do not
|
||||
validate or strip extra properties from the response from the API.
|
||||
|
||||
### Customizing the fetch client
|
||||
|
||||
By default, this library expects a global `fetch` function is defined.
|
||||
|
||||
If you want to use a different `fetch` function, you can either polyfill the global:
|
||||
|
||||
```ts
|
||||
import fetch from 'my-fetch';
|
||||
|
||||
globalThis.fetch = fetch;
|
||||
```
|
||||
|
||||
Or pass it to the client:
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
import fetch from 'my-fetch';
|
||||
|
||||
const client = new Opencode({ fetch });
|
||||
```
|
||||
|
||||
### Fetch options
|
||||
|
||||
If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.)
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode({
|
||||
fetchOptions: {
|
||||
// `RequestInit` options
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Configuring proxies
|
||||
|
||||
To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy
|
||||
options to requests:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/node.svg" align="top" width="18" height="21"> **Node** <sup>[[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)]</sup>
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
import * as undici from 'undici';
|
||||
|
||||
const proxyAgent = new undici.ProxyAgent('http://localhost:8888');
|
||||
const client = new Opencode({
|
||||
fetchOptions: {
|
||||
dispatcher: proxyAgent,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/bun.svg" align="top" width="18" height="21"> **Bun** <sup>[[docs](https://bun.sh/guides/http/proxy)]</sup>
|
||||
|
||||
```ts
|
||||
import Opencode from '@opencode-ai/sdk';
|
||||
|
||||
const client = new Opencode({
|
||||
fetchOptions: {
|
||||
proxy: 'http://localhost:8888',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/deno.svg" align="top" width="18" height="21"> **Deno** <sup>[[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)]</sup>
|
||||
|
||||
```ts
|
||||
import Opencode from 'npm:@opencode-ai/sdk';
|
||||
|
||||
const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });
|
||||
const client = new Opencode({
|
||||
fetchOptions: {
|
||||
client: httpClient,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
## Semantic versioning
|
||||
|
||||
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
|
||||
|
||||
1. Changes that only affect static types, without breaking runtime behavior.
|
||||
2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
|
||||
3. Changes that we do not expect to impact the vast majority of users in practice.
|
||||
|
||||
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
|
||||
|
||||
We are keen for your feedback; please open an [issue](https://www.github.com/sst/opencode-sdk-js/issues) with questions, bugs, or suggestions.
|
||||
|
||||
## Requirements
|
||||
|
||||
TypeScript >= 4.9 is supported.
|
||||
|
||||
The following runtimes are supported:
|
||||
|
||||
- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more)
|
||||
- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions.
|
||||
- Deno v1.28.0 or higher.
|
||||
- Bun 1.0 or later.
|
||||
- Cloudflare Workers.
|
||||
- Vercel Edge Runtime.
|
||||
- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time).
|
||||
- Nitro v2.6 or greater.
|
||||
|
||||
Note that React Native is not supported at this time.
|
||||
|
||||
If you are interested in other runtime environments, please open or upvote an issue on GitHub.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [the contributing documentation](./CONTRIBUTING.md).
|
||||
@@ -1,140 +0,0 @@
|
||||
# Shared
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/shared.ts">MessageAbortedError</a></code>
|
||||
- <code><a href="./src/resources/shared.ts">ProviderAuthError</a></code>
|
||||
- <code><a href="./src/resources/shared.ts">UnknownError</a></code>
|
||||
|
||||
# Event
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/event.ts">EventListResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /event">client.event.<a href="./src/resources/event.ts">list</a>() -> EventListResponse</code>
|
||||
|
||||
# App
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/app.ts">App</a></code>
|
||||
- <code><a href="./src/resources/app.ts">Mode</a></code>
|
||||
- <code><a href="./src/resources/app.ts">Model</a></code>
|
||||
- <code><a href="./src/resources/app.ts">Provider</a></code>
|
||||
- <code><a href="./src/resources/app.ts">AppInitResponse</a></code>
|
||||
- <code><a href="./src/resources/app.ts">AppLogResponse</a></code>
|
||||
- <code><a href="./src/resources/app.ts">AppModesResponse</a></code>
|
||||
- <code><a href="./src/resources/app.ts">AppProvidersResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /app">client.app.<a href="./src/resources/app.ts">get</a>() -> App</code>
|
||||
- <code title="post /app/init">client.app.<a href="./src/resources/app.ts">init</a>() -> AppInitResponse</code>
|
||||
- <code title="post /log">client.app.<a href="./src/resources/app.ts">log</a>({ ...params }) -> AppLogResponse</code>
|
||||
- <code title="get /mode">client.app.<a href="./src/resources/app.ts">modes</a>() -> AppModesResponse</code>
|
||||
- <code title="get /config/providers">client.app.<a href="./src/resources/app.ts">providers</a>() -> AppProvidersResponse</code>
|
||||
|
||||
# Find
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/find.ts">Match</a></code>
|
||||
- <code><a href="./src/resources/find.ts">Symbol</a></code>
|
||||
- <code><a href="./src/resources/find.ts">FindFilesResponse</a></code>
|
||||
- <code><a href="./src/resources/find.ts">FindSymbolsResponse</a></code>
|
||||
- <code><a href="./src/resources/find.ts">FindTextResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /find/file">client.find.<a href="./src/resources/find.ts">files</a>({ ...params }) -> FindFilesResponse</code>
|
||||
- <code title="get /find/symbol">client.find.<a href="./src/resources/find.ts">symbols</a>({ ...params }) -> FindSymbolsResponse</code>
|
||||
- <code title="get /find">client.find.<a href="./src/resources/find.ts">text</a>({ ...params }) -> FindTextResponse</code>
|
||||
|
||||
# File
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/file.ts">File</a></code>
|
||||
- <code><a href="./src/resources/file.ts">FileReadResponse</a></code>
|
||||
- <code><a href="./src/resources/file.ts">FileStatusResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /file">client.file.<a href="./src/resources/file.ts">read</a>({ ...params }) -> FileReadResponse</code>
|
||||
- <code title="get /file/status">client.file.<a href="./src/resources/file.ts">status</a>() -> FileStatusResponse</code>
|
||||
|
||||
# Config
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/config.ts">Config</a></code>
|
||||
- <code><a href="./src/resources/config.ts">KeybindsConfig</a></code>
|
||||
- <code><a href="./src/resources/config.ts">McpLocalConfig</a></code>
|
||||
- <code><a href="./src/resources/config.ts">McpRemoteConfig</a></code>
|
||||
- <code><a href="./src/resources/config.ts">ModeConfig</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /config">client.config.<a href="./src/resources/config.ts">get</a>() -> Config</code>
|
||||
|
||||
# Session
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/session.ts">AssistantMessage</a></code>
|
||||
- <code><a href="./src/resources/session.ts">FilePart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">FilePartInput</a></code>
|
||||
- <code><a href="./src/resources/session.ts">FilePartSource</a></code>
|
||||
- <code><a href="./src/resources/session.ts">FilePartSourceText</a></code>
|
||||
- <code><a href="./src/resources/session.ts">FileSource</a></code>
|
||||
- <code><a href="./src/resources/session.ts">Message</a></code>
|
||||
- <code><a href="./src/resources/session.ts">Part</a></code>
|
||||
- <code><a href="./src/resources/session.ts">Session</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SnapshotPart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">StepFinishPart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">StepStartPart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SymbolSource</a></code>
|
||||
- <code><a href="./src/resources/session.ts">TextPart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">TextPartInput</a></code>
|
||||
- <code><a href="./src/resources/session.ts">ToolPart</a></code>
|
||||
- <code><a href="./src/resources/session.ts">ToolStateCompleted</a></code>
|
||||
- <code><a href="./src/resources/session.ts">ToolStateError</a></code>
|
||||
- <code><a href="./src/resources/session.ts">ToolStatePending</a></code>
|
||||
- <code><a href="./src/resources/session.ts">ToolStateRunning</a></code>
|
||||
- <code><a href="./src/resources/session.ts">UserMessage</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionListResponse</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionDeleteResponse</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionAbortResponse</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionInitResponse</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionMessagesResponse</a></code>
|
||||
- <code><a href="./src/resources/session.ts">SessionSummarizeResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session">client.session.<a href="./src/resources/session.ts">create</a>() -> Session</code>
|
||||
- <code title="get /session">client.session.<a href="./src/resources/session.ts">list</a>() -> SessionListResponse</code>
|
||||
- <code title="delete /session/{id}">client.session.<a href="./src/resources/session.ts">delete</a>(id) -> SessionDeleteResponse</code>
|
||||
- <code title="post /session/{id}/abort">client.session.<a href="./src/resources/session.ts">abort</a>(id) -> SessionAbortResponse</code>
|
||||
- <code title="post /session/{id}/message">client.session.<a href="./src/resources/session.ts">chat</a>(id, { ...params }) -> AssistantMessage</code>
|
||||
- <code title="post /session/{id}/init">client.session.<a href="./src/resources/session.ts">init</a>(id, { ...params }) -> SessionInitResponse</code>
|
||||
- <code title="get /session/{id}/message">client.session.<a href="./src/resources/session.ts">messages</a>(id) -> SessionMessagesResponse</code>
|
||||
- <code title="post /session/{id}/revert">client.session.<a href="./src/resources/session.ts">revert</a>(id, { ...params }) -> Session</code>
|
||||
- <code title="post /session/{id}/share">client.session.<a href="./src/resources/session.ts">share</a>(id) -> Session</code>
|
||||
- <code title="post /session/{id}/summarize">client.session.<a href="./src/resources/session.ts">summarize</a>(id, { ...params }) -> SessionSummarizeResponse</code>
|
||||
- <code title="post /session/{id}/unrevert">client.session.<a href="./src/resources/session.ts">unrevert</a>(id) -> Session</code>
|
||||
- <code title="delete /session/{id}/share">client.session.<a href="./src/resources/session.ts">unshare</a>(id) -> Session</code>
|
||||
|
||||
# Tui
|
||||
|
||||
Types:
|
||||
|
||||
- <code><a href="./src/resources/tui.ts">TuiAppendPromptResponse</a></code>
|
||||
- <code><a href="./src/resources/tui.ts">TuiOpenHelpResponse</a></code>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /tui/append-prompt">client.tui.<a href="./src/resources/tui.ts">appendPrompt</a>({ ...params }) -> TuiAppendPromptResponse</code>
|
||||
- <code title="post /tui/open-help">client.tui.<a href="./src/resources/tui.ts">openHelp</a>() -> TuiOpenHelpResponse</code>
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
errors=()
|
||||
|
||||
if [ -z "${NPM_TOKEN}" ]; then
|
||||
errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets")
|
||||
fi
|
||||
|
||||
lenErrors=${#errors[@]}
|
||||
|
||||
if [[ lenErrors -gt 0 ]]; then
|
||||
echo -e "Found the following errors in the release environment:\n"
|
||||
|
||||
for error in "${errors[@]}"; do
|
||||
echo -e "- $error\n"
|
||||
done
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "The environment is ready to push releases!"
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eux
|
||||
|
||||
npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN"
|
||||
|
||||
yarn build
|
||||
cd dist
|
||||
|
||||
# Get package name and version from package.json
|
||||
PACKAGE_NAME="$(jq -r -e '.name' ./package.json)"
|
||||
VERSION="$(jq -r -e '.version' ./package.json)"
|
||||
|
||||
# Get latest version from npm
|
||||
#
|
||||
# If the package doesn't exist, npm will return:
|
||||
# {
|
||||
# "error": {
|
||||
# "code": "E404",
|
||||
# "summary": "Unpublished on 2025-06-05T09:54:53.528Z",
|
||||
# "detail": "'the_package' is not in this registry..."
|
||||
# }
|
||||
# }
|
||||
NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)"
|
||||
|
||||
# Check if we got an E404 error
|
||||
if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then
|
||||
# Package doesn't exist yet, no last version
|
||||
LAST_VERSION=""
|
||||
elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then
|
||||
# Report other errors
|
||||
echo "ERROR: npm returned unexpected data:"
|
||||
echo "$NPM_INFO"
|
||||
exit 1
|
||||
else
|
||||
# Success - get the version
|
||||
LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes
|
||||
fi
|
||||
|
||||
# Check if current version is pre-release (e.g. alpha / beta / rc)
|
||||
CURRENT_IS_PRERELEASE=false
|
||||
if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then
|
||||
CURRENT_IS_PRERELEASE=true
|
||||
CURRENT_TAG="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# Check if last version is a stable release
|
||||
LAST_IS_STABLE_RELEASE=true
|
||||
if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then
|
||||
LAST_IS_STABLE_RELEASE=false
|
||||
fi
|
||||
|
||||
# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease.
|
||||
if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then
|
||||
TAG="$CURRENT_TAG"
|
||||
else
|
||||
TAG="latest"
|
||||
fi
|
||||
|
||||
# Publish with the appropriate tag
|
||||
yarn publish --access public --tag "$TAG"
|
||||
@@ -1,42 +0,0 @@
|
||||
// @ts-check
|
||||
import tseslint from 'typescript-eslint';
|
||||
import unusedImports from 'eslint-plugin-unused-imports';
|
||||
import prettier from 'eslint-plugin-prettier';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: { sourceType: 'module' },
|
||||
},
|
||||
files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'],
|
||||
ignores: ['dist/'],
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
'unused-imports': unusedImports,
|
||||
prettier,
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
regex: '^@opencode-ai/sdk(/.*)?',
|
||||
message: 'Use a relative import, not a package import.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/**', 'examples/**'],
|
||||
rules: {
|
||||
'no-restricted-imports': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
7
packages/sdk/go/.devcontainer/devcontainer.json
Normal file
7
packages/sdk/go/.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,7 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
||||
{
|
||||
"name": "Development",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
|
||||
"postCreateCommand": "go mod tidy"
|
||||
}
|
||||
49
packages/sdk/go/.github/workflows/ci.yml
vendored
Normal file
49
packages/sdk/go/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'generated'
|
||||
- 'codegen/**'
|
||||
- 'integrated/**'
|
||||
- 'stl-preview-head/**'
|
||||
- 'stl-preview-base/**'
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- 'stl-preview-head/**'
|
||||
- 'stl-preview-base/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
name: lint
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./go.mod
|
||||
|
||||
- name: Run lints
|
||||
run: ./scripts/lint
|
||||
test:
|
||||
timeout-minutes: 10
|
||||
name: test
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./go.mod
|
||||
|
||||
- name: Bootstrap
|
||||
run: ./scripts/bootstrap
|
||||
|
||||
- name: Run tests
|
||||
run: ./scripts/test
|
||||
4
packages/sdk/go/.gitignore
vendored
Normal file
4
packages/sdk/go/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.prism.log
|
||||
codegen.log
|
||||
Brewfile.lock.json
|
||||
.idea/
|
||||
3
packages/sdk/go/.release-please-manifest.json
Normal file
3
packages/sdk/go/.release-please-manifest.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
".": "0.1.0-alpha.8"
|
||||
}
|
||||
4
packages/sdk/go/.stats.yml
Normal file
4
packages/sdk/go/.stats.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
configured_endpoints: 26
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-5bf6a39123d248d306490c1dee61b46ba113ea2c415a4de1a631c76462769c49.yml
|
||||
openapi_spec_hash: 3c5b25f121429281275ffd70c9d5cfe4
|
||||
config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3
|
||||
1
packages/sdk/go/Brewfile
Normal file
1
packages/sdk/go/Brewfile
Normal file
@@ -0,0 +1 @@
|
||||
brew "go"
|
||||
73
packages/sdk/go/CHANGELOG.md
Normal file
73
packages/sdk/go/CHANGELOG.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0-alpha.8 (2025-07-02)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
|
||||
|
||||
## 0.1.0-alpha.7 (2025-06-30)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
|
||||
* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
|
||||
|
||||
## 0.1.0-alpha.6 (2025-06-28)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
|
||||
|
||||
## 0.1.0-alpha.5 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
|
||||
|
||||
## 0.1.0-alpha.4 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
|
||||
|
||||
## 0.1.0-alpha.3 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.2...v0.1.0-alpha.3)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
|
||||
|
||||
## 0.1.0-alpha.2 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.1...v0.1.0-alpha.2)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
|
||||
|
||||
## 0.1.0-alpha.1 (2025-06-27)
|
||||
|
||||
Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-go/compare/v0.0.1-alpha.0...v0.1.0-alpha.1)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
|
||||
* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
|
||||
* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
|
||||
66
packages/sdk/go/CONTRIBUTING.md
Normal file
66
packages/sdk/go/CONTRIBUTING.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## Setting up the environment
|
||||
|
||||
To set up the repository, run:
|
||||
|
||||
```sh
|
||||
$ ./scripts/bootstrap
|
||||
$ ./scripts/build
|
||||
```
|
||||
|
||||
This will install all the required dependencies and build the SDK.
|
||||
|
||||
You can also [install go 1.18+ manually](https://go.dev/doc/install).
|
||||
|
||||
## Modifying/Adding code
|
||||
|
||||
Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
|
||||
result in merge conflicts between manual patches and changes from the generator. The generator will never
|
||||
modify the contents of the `lib/` and `examples/` directories.
|
||||
|
||||
## Adding and running examples
|
||||
|
||||
All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.
|
||||
|
||||
```go
|
||||
# add an example to examples/<your-example>/main.go
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
$ go run ./examples/<your-example>
|
||||
```
|
||||
|
||||
## Using the repository from source
|
||||
|
||||
To use a local version of this library from source in another project, edit the `go.mod` with a replace
|
||||
directive. This can be done through the CLI with the following:
|
||||
|
||||
```sh
|
||||
$ go mod edit -replace github.com/sst/opencode-sdk-go=/path/to/opencode-sdk-go
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
|
||||
|
||||
```sh
|
||||
# you will need npm installed
|
||||
$ npx prism mock path/to/your/openapi.yml
|
||||
```
|
||||
|
||||
```sh
|
||||
$ ./scripts/test
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
This library uses the standard gofmt code formatter:
|
||||
|
||||
```sh
|
||||
$ ./scripts/format
|
||||
```
|
||||
354
packages/sdk/go/README.md
Normal file
354
packages/sdk/go/README.md
Normal file
@@ -0,0 +1,354 @@
|
||||
# Opencode Go API Library
|
||||
|
||||
<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go"><img src="https://pkg.go.dev/badge/github.com/sst/opencode-sdk-go.svg" alt="Go Reference"></a>
|
||||
|
||||
The Opencode Go library provides convenient access to the [Opencode REST API](https://opencode.ai/docs)
|
||||
from applications written in Go.
|
||||
|
||||
It is generated with [Stainless](https://www.stainless.com/).
|
||||
|
||||
## Installation
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/sst/opencode-sdk-go" // imported as opencode
|
||||
)
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
Or to pin the version:
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```sh
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.1.0-alpha.8'
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
## Requirements
|
||||
|
||||
This library requires Go 1.18+.
|
||||
|
||||
## Usage
|
||||
|
||||
The full API of this library can be found in [api.md](api.md).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sst/opencode-sdk-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := opencode.NewClient()
|
||||
sessions, err := client.Session.List(context.TODO())
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
fmt.Printf("%+v\n", sessions)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Request fields
|
||||
|
||||
All request parameters are wrapped in a generic `Field` type,
|
||||
which we use to distinguish zero values from null or omitted fields.
|
||||
|
||||
This prevents accidentally sending a zero value if you forget a required parameter,
|
||||
and enables explicitly sending `null`, `false`, `''`, or `0` on optional parameters.
|
||||
Any field not specified is not sent.
|
||||
|
||||
To construct fields with values, use the helpers `String()`, `Int()`, `Float()`, or most commonly, the generic `F[T]()`.
|
||||
To send a null, use `Null[T]()`, and to send a nonconforming value, use `Raw[T](any)`. For example:
|
||||
|
||||
```go
|
||||
params := FooParams{
|
||||
Name: opencode.F("hello"),
|
||||
|
||||
// Explicitly send `"description": null`
|
||||
Description: opencode.Null[string](),
|
||||
|
||||
Point: opencode.F(opencode.Point{
|
||||
X: opencode.Int(0),
|
||||
Y: opencode.Int(1),
|
||||
|
||||
// In cases where the API specifies a given type,
|
||||
// but you want to send something else, use `Raw`:
|
||||
Z: opencode.Raw[int64](0.01), // sends a float
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
### Response objects
|
||||
|
||||
All fields in response structs are value types (not pointers or wrappers).
|
||||
|
||||
If a given field is `null`, not present, or invalid, the corresponding field
|
||||
will simply be its zero value.
|
||||
|
||||
All response structs also include a special `JSON` field, containing more detailed
|
||||
information about each property, which you can use like so:
|
||||
|
||||
```go
|
||||
if res.Name == "" {
|
||||
// true if `"name"` is either not present or explicitly null
|
||||
res.JSON.Name.IsNull()
|
||||
|
||||
// true if the `"name"` key was not present in the response JSON at all
|
||||
res.JSON.Name.IsMissing()
|
||||
|
||||
// When the API returns data that cannot be coerced to the expected type:
|
||||
if res.JSON.Name.IsInvalid() {
|
||||
raw := res.JSON.Name.Raw()
|
||||
|
||||
legacyName := struct{
|
||||
First string `json:"first"`
|
||||
Last string `json:"last"`
|
||||
}{}
|
||||
json.Unmarshal([]byte(raw), &legacyName)
|
||||
name = legacyName.First + " " + legacyName.Last
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These `.JSON` structs also include an `Extras` map containing
|
||||
any properties in the json response that were not specified
|
||||
in the struct. This can be useful for API features not yet
|
||||
present in the SDK.
|
||||
|
||||
```go
|
||||
body := res.JSON.ExtraFields["my_unexpected_field"].Raw()
|
||||
```
|
||||
|
||||
### RequestOptions
|
||||
|
||||
This library uses the functional options pattern. Functions defined in the
|
||||
`option` package return a `RequestOption`, which is a closure that mutates a
|
||||
`RequestConfig`. These options can be supplied to the client or at individual
|
||||
requests. For example:
|
||||
|
||||
```go
|
||||
client := opencode.NewClient(
|
||||
// Adds a header to every request made by the client
|
||||
option.WithHeader("X-Some-Header", "custom_header_info"),
|
||||
)
|
||||
|
||||
client.Session.List(context.TODO(), ...,
|
||||
// Override the header
|
||||
option.WithHeader("X-Some-Header", "some_other_custom_header_info"),
|
||||
// Add an undocumented field to the request body, using sjson syntax
|
||||
option.WithJSONSet("some.json.path", map[string]string{"my": "object"}),
|
||||
)
|
||||
```
|
||||
|
||||
See the [full list of request options](https://pkg.go.dev/github.com/sst/opencode-sdk-go/option).
|
||||
|
||||
### Pagination
|
||||
|
||||
This library provides some conveniences for working with paginated list endpoints.
|
||||
|
||||
You can use `.ListAutoPaging()` methods to iterate through items across all pages:
|
||||
|
||||
Or you can use simple `.List()` methods to fetch a single page and receive a standard response object
|
||||
with additional helper methods like `.GetNextPage()`, e.g.:
|
||||
|
||||
### Errors
|
||||
|
||||
When the API returns a non-success status code, we return an error with type
|
||||
`*opencode.Error`. This contains the `StatusCode`, `*http.Request`, and
|
||||
`*http.Response` values of the request, as well as the JSON of the error body
|
||||
(much like other response objects in the SDK).
|
||||
|
||||
To handle errors, we recommend that you use the `errors.As` pattern:
|
||||
|
||||
```go
|
||||
_, err := client.Session.List(context.TODO())
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request
|
||||
println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response
|
||||
}
|
||||
panic(err.Error()) // GET "/session": 400 Bad Request { ... }
|
||||
}
|
||||
```
|
||||
|
||||
When other errors occur, they are returned unwrapped; for example,
|
||||
if HTTP transport fails, you might receive `*url.Error` wrapping `*net.OpError`.
|
||||
|
||||
### Timeouts
|
||||
|
||||
Requests do not time out by default; use context to configure a timeout for a request lifecycle.
|
||||
|
||||
Note that if a request is [retried](#retries), the context timeout does not start over.
|
||||
To set a per-retry timeout, use `option.WithRequestTimeout()`.
|
||||
|
||||
```go
|
||||
// This sets the timeout for the request, including all the retries.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
client.Session.List(
|
||||
ctx,
|
||||
// This sets the per-retry timeout
|
||||
option.WithRequestTimeout(20*time.Second),
|
||||
)
|
||||
```
|
||||
|
||||
### File uploads
|
||||
|
||||
Request parameters that correspond to file uploads in multipart requests are typed as
|
||||
`param.Field[io.Reader]`. The contents of the `io.Reader` will by default be sent as a multipart form
|
||||
part with the file name of "anonymous_file" and content-type of "application/octet-stream".
|
||||
|
||||
The file name and content-type can be customized by implementing `Name() string` or `ContentType()
|
||||
string` on the run-time type of `io.Reader`. Note that `os.File` implements `Name() string`, so a
|
||||
file returned by `os.Open` will be sent with the file name on disk.
|
||||
|
||||
We also provide a helper `opencode.FileParam(reader io.Reader, filename string, contentType string)`
|
||||
which can be used to wrap any `io.Reader` with the appropriate file name and content type.
|
||||
|
||||
### Retries
|
||||
|
||||
Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
|
||||
We retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit,
|
||||
and >=500 Internal errors.
|
||||
|
||||
You can use the `WithMaxRetries` option to configure or disable this:
|
||||
|
||||
```go
|
||||
// Configure the default for all requests:
|
||||
client := opencode.NewClient(
|
||||
option.WithMaxRetries(0), // default is 2
|
||||
)
|
||||
|
||||
// Override per-request:
|
||||
client.Session.List(context.TODO(), option.WithMaxRetries(5))
|
||||
```
|
||||
|
||||
### Accessing raw response data (e.g. response headers)
|
||||
|
||||
You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when
|
||||
you need to examine response headers, status codes, or other details.
|
||||
|
||||
```go
|
||||
// Create a variable to store the HTTP response
|
||||
var response *http.Response
|
||||
sessions, err := client.Session.List(context.TODO(), option.WithResponseInto(&response))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
fmt.Printf("%+v\n", sessions)
|
||||
|
||||
fmt.Printf("Status Code: %d\n", response.StatusCode)
|
||||
fmt.Printf("Headers: %+#v\n", response.Header)
|
||||
```
|
||||
|
||||
### Making custom/undocumented requests
|
||||
|
||||
This library is typed for convenient access to the documented API. If you need to access undocumented
|
||||
endpoints, params, or response properties, the library can still be used.
|
||||
|
||||
#### Undocumented endpoints
|
||||
|
||||
To make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs.
|
||||
`RequestOptions` on the client, such as retries, will be respected when making these requests.
|
||||
|
||||
```go
|
||||
var (
|
||||
// params can be an io.Reader, a []byte, an encoding/json serializable object,
|
||||
// or a "…Params" struct defined in this library.
|
||||
params map[string]interface{}
|
||||
|
||||
// result can be an []byte, *http.Response, a encoding/json deserializable object,
|
||||
// or a model defined in this library.
|
||||
result *http.Response
|
||||
)
|
||||
err := client.Post(context.Background(), "/unspecified", params, &result)
|
||||
if err != nil {
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
#### Undocumented request params
|
||||
|
||||
To make requests using undocumented parameters, you may use either the `option.WithQuerySet()`
|
||||
or the `option.WithJSONSet()` methods.
|
||||
|
||||
```go
|
||||
params := FooNewParams{
|
||||
ID: opencode.F("id_xxxx"),
|
||||
Data: opencode.F(FooNewParamsData{
|
||||
FirstName: opencode.F("John"),
|
||||
}),
|
||||
}
|
||||
client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe"))
|
||||
```
|
||||
|
||||
#### Undocumented response properties
|
||||
|
||||
To access undocumented response properties, you may either access the raw JSON of the response as a string
|
||||
with `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with
|
||||
`result.JSON.Foo.Raw()`.
|
||||
|
||||
Any fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`.
|
||||
|
||||
### Middleware
|
||||
|
||||
We provide `option.WithMiddleware` which applies the given
|
||||
middleware to requests.
|
||||
|
||||
```go
|
||||
func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
|
||||
// Before the request
|
||||
start := time.Now()
|
||||
LogReq(req)
|
||||
|
||||
// Forward the request to the next handler
|
||||
res, err = next(req)
|
||||
|
||||
// Handle stuff after the request
|
||||
end := time.Now()
|
||||
LogRes(res, err, start - end)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
client := opencode.NewClient(
|
||||
option.WithMiddleware(Logger),
|
||||
)
|
||||
```
|
||||
|
||||
When multiple middlewares are provided as variadic arguments, the middlewares
|
||||
are applied left to right. If `option.WithMiddleware` is given
|
||||
multiple times, for example first in the client then the method, the
|
||||
middleware in the client will run first and the middleware given in the method
|
||||
will run next.
|
||||
|
||||
You may also replace the default `http.Client` with
|
||||
`option.WithHTTPClient(client)`. Only one http client is
|
||||
accepted (this overwrites any previous client) and receives requests after any
|
||||
middleware has been applied.
|
||||
|
||||
## Semantic versioning
|
||||
|
||||
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
|
||||
|
||||
1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
|
||||
2. Changes that we do not expect to impact the vast majority of users in practice.
|
||||
|
||||
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
|
||||
|
||||
We are keen for your feedback; please open an [issue](https://www.github.com/sst/opencode-sdk-go/issues) with questions, bugs, or suggestions.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [the contributing documentation](./CONTRIBUTING.md).
|
||||
43
packages/sdk/go/aliases.go
Normal file
43
packages/sdk/go/aliases.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"github.com/sst/opencode-sdk-go/internal/apierror"
|
||||
"github.com/sst/opencode-sdk-go/shared"
|
||||
)
|
||||
|
||||
type Error = apierror.Error
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type MessageAbortedError = shared.MessageAbortedError
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type MessageAbortedErrorName = shared.MessageAbortedErrorName
|
||||
|
||||
// This is an alias to an internal value.
|
||||
const MessageAbortedErrorNameMessageAbortedError = shared.MessageAbortedErrorNameMessageAbortedError
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type ProviderAuthError = shared.ProviderAuthError
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type ProviderAuthErrorData = shared.ProviderAuthErrorData
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type ProviderAuthErrorName = shared.ProviderAuthErrorName
|
||||
|
||||
// This is an alias to an internal value.
|
||||
const ProviderAuthErrorNameProviderAuthError = shared.ProviderAuthErrorNameProviderAuthError
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type UnknownError = shared.UnknownError
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type UnknownErrorData = shared.UnknownErrorData
|
||||
|
||||
// This is an alias to an internal type.
|
||||
type UnknownErrorName = shared.UnknownErrorName
|
||||
|
||||
// This is an alias to an internal value.
|
||||
const UnknownErrorNameUnknownError = shared.UnknownErrorNameUnknownError
|
||||
128
packages/sdk/go/api.md
Normal file
128
packages/sdk/go/api.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Shared Response Types
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#MessageAbortedError">MessageAbortedError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#ProviderAuthError">ProviderAuthError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared">shared</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go/shared#UnknownError">UnknownError</a>
|
||||
|
||||
# Event
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /event">client.Event.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#EventListResponse">EventListResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# App
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#App">App</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Mode">Mode</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Model">Model</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Provider">Provider</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersResponse">AppProvidersResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /app">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#App">App</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /app/init">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /log">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Log">Log</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppLogParams">AppLogParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /mode">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Modes">Modes</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Mode">Mode</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /config/providers">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Providers">Providers</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppProvidersResponse">AppProvidersResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Find
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Symbol">Symbol</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextResponse">FindTextResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /find/file">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Files">Files</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindFilesParams">FindFilesParams</a>) ([]<a href="https://pkg.go.dev/builtin#string">string</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find/symbol">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Symbols">Symbols</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindSymbolsParams">FindSymbolsParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Symbol">Symbol</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /find">client.Find.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindService.Text">Text</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextParams">FindTextParams</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FindTextResponse">FindTextResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# File
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /file">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Read">Read</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadParams">FileReadParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileReadResponse">FileReadResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /file/status">client.File.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileService.Status">Status</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#File">File</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Config
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#KeybindsConfig">KeybindsConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpLocalConfig">McpLocalConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpRemoteConfig">McpRemoteConfig</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ModeConfig">ModeConfig</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="get /config">client.Config.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ConfigService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Session
|
||||
|
||||
Params Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartInputParam">FilePartInputParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceUnionParam">FilePartSourceUnionParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceTextParam">FilePartSourceTextParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSourceParam">FileSourceParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSourceParam">SymbolSourceParam</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPartInputParam">TextPartInputParam</a>
|
||||
|
||||
Response Types:
|
||||
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePart">FilePart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSource">FilePartSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceText">FilePartSourceText</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSource">FileSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Message">Message</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Part">Part</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SnapshotPart">SnapshotPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepFinishPart">StepFinishPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepStartPart">StepStartPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSource">SymbolSource</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPart">TextPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolPart">ToolPart</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateCompleted">ToolStateCompleted</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateError">ToolStateError</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
|
||||
# Tui
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /tui/append-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.AppendPrompt">AppendPrompt</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiAppendPromptParams">TuiAppendPromptParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <code title="post /tui/open-help">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenHelp">OpenHelp</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
368
packages/sdk/go/app.go
Normal file
368
packages/sdk/go/app.go
Normal file
@@ -0,0 +1,368 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
// AppService contains methods and other services that help with interacting with
|
||||
// the opencode API.
|
||||
//
|
||||
// Note, unlike clients, this service does not read variables from the environment
|
||||
// automatically. You should not instantiate this service directly, and instead use
|
||||
// the [NewAppService] method instead.
|
||||
type AppService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewAppService generates a new service that applies the given options to each
|
||||
// request. These options are applied after the parent client's options (if there
|
||||
// is one), and before any request-specific options.
|
||||
func NewAppService(opts ...option.RequestOption) (r *AppService) {
|
||||
r = &AppService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Get app info
|
||||
func (r *AppService) Get(ctx context.Context, opts ...option.RequestOption) (res *App, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "app"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the app
|
||||
func (r *AppService) Init(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "app/init"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Write a log entry to the server logs
|
||||
func (r *AppService) Log(ctx context.Context, body AppLogParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "log"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// List all modes
|
||||
func (r *AppService) Modes(ctx context.Context, opts ...option.RequestOption) (res *[]Mode, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "mode"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// List all providers
|
||||
func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption) (res *AppProvidersResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "config/providers"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Git bool `json:"git,required"`
|
||||
Hostname string `json:"hostname,required"`
|
||||
Path AppPath `json:"path,required"`
|
||||
Time AppTime `json:"time,required"`
|
||||
JSON appJSON `json:"-"`
|
||||
}
|
||||
|
||||
// appJSON contains the JSON metadata for the struct [App]
|
||||
type appJSON struct {
|
||||
Git apijson.Field
|
||||
Hostname apijson.Field
|
||||
Path apijson.Field
|
||||
Time apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *App) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r appJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AppPath struct {
|
||||
Config string `json:"config,required"`
|
||||
Cwd string `json:"cwd,required"`
|
||||
Data string `json:"data,required"`
|
||||
Root string `json:"root,required"`
|
||||
State string `json:"state,required"`
|
||||
JSON appPathJSON `json:"-"`
|
||||
}
|
||||
|
||||
// appPathJSON contains the JSON metadata for the struct [AppPath]
|
||||
type appPathJSON struct {
|
||||
Config apijson.Field
|
||||
Cwd apijson.Field
|
||||
Data apijson.Field
|
||||
Root apijson.Field
|
||||
State apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AppPath) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r appPathJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AppTime struct {
|
||||
Initialized float64 `json:"initialized"`
|
||||
JSON appTimeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// appTimeJSON contains the JSON metadata for the struct [AppTime]
|
||||
type appTimeJSON struct {
|
||||
Initialized apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AppTime) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r appTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type Mode struct {
|
||||
Name string `json:"name,required"`
|
||||
Tools map[string]bool `json:"tools,required"`
|
||||
Model ModeModel `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
JSON modeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modeJSON contains the JSON metadata for the struct [Mode]
|
||||
type modeJSON struct {
|
||||
Name apijson.Field
|
||||
Tools apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Mode) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ModeModel struct {
|
||||
ModelID string `json:"modelID,required"`
|
||||
ProviderID string `json:"providerID,required"`
|
||||
JSON modeModelJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modeModelJSON contains the JSON metadata for the struct [ModeModel]
|
||||
type modeModelJSON struct {
|
||||
ModelID apijson.Field
|
||||
ProviderID apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ModeModel) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modeModelJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
ID string `json:"id,required"`
|
||||
Attachment bool `json:"attachment,required"`
|
||||
Cost ModelCost `json:"cost,required"`
|
||||
Limit ModelLimit `json:"limit,required"`
|
||||
Name string `json:"name,required"`
|
||||
Options map[string]interface{} `json:"options,required"`
|
||||
Reasoning bool `json:"reasoning,required"`
|
||||
ReleaseDate string `json:"release_date,required"`
|
||||
Temperature bool `json:"temperature,required"`
|
||||
ToolCall bool `json:"tool_call,required"`
|
||||
JSON modelJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modelJSON contains the JSON metadata for the struct [Model]
|
||||
type modelJSON struct {
|
||||
ID apijson.Field
|
||||
Attachment apijson.Field
|
||||
Cost apijson.Field
|
||||
Limit apijson.Field
|
||||
Name apijson.Field
|
||||
Options apijson.Field
|
||||
Reasoning apijson.Field
|
||||
ReleaseDate apijson.Field
|
||||
Temperature apijson.Field
|
||||
ToolCall apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Model) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modelJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ModelCost struct {
|
||||
Input float64 `json:"input,required"`
|
||||
Output float64 `json:"output,required"`
|
||||
CacheRead float64 `json:"cache_read"`
|
||||
CacheWrite float64 `json:"cache_write"`
|
||||
JSON modelCostJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modelCostJSON contains the JSON metadata for the struct [ModelCost]
|
||||
type modelCostJSON struct {
|
||||
Input apijson.Field
|
||||
Output apijson.Field
|
||||
CacheRead apijson.Field
|
||||
CacheWrite apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ModelCost) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modelCostJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ModelLimit struct {
|
||||
Context float64 `json:"context,required"`
|
||||
Output float64 `json:"output,required"`
|
||||
JSON modelLimitJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modelLimitJSON contains the JSON metadata for the struct [ModelLimit]
|
||||
type modelLimitJSON struct {
|
||||
Context apijson.Field
|
||||
Output apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ModelLimit) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modelLimitJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
ID string `json:"id,required"`
|
||||
Env []string `json:"env,required"`
|
||||
Models map[string]Model `json:"models,required"`
|
||||
Name string `json:"name,required"`
|
||||
API string `json:"api"`
|
||||
Npm string `json:"npm"`
|
||||
JSON providerJSON `json:"-"`
|
||||
}
|
||||
|
||||
// providerJSON contains the JSON metadata for the struct [Provider]
|
||||
type providerJSON struct {
|
||||
ID apijson.Field
|
||||
Env apijson.Field
|
||||
Models apijson.Field
|
||||
Name apijson.Field
|
||||
API apijson.Field
|
||||
Npm apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Provider) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r providerJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AppProvidersResponse struct {
|
||||
Default map[string]string `json:"default,required"`
|
||||
Providers []Provider `json:"providers,required"`
|
||||
JSON appProvidersResponseJSON `json:"-"`
|
||||
}
|
||||
|
||||
// appProvidersResponseJSON contains the JSON metadata for the struct
|
||||
// [AppProvidersResponse]
|
||||
type appProvidersResponseJSON struct {
|
||||
Default apijson.Field
|
||||
Providers apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AppProvidersResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r appProvidersResponseJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AppLogParams struct {
|
||||
// Log level
|
||||
Level param.Field[AppLogParamsLevel] `json:"level,required"`
|
||||
// Log message
|
||||
Message param.Field[string] `json:"message,required"`
|
||||
// Service name for the log entry
|
||||
Service param.Field[string] `json:"service,required"`
|
||||
// Additional metadata for the log entry
|
||||
Extra param.Field[map[string]interface{}] `json:"extra"`
|
||||
}
|
||||
|
||||
func (r AppLogParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
// Log level
|
||||
type AppLogParamsLevel string
|
||||
|
||||
const (
|
||||
AppLogParamsLevelDebug AppLogParamsLevel = "debug"
|
||||
AppLogParamsLevelInfo AppLogParamsLevel = "info"
|
||||
AppLogParamsLevelError AppLogParamsLevel = "error"
|
||||
AppLogParamsLevelWarn AppLogParamsLevel = "warn"
|
||||
)
|
||||
|
||||
func (r AppLogParamsLevel) IsKnown() bool {
|
||||
switch r {
|
||||
case AppLogParamsLevelDebug, AppLogParamsLevelInfo, AppLogParamsLevelError, AppLogParamsLevelWarn:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
131
packages/sdk/go/app_test.go
Normal file
131
packages/sdk/go/app_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/sst/opencode-sdk-go"
|
||||
"github.com/sst/opencode-sdk-go/internal/testutil"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
func TestAppGet(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.App.Get(context.TODO())
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInit(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.App.Init(context.TODO())
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppLogWithOptionalParams(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.App.Log(context.TODO(), opencode.AppLogParams{
|
||||
Level: opencode.F(opencode.AppLogParamsLevelDebug),
|
||||
Message: opencode.F("message"),
|
||||
Service: opencode.F("service"),
|
||||
Extra: opencode.F(map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppModes(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.App.Modes(context.TODO())
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppProviders(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||
baseURL = envURL
|
||||
}
|
||||
if !testutil.CheckTestServer(t, baseURL) {
|
||||
return
|
||||
}
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.App.Providers(context.TODO())
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
t.Log(string(apierr.DumpRequest(true)))
|
||||
}
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
125
packages/sdk/go/client.go
Normal file
125
packages/sdk/go/client.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
// Client creates a struct with services and top level methods that help with
|
||||
// interacting with the opencode API. You should not instantiate this client
|
||||
// directly, and instead use the [NewClient] method instead.
|
||||
type Client struct {
|
||||
Options []option.RequestOption
|
||||
Event *EventService
|
||||
App *AppService
|
||||
Find *FindService
|
||||
File *FileService
|
||||
Config *ConfigService
|
||||
Session *SessionService
|
||||
Tui *TuiService
|
||||
}
|
||||
|
||||
// DefaultClientOptions read from the environment (OPENCODE_BASE_URL). This should
|
||||
// be used to initialize new clients.
|
||||
func DefaultClientOptions() []option.RequestOption {
|
||||
defaults := []option.RequestOption{option.WithEnvironmentProduction()}
|
||||
if o, ok := os.LookupEnv("OPENCODE_BASE_URL"); ok {
|
||||
defaults = append(defaults, option.WithBaseURL(o))
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
// NewClient generates a new client with the default option read from the
|
||||
// environment (OPENCODE_BASE_URL). The option passed in as arguments are applied
|
||||
// after these default arguments, and all option will be passed down to the
|
||||
// services and requests that this client makes.
|
||||
func NewClient(opts ...option.RequestOption) (r *Client) {
|
||||
opts = append(DefaultClientOptions(), opts...)
|
||||
|
||||
r = &Client{Options: opts}
|
||||
|
||||
r.Event = NewEventService(opts...)
|
||||
r.App = NewAppService(opts...)
|
||||
r.Find = NewFindService(opts...)
|
||||
r.File = NewFileService(opts...)
|
||||
r.Config = NewConfigService(opts...)
|
||||
r.Session = NewSessionService(opts...)
|
||||
r.Tui = NewTuiService(opts...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Execute makes a request with the given context, method, URL, request params,
|
||||
// response, and request options. This is useful for hitting undocumented endpoints
|
||||
// while retaining the base URL, auth, retries, and other options from the client.
|
||||
//
|
||||
// If a byte slice or an [io.Reader] is supplied to params, it will be used as-is
|
||||
// for the request body.
|
||||
//
|
||||
// The params is by default serialized into the body using [encoding/json]. If your
|
||||
// type implements a MarshalJSON function, it will be used instead to serialize the
|
||||
// request. If a URLQuery method is implemented, the returned [url.Values] will be
|
||||
// used as query strings to the url.
|
||||
//
|
||||
// If your params struct uses [param.Field], you must provide either [MarshalJSON],
|
||||
// [URLQuery], and/or [MarshalForm] functions. It is undefined behavior to use a
|
||||
// struct uses [param.Field] without specifying how it is serialized.
|
||||
//
|
||||
// Any "…Params" object defined in this library can be used as the request
|
||||
// argument. Note that 'path' arguments will not be forwarded into the url.
|
||||
//
|
||||
// The response body will be deserialized into the res variable, depending on its
|
||||
// type:
|
||||
//
|
||||
// - A pointer to a [*http.Response] is populated by the raw response.
|
||||
// - A pointer to a byte array will be populated with the contents of the request
|
||||
// body.
|
||||
// - A pointer to any other type uses this library's default JSON decoding, which
|
||||
// respects UnmarshalJSON if it is defined on the type.
|
||||
// - A nil value will not read the response body.
|
||||
//
|
||||
// For even greater flexibility, see [option.WithResponseInto] and
|
||||
// [option.WithResponseBodyInto].
|
||||
func (r *Client) Execute(ctx context.Context, method string, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
opts = append(r.Options, opts...)
|
||||
return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...)
|
||||
}
|
||||
|
||||
// Get makes a GET request with the given URL, params, and optionally deserializes
|
||||
// to a response. See [Execute] documentation on the params and response.
|
||||
func (r *Client) Get(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
return r.Execute(ctx, http.MethodGet, path, params, res, opts...)
|
||||
}
|
||||
|
||||
// Post makes a POST request with the given URL, params, and optionally
|
||||
// deserializes to a response. See [Execute] documentation on the params and
|
||||
// response.
|
||||
func (r *Client) Post(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
return r.Execute(ctx, http.MethodPost, path, params, res, opts...)
|
||||
}
|
||||
|
||||
// Put makes a PUT request with the given URL, params, and optionally deserializes
|
||||
// to a response. See [Execute] documentation on the params and response.
|
||||
func (r *Client) Put(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
return r.Execute(ctx, http.MethodPut, path, params, res, opts...)
|
||||
}
|
||||
|
||||
// Patch makes a PATCH request with the given URL, params, and optionally
|
||||
// deserializes to a response. See [Execute] documentation on the params and
|
||||
// response.
|
||||
func (r *Client) Patch(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
return r.Execute(ctx, http.MethodPatch, path, params, res, opts...)
|
||||
}
|
||||
|
||||
// Delete makes a DELETE request with the given URL, params, and optionally
|
||||
// deserializes to a response. See [Execute] documentation on the params and
|
||||
// response.
|
||||
func (r *Client) Delete(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error {
|
||||
return r.Execute(ctx, http.MethodDelete, path, params, res, opts...)
|
||||
}
|
||||
332
packages/sdk/go/client_test.go
Normal file
332
packages/sdk/go/client_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sst/opencode-sdk-go"
|
||||
"github.com/sst/opencode-sdk-go/internal"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
type closureTransport struct {
|
||||
fn func(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (t *closureTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.fn(req)
|
||||
}
|
||||
|
||||
func TestUserAgentHeader(t *testing.T) {
|
||||
var userAgent string
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
userAgent = req.Header.Get("User-Agent")
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
client.Session.List(context.Background())
|
||||
if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
|
||||
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryAfter(t *testing.T) {
|
||||
retryCountHeaders := make([]string, 0)
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusTooManyRequests,
|
||||
Header: http.Header{
|
||||
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
_, err := client.Session.List(context.Background())
|
||||
if err == nil {
|
||||
t.Error("Expected there to be a cancel error")
|
||||
}
|
||||
|
||||
attempts := len(retryCountHeaders)
|
||||
if attempts != 3 {
|
||||
t.Errorf("Expected %d attempts, got %d", 3, attempts)
|
||||
}
|
||||
|
||||
expectedRetryCountHeaders := []string{"0", "1", "2"}
|
||||
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
|
||||
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRetryCountHeader(t *testing.T) {
|
||||
retryCountHeaders := make([]string, 0)
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusTooManyRequests,
|
||||
Header: http.Header{
|
||||
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
option.WithHeaderDel("X-Stainless-Retry-Count"),
|
||||
)
|
||||
_, err := client.Session.List(context.Background())
|
||||
if err == nil {
|
||||
t.Error("Expected there to be a cancel error")
|
||||
}
|
||||
|
||||
expectedRetryCountHeaders := []string{"", "", ""}
|
||||
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
|
||||
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverwriteRetryCountHeader(t *testing.T) {
|
||||
retryCountHeaders := make([]string, 0)
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusTooManyRequests,
|
||||
Header: http.Header{
|
||||
http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
option.WithHeader("X-Stainless-Retry-Count", "42"),
|
||||
)
|
||||
_, err := client.Session.List(context.Background())
|
||||
if err == nil {
|
||||
t.Error("Expected there to be a cancel error")
|
||||
}
|
||||
|
||||
expectedRetryCountHeaders := []string{"42", "42", "42"}
|
||||
if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
|
||||
t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryAfterMs(t *testing.T) {
|
||||
attempts := 0
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
attempts++
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusTooManyRequests,
|
||||
Header: http.Header{
|
||||
http.CanonicalHeaderKey("Retry-After-Ms"): []string{"100"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
_, err := client.Session.List(context.Background())
|
||||
if err == nil {
|
||||
t.Error("Expected there to be a cancel error")
|
||||
}
|
||||
if want := 3; attempts != want {
|
||||
t.Errorf("Expected %d attempts, got %d", want, attempts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextCancel(t *testing.T) {
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
<-req.Context().Done()
|
||||
return nil, req.Context().Err()
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
_, err := client.Session.List(cancelCtx)
|
||||
if err == nil {
|
||||
t.Error("Expected there to be a cancel error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextCancelDelay(t *testing.T) {
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
<-req.Context().Done()
|
||||
return nil, req.Context().Err()
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err := client.Session.List(cancelCtx)
|
||||
if err == nil {
|
||||
t.Error("expected there to be a cancel error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextDeadline(t *testing.T) {
|
||||
testTimeout := time.After(3 * time.Second)
|
||||
testDone := make(chan struct{})
|
||||
|
||||
deadline := time.Now().Add(100 * time.Millisecond)
|
||||
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
<-req.Context().Done()
|
||||
return nil, req.Context().Err()
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
_, err := client.Session.List(deadlineCtx)
|
||||
if err == nil {
|
||||
t.Error("expected there to be a deadline error")
|
||||
}
|
||||
close(testDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-testTimeout:
|
||||
t.Fatal("client didn't finish in time")
|
||||
case <-testDone:
|
||||
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
|
||||
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextDeadlineStreaming(t *testing.T) {
|
||||
testTimeout := time.After(3 * time.Second)
|
||||
testDone := make(chan struct{})
|
||||
|
||||
deadline := time.Now().Add(100 * time.Millisecond)
|
||||
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Status: "200 OK",
|
||||
Body: io.NopCloser(
|
||||
io.Reader(readerFunc(func([]byte) (int, error) {
|
||||
<-req.Context().Done()
|
||||
return 0, req.Context().Err()
|
||||
})),
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
stream := client.Event.ListStreaming(deadlineCtx)
|
||||
for stream.Next() {
|
||||
_ = stream.Current()
|
||||
}
|
||||
if stream.Err() == nil {
|
||||
t.Error("expected there to be a deadline error")
|
||||
}
|
||||
close(testDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-testTimeout:
|
||||
t.Fatal("client didn't finish in time")
|
||||
case <-testDone:
|
||||
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
|
||||
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
|
||||
testTimeout := time.After(3 * time.Second)
|
||||
testDone := make(chan struct{})
|
||||
deadline := time.Now().Add(100 * time.Millisecond)
|
||||
|
||||
go func() {
|
||||
client := opencode.NewClient(
|
||||
option.WithHTTPClient(&http.Client{
|
||||
Transport: &closureTransport{
|
||||
fn: func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Status: "200 OK",
|
||||
Body: io.NopCloser(
|
||||
io.Reader(readerFunc(func([]byte) (int, error) {
|
||||
<-req.Context().Done()
|
||||
return 0, req.Context().Err()
|
||||
})),
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
stream := client.Event.ListStreaming(context.Background(), option.WithRequestTimeout((100 * time.Millisecond)))
|
||||
for stream.Next() {
|
||||
_ = stream.Current()
|
||||
}
|
||||
if stream.Err() == nil {
|
||||
t.Error("expected there to be a deadline error")
|
||||
}
|
||||
close(testDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-testTimeout:
|
||||
t.Fatal("client didn't finish in time")
|
||||
case <-testDone:
|
||||
if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
|
||||
t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type readerFunc func([]byte) (int, error)
|
||||
|
||||
func (f readerFunc) Read(p []byte) (int, error) { return f(p) }
|
||||
func (f readerFunc) Close() error { return nil }
|
||||
887
packages/sdk/go/config.go
Normal file
887
packages/sdk/go/config.go
Normal file
@@ -0,0 +1,887 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ConfigService contains methods and other services that help with interacting
|
||||
// with the opencode API.
|
||||
//
|
||||
// Note, unlike clients, this service does not read variables from the environment
|
||||
// automatically. You should not instantiate this service directly, and instead use
|
||||
// the [NewConfigService] method instead.
|
||||
type ConfigService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewConfigService generates a new service that applies the given options to each
|
||||
// request. These options are applied after the parent client's options (if there
|
||||
// is one), and before any request-specific options.
|
||||
func NewConfigService(opts ...option.RequestOption) (r *ConfigService) {
|
||||
r = &ConfigService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Get config info
|
||||
func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *Config, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "config"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// JSON schema reference for configuration validation
|
||||
Schema string `json:"$schema"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
Agent ConfigAgent `json:"agent"`
|
||||
// @deprecated Use 'share' field instead. Share newly created sessions
|
||||
// automatically
|
||||
Autoshare bool `json:"autoshare"`
|
||||
// Automatically update to the latest version
|
||||
Autoupdate bool `json:"autoupdate"`
|
||||
// Disable providers that are loaded automatically
|
||||
DisabledProviders []string `json:"disabled_providers"`
|
||||
Experimental ConfigExperimental `json:"experimental"`
|
||||
// Additional instruction files or patterns to include
|
||||
Instructions []string `json:"instructions"`
|
||||
// Custom keybind configurations
|
||||
Keybinds KeybindsConfig `json:"keybinds"`
|
||||
// @deprecated Always uses stretch layout.
|
||||
Layout ConfigLayout `json:"layout"`
|
||||
// MCP (Model Context Protocol) server configurations
|
||||
Mcp map[string]ConfigMcp `json:"mcp"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
Mode ConfigMode `json:"mode"`
|
||||
// Model to use in the format of provider/model, eg anthropic/claude-2
|
||||
Model string `json:"model"`
|
||||
Permission ConfigPermission `json:"permission"`
|
||||
// Custom provider configurations and model overrides
|
||||
Provider map[string]ConfigProvider `json:"provider"`
|
||||
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
||||
// enables automatic sharing, 'disabled' disables all sharing
|
||||
Share ConfigShare `json:"share"`
|
||||
// Small model to use for tasks like summarization and title generation in the
|
||||
// format of provider/model
|
||||
SmallModel string `json:"small_model"`
|
||||
// Theme name to use for the interface
|
||||
Theme string `json:"theme"`
|
||||
// Custom username to display in conversations instead of system username
|
||||
Username string `json:"username"`
|
||||
JSON configJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configJSON contains the JSON metadata for the struct [Config]
|
||||
type configJSON struct {
|
||||
Schema apijson.Field
|
||||
Agent apijson.Field
|
||||
Autoshare apijson.Field
|
||||
Autoupdate apijson.Field
|
||||
DisabledProviders apijson.Field
|
||||
Experimental apijson.Field
|
||||
Instructions apijson.Field
|
||||
Keybinds apijson.Field
|
||||
Layout apijson.Field
|
||||
Mcp apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Permission apijson.Field
|
||||
Provider apijson.Field
|
||||
Share apijson.Field
|
||||
SmallModel apijson.Field
|
||||
Theme apijson.Field
|
||||
Username apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Config) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
type ConfigAgent struct {
|
||||
General ConfigAgentGeneral `json:"general"`
|
||||
ExtraFields map[string]ConfigAgent `json:"-,extras"`
|
||||
JSON configAgentJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configAgentJSON contains the JSON metadata for the struct [ConfigAgent]
|
||||
type configAgentJSON struct {
|
||||
General apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigAgent) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configAgentJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigAgentGeneral struct {
|
||||
Description string `json:"description,required"`
|
||||
JSON configAgentGeneralJSON `json:"-"`
|
||||
ModeConfig
|
||||
}
|
||||
|
||||
// configAgentGeneralJSON contains the JSON metadata for the struct
|
||||
// [ConfigAgentGeneral]
|
||||
type configAgentGeneralJSON struct {
|
||||
Description apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigAgentGeneral) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configAgentGeneralJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigExperimental struct {
|
||||
Hook ConfigExperimentalHook `json:"hook"`
|
||||
JSON configExperimentalJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configExperimentalJSON contains the JSON metadata for the struct
|
||||
// [ConfigExperimental]
|
||||
type configExperimentalJSON struct {
|
||||
Hook apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configExperimentalJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigExperimentalHook struct {
|
||||
FileEdited map[string][]ConfigExperimentalHookFileEdited `json:"file_edited"`
|
||||
SessionCompleted []ConfigExperimentalHookSessionCompleted `json:"session_completed"`
|
||||
JSON configExperimentalHookJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configExperimentalHookJSON contains the JSON metadata for the struct
|
||||
// [ConfigExperimentalHook]
|
||||
type configExperimentalHookJSON struct {
|
||||
FileEdited apijson.Field
|
||||
SessionCompleted apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigExperimentalHook) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configExperimentalHookJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigExperimentalHookFileEdited struct {
|
||||
Command []string `json:"command,required"`
|
||||
Environment map[string]string `json:"environment"`
|
||||
JSON configExperimentalHookFileEditedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configExperimentalHookFileEditedJSON contains the JSON metadata for the struct
|
||||
// [ConfigExperimentalHookFileEdited]
|
||||
type configExperimentalHookFileEditedJSON struct {
|
||||
Command apijson.Field
|
||||
Environment apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigExperimentalHookFileEdited) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configExperimentalHookFileEditedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigExperimentalHookSessionCompleted struct {
|
||||
Command []string `json:"command,required"`
|
||||
Environment map[string]string `json:"environment"`
|
||||
JSON configExperimentalHookSessionCompletedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configExperimentalHookSessionCompletedJSON contains the JSON metadata for the
|
||||
// struct [ConfigExperimentalHookSessionCompleted]
|
||||
type configExperimentalHookSessionCompletedJSON struct {
|
||||
Command apijson.Field
|
||||
Environment apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigExperimentalHookSessionCompleted) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// @deprecated Always uses stretch layout.
|
||||
type ConfigLayout string
|
||||
|
||||
const (
|
||||
ConfigLayoutAuto ConfigLayout = "auto"
|
||||
ConfigLayoutStretch ConfigLayout = "stretch"
|
||||
)
|
||||
|
||||
func (r ConfigLayout) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigLayoutAuto, ConfigLayoutStretch:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigMcp struct {
|
||||
// Type of MCP server connection
|
||||
Type ConfigMcpType `json:"type,required"`
|
||||
// This field can have the runtime type of [[]string].
|
||||
Command interface{} `json:"command"`
|
||||
// Enable or disable the MCP server on startup
|
||||
Enabled bool `json:"enabled"`
|
||||
// This field can have the runtime type of [map[string]string].
|
||||
Environment interface{} `json:"environment"`
|
||||
// This field can have the runtime type of [map[string]string].
|
||||
Headers interface{} `json:"headers"`
|
||||
// URL of the remote MCP server
|
||||
URL string `json:"url"`
|
||||
JSON configMcpJSON `json:"-"`
|
||||
union ConfigMcpUnion
|
||||
}
|
||||
|
||||
// configMcpJSON contains the JSON metadata for the struct [ConfigMcp]
|
||||
type configMcpJSON struct {
|
||||
Type apijson.Field
|
||||
Command apijson.Field
|
||||
Enabled apijson.Field
|
||||
Environment apijson.Field
|
||||
Headers apijson.Field
|
||||
URL apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r configMcpJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r *ConfigMcp) UnmarshalJSON(data []byte) (err error) {
|
||||
*r = ConfigMcp{}
|
||||
err = apijson.UnmarshalRoot(data, &r.union)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apijson.Port(r.union, &r)
|
||||
}
|
||||
|
||||
// AsUnion returns a [ConfigMcpUnion] interface which you can cast to the specific
|
||||
// types for more type safety.
|
||||
//
|
||||
// Possible runtime types of the union are [McpLocalConfig], [McpRemoteConfig].
|
||||
func (r ConfigMcp) AsUnion() ConfigMcpUnion {
|
||||
return r.union
|
||||
}
|
||||
|
||||
// Union satisfied by [McpLocalConfig] or [McpRemoteConfig].
|
||||
type ConfigMcpUnion interface {
|
||||
implementsConfigMcp()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*ConfigMcpUnion)(nil)).Elem(),
|
||||
"type",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(McpLocalConfig{}),
|
||||
DiscriminatorValue: "local",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(McpRemoteConfig{}),
|
||||
DiscriminatorValue: "remote",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Type of MCP server connection
|
||||
type ConfigMcpType string
|
||||
|
||||
const (
|
||||
ConfigMcpTypeLocal ConfigMcpType = "local"
|
||||
ConfigMcpTypeRemote ConfigMcpType = "remote"
|
||||
)
|
||||
|
||||
func (r ConfigMcpType) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigMcpTypeLocal, ConfigMcpTypeRemote:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
type ConfigMode struct {
|
||||
Build ModeConfig `json:"build"`
|
||||
Plan ModeConfig `json:"plan"`
|
||||
ExtraFields map[string]ModeConfig `json:"-,extras"`
|
||||
JSON configModeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configModeJSON contains the JSON metadata for the struct [ConfigMode]
|
||||
type configModeJSON struct {
|
||||
Build apijson.Field
|
||||
Plan apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigMode) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configModeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigPermission struct {
|
||||
Bash ConfigPermissionBashUnion `json:"bash"`
|
||||
Edit ConfigPermissionEdit `json:"edit"`
|
||||
JSON configPermissionJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configPermissionJSON contains the JSON metadata for the struct
|
||||
// [ConfigPermission]
|
||||
type configPermissionJSON struct {
|
||||
Bash apijson.Field
|
||||
Edit apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigPermission) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configPermissionJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Union satisfied by [ConfigPermissionBashString] or [ConfigPermissionBashMap].
|
||||
type ConfigPermissionBashUnion interface {
|
||||
implementsConfigPermissionBashUnion()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*ConfigPermissionBashUnion)(nil)).Elem(),
|
||||
"",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.String,
|
||||
Type: reflect.TypeOf(ConfigPermissionBashString("")),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(ConfigPermissionBashMap{}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type ConfigPermissionBashString string
|
||||
|
||||
const (
|
||||
ConfigPermissionBashStringAsk ConfigPermissionBashString = "ask"
|
||||
ConfigPermissionBashStringAllow ConfigPermissionBashString = "allow"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionBashString) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r ConfigPermissionBashString) implementsConfigPermissionBashUnion() {}
|
||||
|
||||
type ConfigPermissionBashMap map[string]ConfigPermissionBashMapItem
|
||||
|
||||
func (r ConfigPermissionBashMap) implementsConfigPermissionBashUnion() {}
|
||||
|
||||
type ConfigPermissionBashMapItem string
|
||||
|
||||
const (
|
||||
ConfigPermissionBashMapAsk ConfigPermissionBashMapItem = "ask"
|
||||
ConfigPermissionBashMapAllow ConfigPermissionBashMapItem = "allow"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionBashMapItem) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigPermissionEdit string
|
||||
|
||||
const (
|
||||
ConfigPermissionEditAsk ConfigPermissionEdit = "ask"
|
||||
ConfigPermissionEditAllow ConfigPermissionEdit = "allow"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionEdit) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionEditAsk, ConfigPermissionEditAllow:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigProvider struct {
|
||||
Models map[string]ConfigProviderModel `json:"models,required"`
|
||||
ID string `json:"id"`
|
||||
API string `json:"api"`
|
||||
Env []string `json:"env"`
|
||||
Name string `json:"name"`
|
||||
Npm string `json:"npm"`
|
||||
Options ConfigProviderOptions `json:"options"`
|
||||
JSON configProviderJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderJSON contains the JSON metadata for the struct [ConfigProvider]
|
||||
type configProviderJSON struct {
|
||||
Models apijson.Field
|
||||
ID apijson.Field
|
||||
API apijson.Field
|
||||
Env apijson.Field
|
||||
Name apijson.Field
|
||||
Npm apijson.Field
|
||||
Options apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigProvider) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configProviderJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigProviderModel struct {
|
||||
ID string `json:"id"`
|
||||
Attachment bool `json:"attachment"`
|
||||
Cost ConfigProviderModelsCost `json:"cost"`
|
||||
Limit ConfigProviderModelsLimit `json:"limit"`
|
||||
Name string `json:"name"`
|
||||
Options map[string]interface{} `json:"options"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Temperature bool `json:"temperature"`
|
||||
ToolCall bool `json:"tool_call"`
|
||||
JSON configProviderModelJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderModelJSON contains the JSON metadata for the struct
|
||||
// [ConfigProviderModel]
|
||||
type configProviderModelJSON struct {
|
||||
ID apijson.Field
|
||||
Attachment apijson.Field
|
||||
Cost apijson.Field
|
||||
Limit apijson.Field
|
||||
Name apijson.Field
|
||||
Options apijson.Field
|
||||
Reasoning apijson.Field
|
||||
ReleaseDate apijson.Field
|
||||
Temperature apijson.Field
|
||||
ToolCall apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configProviderModelJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigProviderModelsCost struct {
|
||||
Input float64 `json:"input,required"`
|
||||
Output float64 `json:"output,required"`
|
||||
CacheRead float64 `json:"cache_read"`
|
||||
CacheWrite float64 `json:"cache_write"`
|
||||
JSON configProviderModelsCostJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderModelsCostJSON contains the JSON metadata for the struct
|
||||
// [ConfigProviderModelsCost]
|
||||
type configProviderModelsCostJSON struct {
|
||||
Input apijson.Field
|
||||
Output apijson.Field
|
||||
CacheRead apijson.Field
|
||||
CacheWrite apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigProviderModelsCost) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configProviderModelsCostJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigProviderModelsLimit struct {
|
||||
Context float64 `json:"context,required"`
|
||||
Output float64 `json:"output,required"`
|
||||
JSON configProviderModelsLimitJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderModelsLimitJSON contains the JSON metadata for the struct
|
||||
// [ConfigProviderModelsLimit]
|
||||
type configProviderModelsLimitJSON struct {
|
||||
Context apijson.Field
|
||||
Output apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigProviderModelsLimit) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configProviderModelsLimitJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigProviderOptions struct {
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
ExtraFields map[string]interface{} `json:"-,extras"`
|
||||
JSON configProviderOptionsJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configProviderOptionsJSON contains the JSON metadata for the struct
|
||||
// [ConfigProviderOptions]
|
||||
type configProviderOptionsJSON struct {
|
||||
APIKey apijson.Field
|
||||
BaseURL apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigProviderOptions) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configProviderOptionsJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
|
||||
// enables automatic sharing, 'disabled' disables all sharing
|
||||
type ConfigShare string
|
||||
|
||||
const (
|
||||
ConfigShareManual ConfigShare = "manual"
|
||||
ConfigShareAuto ConfigShare = "auto"
|
||||
ConfigShareDisabled ConfigShare = "disabled"
|
||||
)
|
||||
|
||||
func (r ConfigShare) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigShareManual, ConfigShareAuto, ConfigShareDisabled:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type KeybindsConfig struct {
|
||||
// Exit the application
|
||||
AppExit string `json:"app_exit,required"`
|
||||
// Show help dialog
|
||||
AppHelp string `json:"app_help,required"`
|
||||
// Open external editor
|
||||
EditorOpen string `json:"editor_open,required"`
|
||||
// Close file
|
||||
FileClose string `json:"file_close,required"`
|
||||
// Split/unified diff
|
||||
FileDiffToggle string `json:"file_diff_toggle,required"`
|
||||
// List files
|
||||
FileList string `json:"file_list,required"`
|
||||
// Search file
|
||||
FileSearch string `json:"file_search,required"`
|
||||
// Clear input field
|
||||
InputClear string `json:"input_clear,required"`
|
||||
// Insert newline in input
|
||||
InputNewline string `json:"input_newline,required"`
|
||||
// Paste from clipboard
|
||||
InputPaste string `json:"input_paste,required"`
|
||||
// Submit input
|
||||
InputSubmit string `json:"input_submit,required"`
|
||||
// Leader key for keybind combinations
|
||||
Leader string `json:"leader,required"`
|
||||
// Copy message
|
||||
MessagesCopy string `json:"messages_copy,required"`
|
||||
// Navigate to first message
|
||||
MessagesFirst string `json:"messages_first,required"`
|
||||
// Scroll messages down by half page
|
||||
MessagesHalfPageDown string `json:"messages_half_page_down,required"`
|
||||
// Scroll messages up by half page
|
||||
MessagesHalfPageUp string `json:"messages_half_page_up,required"`
|
||||
// Navigate to last message
|
||||
MessagesLast string `json:"messages_last,required"`
|
||||
// Toggle layout
|
||||
MessagesLayoutToggle string `json:"messages_layout_toggle,required"`
|
||||
// Navigate to next message
|
||||
MessagesNext string `json:"messages_next,required"`
|
||||
// Scroll messages down by one page
|
||||
MessagesPageDown string `json:"messages_page_down,required"`
|
||||
// Scroll messages up by one page
|
||||
MessagesPageUp string `json:"messages_page_up,required"`
|
||||
// Navigate to previous message
|
||||
MessagesPrevious string `json:"messages_previous,required"`
|
||||
// Redo message
|
||||
MessagesRedo string `json:"messages_redo,required"`
|
||||
// @deprecated use messages_undo. Revert message
|
||||
MessagesRevert string `json:"messages_revert,required"`
|
||||
// Undo message
|
||||
MessagesUndo string `json:"messages_undo,required"`
|
||||
// List available models
|
||||
ModelList string `json:"model_list,required"`
|
||||
// Create/update AGENTS.md
|
||||
ProjectInit string `json:"project_init,required"`
|
||||
// Compact the session
|
||||
SessionCompact string `json:"session_compact,required"`
|
||||
// Export session to editor
|
||||
SessionExport string `json:"session_export,required"`
|
||||
// Interrupt current session
|
||||
SessionInterrupt string `json:"session_interrupt,required"`
|
||||
// List all sessions
|
||||
SessionList string `json:"session_list,required"`
|
||||
// Create a new session
|
||||
SessionNew string `json:"session_new,required"`
|
||||
// Share current session
|
||||
SessionShare string `json:"session_share,required"`
|
||||
// Unshare current session
|
||||
SessionUnshare string `json:"session_unshare,required"`
|
||||
// Next mode
|
||||
SwitchMode string `json:"switch_mode,required"`
|
||||
// Previous Mode
|
||||
SwitchModeReverse string `json:"switch_mode_reverse,required"`
|
||||
// List available themes
|
||||
ThemeList string `json:"theme_list,required"`
|
||||
// Toggle tool details
|
||||
ToolDetails string `json:"tool_details,required"`
|
||||
JSON keybindsConfigJSON `json:"-"`
|
||||
}
|
||||
|
||||
// keybindsConfigJSON contains the JSON metadata for the struct [KeybindsConfig]
|
||||
type keybindsConfigJSON struct {
|
||||
AppExit apijson.Field
|
||||
AppHelp apijson.Field
|
||||
EditorOpen apijson.Field
|
||||
FileClose apijson.Field
|
||||
FileDiffToggle apijson.Field
|
||||
FileList apijson.Field
|
||||
FileSearch apijson.Field
|
||||
InputClear apijson.Field
|
||||
InputNewline apijson.Field
|
||||
InputPaste apijson.Field
|
||||
InputSubmit apijson.Field
|
||||
Leader apijson.Field
|
||||
MessagesCopy apijson.Field
|
||||
MessagesFirst apijson.Field
|
||||
MessagesHalfPageDown apijson.Field
|
||||
MessagesHalfPageUp apijson.Field
|
||||
MessagesLast apijson.Field
|
||||
MessagesLayoutToggle apijson.Field
|
||||
MessagesNext apijson.Field
|
||||
MessagesPageDown apijson.Field
|
||||
MessagesPageUp apijson.Field
|
||||
MessagesPrevious apijson.Field
|
||||
MessagesRedo apijson.Field
|
||||
MessagesRevert apijson.Field
|
||||
MessagesUndo apijson.Field
|
||||
ModelList apijson.Field
|
||||
ProjectInit apijson.Field
|
||||
SessionCompact apijson.Field
|
||||
SessionExport apijson.Field
|
||||
SessionInterrupt apijson.Field
|
||||
SessionList apijson.Field
|
||||
SessionNew apijson.Field
|
||||
SessionShare apijson.Field
|
||||
SessionUnshare apijson.Field
|
||||
SwitchMode apijson.Field
|
||||
SwitchModeReverse apijson.Field
|
||||
ThemeList apijson.Field
|
||||
ToolDetails apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *KeybindsConfig) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r keybindsConfigJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type McpLocalConfig struct {
|
||||
// Command and arguments to run the MCP server
|
||||
Command []string `json:"command,required"`
|
||||
// Type of MCP server connection
|
||||
Type McpLocalConfigType `json:"type,required"`
|
||||
// Enable or disable the MCP server on startup
|
||||
Enabled bool `json:"enabled"`
|
||||
// Environment variables to set when running the MCP server
|
||||
Environment map[string]string `json:"environment"`
|
||||
JSON mcpLocalConfigJSON `json:"-"`
|
||||
}
|
||||
|
||||
// mcpLocalConfigJSON contains the JSON metadata for the struct [McpLocalConfig]
|
||||
type mcpLocalConfigJSON struct {
|
||||
Command apijson.Field
|
||||
Type apijson.Field
|
||||
Enabled apijson.Field
|
||||
Environment apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *McpLocalConfig) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r mcpLocalConfigJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r McpLocalConfig) implementsConfigMcp() {}
|
||||
|
||||
// Type of MCP server connection
|
||||
type McpLocalConfigType string
|
||||
|
||||
const (
|
||||
McpLocalConfigTypeLocal McpLocalConfigType = "local"
|
||||
)
|
||||
|
||||
func (r McpLocalConfigType) IsKnown() bool {
|
||||
switch r {
|
||||
case McpLocalConfigTypeLocal:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type McpRemoteConfig struct {
|
||||
// Type of MCP server connection
|
||||
Type McpRemoteConfigType `json:"type,required"`
|
||||
// URL of the remote MCP server
|
||||
URL string `json:"url,required"`
|
||||
// Enable or disable the MCP server on startup
|
||||
Enabled bool `json:"enabled"`
|
||||
// Headers to send with the request
|
||||
Headers map[string]string `json:"headers"`
|
||||
JSON mcpRemoteConfigJSON `json:"-"`
|
||||
}
|
||||
|
||||
// mcpRemoteConfigJSON contains the JSON metadata for the struct [McpRemoteConfig]
|
||||
type mcpRemoteConfigJSON struct {
|
||||
Type apijson.Field
|
||||
URL apijson.Field
|
||||
Enabled apijson.Field
|
||||
Headers apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *McpRemoteConfig) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r mcpRemoteConfigJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r McpRemoteConfig) implementsConfigMcp() {}
|
||||
|
||||
// Type of MCP server connection
|
||||
type McpRemoteConfigType string
|
||||
|
||||
const (
|
||||
McpRemoteConfigTypeRemote McpRemoteConfigType = "remote"
|
||||
)
|
||||
|
||||
func (r McpRemoteConfigType) IsKnown() bool {
|
||||
switch r {
|
||||
case McpRemoteConfigTypeRemote:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ModeConfig struct {
|
||||
Disable bool `json:"disable"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
JSON modeConfigJSON `json:"-"`
|
||||
}
|
||||
|
||||
// modeConfigJSON contains the JSON metadata for the struct [ModeConfig]
|
||||
type modeConfigJSON struct {
|
||||
Disable apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ModeConfig) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r modeConfigJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user