Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f164c53d2 | |||
| 02ef96f89b | |||
| 8750744068 | |||
| 3e74107e36 | |||
| 160f839b25 | |||
| bf5b109c1f | |||
| 60254d8ac0 | |||
| c34aec060f | |||
| 12f1ad521f | |||
| 723a37ea9a | |||
| c6a46615c0 | |||
| da29380093 | |||
| 7950ae1462 | |||
| 15e830410f | |||
| 1a561bb512 | |||
| fecae609d9 | |||
| e01a540b08 | |||
| 54457e48bb | |||
| b179d08484 | |||
| d9edd6818f | |||
| 4217286b72 | |||
| 28a4517ec6 | |||
| b00b2ded4f | |||
| 7b6d5b1429 | |||
| 7210db19e9 | |||
| 90d2b26426 | |||
| 6beba2c04f | |||
| b8a0ecca98 | |||
| ad10d3a126 | |||
| a48274f82b | |||
| 6b25b7e95e | |||
| 030a3a7446 | |||
| 1a0e7f1e63 | |||
| 677fb6032b | |||
| 49aa48ce58 | |||
| 857a3cd522 | |||
| 6ed774ef62 | |||
| 5e825a4b6a | |||
| 3db8e7c2b6 | |||
| b459055757 | |||
| 2b195e82ee | |||
| 58e889796c | |||
| 51498c8de4 | |||
| 7a495faa49 | |||
| 4957fca718 | |||
| 8168626cd3 | |||
| b824809605 | |||
| 5536b14347 | |||
| 01efe236ef | |||
| 7a1f96399d | |||
| 40036abb9d | |||
| 2970ba6416 | |||
| 81412b6197 | |||
| 5bf7691ea6 | |||
| b1055a74d3 | |||
| ffcb27fa9a | |||
| 38819e89b8 | |||
| 0a42068fbb | |||
| b05decc572 | |||
| c2f487906a | |||
| ae78ec7a0c | |||
| e8c03f13dd | |||
| f85d30c484 | |||
| 1bac46612c | |||
| 9ab3462821 | |||
| 3b36822696 | |||
| 5b731479d5 | |||
| a50bef6913 | |||
| ed397c5057 | |||
| c9187a9f3a | |||
| 2c67b26b5d | |||
| 170b94a99e | |||
| cd58f10e3c | |||
| ea85fdf3cd | |||
| edda26ab33 | |||
| ea4e1913c0 | |||
| 5eebc8ab51 | |||
| 21c52fd5cb | |||
| 5e8634afaf | |||
| d4bac5cdbd | |||
| 263b266476 | |||
| 06830327e7 | |||
| 4b204fee58 | |||
| 99d3a0bb24 | |||
| 0930f6ac55 | |||
| 24515162fa | |||
| 53aa899e45 | |||
| 7e763e1c06 | |||
| b0f2cc0c22 | |||
| f90aa62784 | |||
| 852191f6cb | |||
| c5e9dc081c | |||
| 49c8889228 | |||
| f739e1a958 | |||
| 841f1907bb | |||
| 9255c507d6 | |||
| 2711047166 | |||
| 908048baef | |||
| a9fbe07408 | |||
| 0ae213ee0e | |||
| ca031278ca | |||
| ae6e47bb42 | |||
| 42a5fcead4 | |||
| 8ad83f71a9 | |||
| fa95c09cdc | |||
| 0b132c032a | |||
| 44d7103a42 | |||
| 8f45a0e227 | |||
| 6581741318 | |||
| 80d68d01f4 | |||
| fa9db3c167 | |||
| 5a727c0794 | |||
| 71cd84dbbb | |||
| e1b7e25f4d | |||
| 98b6bb218b | |||
| 5592ce8eaf | |||
| 510fe8a72a | |||
| 04a1ab3893 | |||
| e74b4d098b | |||
| 50e4b3e6a7 | |||
| 6ebd828aa5 | |||
| 022c979d28 | |||
| 4172e3ad28 | |||
| 90d1698aed | |||
| b0c38ce56b | |||
| 9b37d0e191 | |||
| ea794a4bf6 | |||
| 52f9b37576 | |||
| a0d2e53bde | |||
| 851e900982 | |||
| 3aa6eeb426 | |||
| b6ee8e92f9 | |||
| 44211e1526 | |||
| 12f84f198f | |||
| e6db1cf29d | |||
| f07f04d969 | |||
| 33d613a470 | |||
| 0bbd7ea17b | |||
| 87f3166437 | |||
| 7665bd9439 | |||
| 30e10127f2 | |||
| 5e66fc2318 | |||
| c1c99c7e0f | |||
| 04e3e83db3 | |||
| 4273714a62 | |||
| a21e237706 | |||
| aa9105649d | |||
| 53be288040 | |||
| 13dbf912ca | |||
| 69966c73f8 | |||
| a00de2df08 | |||
| 5e72f50554 | |||
| d558f15c91 | |||
| 614a23698f | |||
| a2191ce6fb | |||
| 168350c981 | |||
| f5f55062f1 | |||
| 360194e219 | |||
| 5ee994c31f | |||
| fc73d4b1f9 | |||
| 936f4cb0c6 | |||
| a5b20f973f | |||
| 872b1e068f | |||
| e4e0b8fd34 | |||
| c5368e7412 | |||
| 1d682544b9 | |||
| d9210af98c | |||
| ef633fe92e | |||
| 5500698734 | |||
| e7631763f3 |
@@ -7,8 +7,10 @@ on:
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,4 +26,4 @@ jobs:
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
|
||||
@@ -39,6 +39,15 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Cache ~/.bun
|
||||
id: cache-bun
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install makepkg
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -53,9 +62,11 @@ jobs:
|
||||
git config --global user.email "opencode@sst.dev"
|
||||
git config --global user.name "opencode"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
bun install
|
||||
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
|
||||
|
||||
@@ -30,3 +30,5 @@ jobs:
|
||||
git add STATS.md
|
||||
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
||||
git push
|
||||
env:
|
||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Typecheck
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
@@ -5,4 +5,4 @@ node_modules
|
||||
.idea
|
||||
.vscode
|
||||
openapi.json
|
||||
scratch
|
||||
playground
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
description: You MUST use this agent when writing documentation
|
||||
---
|
||||
|
||||
You are an expert technical documentation writer
|
||||
|
||||
You are not verbose
|
||||
|
||||
Every chunk of text should be followed by an example or something besides text
|
||||
to look at.
|
||||
|
||||
Chunks of text should not be more than 2 sentences long.
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,13 +1,12 @@
|
||||
## Style
|
||||
## IMPORTANT
|
||||
|
||||
- prefer single word variable/function names
|
||||
- avoid try catch where possible - prefer to let exceptions bubble up
|
||||
- avoid else statements where possible
|
||||
- do not make useless helper functions - inline functionality unless the
|
||||
function is reusable or composable
|
||||
- prefer Bun apis
|
||||
|
||||
## Workflow
|
||||
|
||||
- you can regenerate the golang sdk by calling ./scripts/stainless.ts
|
||||
- we use bun for everything
|
||||
- Try to keep things in one function unless composable or reusable
|
||||
- DO NOT do unnecessary destructuring of variables
|
||||
- DO NOT use `else` statements unless necessary
|
||||
- DO NOT use `try`/`catch` if it can be avoided
|
||||
- AVOID `try`/`catch` where possible
|
||||
- AVOID `else` statements
|
||||
- AVOID using `any` type
|
||||
- AVOID `let` statements
|
||||
- PREFER single word variable names where possible
|
||||
- Use as many bun apis as possible like Bun.file()
|
||||
|
||||
@@ -83,7 +83,7 @@ And run.
|
||||
|
||||
```bash
|
||||
$ bun install
|
||||
$ bun run packages/opencode/src/index.ts
|
||||
$ bun dev
|
||||
```
|
||||
|
||||
#### Development Notes
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
# Download Stats
|
||||
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ----------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-10 | 43,796 (+5,744) | 71,402 (+6,934) | 115,198 (+12,678) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 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) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| Date | GitHub Downloads | npm Downloads | Total |
|
||||
| ---------- | ---------------- | ---------------- | ---------------- |
|
||||
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
|
||||
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
|
||||
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
|
||||
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
|
||||
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
|
||||
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
|
||||
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
|
||||
| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
|
||||
| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
|
||||
| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
|
||||
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
|
||||
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
|
||||
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
|
||||
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
|
||||
| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
|
||||
| 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) |
|
||||
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
|
||||
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
|
||||
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
|
||||
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
|
||||
| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
|
||||
| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
|
||||
| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
|
||||
| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
|
||||
|
||||
@@ -46,3 +46,15 @@ new sst.cloudflare.x.Astro("Web", {
|
||||
VITE_API_URL: api.url,
|
||||
},
|
||||
})
|
||||
|
||||
const OPENCODE_API_KEY = new sst.Secret("OPENCODE_API_KEY")
|
||||
const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
|
||||
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
|
||||
const ZHIPU_API_KEY = new sst.Secret("ZHIPU_API_KEY")
|
||||
|
||||
export const gateway = new sst.cloudflare.Worker("GatewayApi", {
|
||||
domain: `api.gateway.${domain}`,
|
||||
handler: "packages/function/src/gateway.ts",
|
||||
url: true,
|
||||
link: [OPENCODE_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, ZHIPU_API_KEY],
|
||||
})
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"openrouter": {
|
||||
"models": {
|
||||
"moonshotai/kimi-k2": {
|
||||
"options": {
|
||||
"provider": {
|
||||
"order": ["baseten"],
|
||||
"allow_fallbacks": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"huggingface": {
|
||||
"models": {
|
||||
"Qwen/Qwen3-235B-A22B-Instruct-2507:fireworks-ai": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcp": {
|
||||
"context7": {
|
||||
"type": "remote",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.14",
|
||||
"scripts": {
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"stainless": "./scripts/stainless",
|
||||
"postinstall": "./script/hooks"
|
||||
@@ -18,7 +18,7 @@
|
||||
"catalog": {
|
||||
"@types/node": "22.13.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"ai": "5.0.0-beta.33",
|
||||
"ai": "5.0.0-beta.34",
|
||||
"hono": "4.7.10",
|
||||
"typescript": "5.8.2",
|
||||
"zod": "3.25.49",
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.0.1",
|
||||
"version": "0.3.133",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "4.20250522.0",
|
||||
"typescript": "catalog:",
|
||||
"@types/node": "catalog:"
|
||||
"@types/node": "catalog:",
|
||||
"openai": "5.11.0",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
"@ai-sdk/openai-compatible": "1.0.1",
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"ai": "catalog:",
|
||||
"hono": "catalog:",
|
||||
"jose": "6.0.11"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
import { Hono, Context, Next } from "hono"
|
||||
import { Resource } from "sst"
|
||||
import { generateText, streamText } from "ai"
|
||||
import { createAnthropic } from "@ai-sdk/anthropic"
|
||||
import { createOpenAI } from "@ai-sdk/openai"
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
|
||||
import { type LanguageModelV2Prompt } from "@ai-sdk/provider"
|
||||
import { type ChatCompletionCreateParamsBase } from "openai/resources/chat/completions"
|
||||
|
||||
type Env = {}
|
||||
|
||||
const auth = async (c: Context, next: Next) => {
|
||||
const authHeader = c.req.header("authorization")
|
||||
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
message: "Missing API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
const apiKey = authHeader.split(" ")[1]
|
||||
|
||||
// Replace with your validation logic
|
||||
if (apiKey !== Resource.OPENCODE_API_KEY.value) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
message: "Invalid API key.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "unauthorized",
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
await next()
|
||||
}
|
||||
export default new Hono<{ Bindings: Env }>()
|
||||
.get("/", (c) => c.text("Hello, world!"))
|
||||
.post("/v1/chat/completions", auth, async (c) => {
|
||||
try {
|
||||
const body = await c.req.json<ChatCompletionCreateParamsBase>()
|
||||
|
||||
console.log(body)
|
||||
|
||||
const model = (() => {
|
||||
const [provider, ...parts] = body.model.split("/")
|
||||
const model = parts.join("/")
|
||||
if (provider === "anthropic" && model === "claude-sonnet-4") {
|
||||
return createAnthropic({
|
||||
apiKey: Resource.ANTHROPIC_API_KEY.value,
|
||||
})("claude-sonnet-4-20250514")
|
||||
}
|
||||
if (provider === "openai" && model === "gpt-4.1") {
|
||||
return createOpenAI({
|
||||
apiKey: Resource.OPENAI_API_KEY.value,
|
||||
})("gpt-4.1")
|
||||
}
|
||||
if (provider === "zhipuai" && model === "glm-4.5-flash") {
|
||||
return createOpenAICompatible({
|
||||
name: "Zhipu AI",
|
||||
baseURL: "https://api.z.ai/api/paas/v4",
|
||||
apiKey: Resource.ZHIPU_API_KEY.value,
|
||||
})("glm-4.5-flash")
|
||||
}
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
})()
|
||||
|
||||
const requestBody = transformOpenAIRequestToAiSDK()
|
||||
|
||||
return body.stream ? await handleStream() : await handleGenerate()
|
||||
|
||||
async function handleStream() {
|
||||
const result = await streamText({
|
||||
model,
|
||||
...requestBody,
|
||||
})
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const id = `chatcmpl-${Date.now()}`
|
||||
const created = Math.floor(Date.now() / 1000)
|
||||
|
||||
try {
|
||||
for await (const chunk of result.fullStream) {
|
||||
// TODO
|
||||
//console.log("!!! CHUCK !!!", chunk);
|
||||
switch (chunk.type) {
|
||||
case "text-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content: chunk.text,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "reasoning-delta": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
reasoning_content: chunk.text,
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "tool-call": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
id: chunk.toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: chunk.toolName,
|
||||
arguments: JSON.stringify(chunk.input),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
break
|
||||
}
|
||||
|
||||
case "error": {
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
error: {
|
||||
message: chunk.error,
|
||||
type: "server_error",
|
||||
},
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
case "finish": {
|
||||
const finishReason =
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
}[chunk.finishReason] || "stop"
|
||||
|
||||
const data = {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: chunk.totalUsage.inputTokens,
|
||||
completion_tokens: chunk.totalUsage.outputTokens,
|
||||
total_tokens: chunk.totalUsage.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: chunk.totalUsage.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: chunk.totalUsage.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
break
|
||||
}
|
||||
|
||||
//case "stream-start":
|
||||
//case "response-metadata":
|
||||
case "start-step":
|
||||
case "finish-step":
|
||||
case "text-start":
|
||||
case "text-end":
|
||||
case "reasoning-start":
|
||||
case "reasoning-end":
|
||||
case "tool-input-start":
|
||||
case "tool-input-delta":
|
||||
case "tool-input-end":
|
||||
case "raw":
|
||||
default:
|
||||
// Log unknown chunk types for debugging
|
||||
console.warn(`Unknown chunk type: ${(chunk as any).type}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function handleGenerate() {
|
||||
const response = await generateText({
|
||||
model,
|
||||
...requestBody,
|
||||
})
|
||||
return c.json({
|
||||
id: `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion" as const,
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: body.model,
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant" as const,
|
||||
content: response.content?.find((c) => c.type === "text")?.text ?? "",
|
||||
reasoning_content: response.content?.find((c) => c.type === "reasoning")?.text,
|
||||
tool_calls: response.content
|
||||
?.filter((c) => c.type === "tool-call")
|
||||
.map((toolCall) => ({
|
||||
id: toolCall.toolCallId,
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: toolCall.toolName,
|
||||
arguments: toolCall.input,
|
||||
},
|
||||
})),
|
||||
},
|
||||
finish_reason:
|
||||
(
|
||||
{
|
||||
stop: "stop",
|
||||
length: "length",
|
||||
"content-filter": "content_filter",
|
||||
"tool-calls": "tool_calls",
|
||||
error: "stop",
|
||||
other: "stop",
|
||||
unknown: "stop",
|
||||
} as const
|
||||
)[response.finishReason] || "stop",
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: response.usage?.inputTokens,
|
||||
completion_tokens: response.usage?.outputTokens,
|
||||
total_tokens: response.usage?.totalTokens,
|
||||
completion_tokens_details: {
|
||||
reasoning_tokens: response.usage?.reasoningTokens,
|
||||
},
|
||||
prompt_tokens_details: {
|
||||
cached_tokens: response.usage?.cachedInputTokens,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function transformOpenAIRequestToAiSDK() {
|
||||
const prompt = transformMessages()
|
||||
|
||||
return {
|
||||
prompt,
|
||||
maxOutputTokens: body.max_tokens ?? body.max_completion_tokens ?? undefined,
|
||||
temperature: body.temperature ?? undefined,
|
||||
topP: body.top_p ?? undefined,
|
||||
frequencyPenalty: body.frequency_penalty ?? undefined,
|
||||
presencePenalty: body.presence_penalty ?? undefined,
|
||||
providerOptions: body.reasoning_effort
|
||||
? {
|
||||
anthropic: {
|
||||
reasoningEffort: body.reasoning_effort,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
stopSequences: (typeof body.stop === "string" ? [body.stop] : body.stop) ?? undefined,
|
||||
responseFormat: (() => {
|
||||
if (!body.response_format) return { type: "text" }
|
||||
if (body.response_format.type === "json_schema")
|
||||
return {
|
||||
type: "json",
|
||||
schema: body.response_format.json_schema.schema,
|
||||
name: body.response_format.json_schema.name,
|
||||
description: body.response_format.json_schema.description,
|
||||
}
|
||||
if (body.response_format.type === "json_object") return { type: "json" }
|
||||
throw new Error("Unsupported response format")
|
||||
})(),
|
||||
seed: body.seed ?? undefined,
|
||||
}
|
||||
|
||||
function transformTools() {
|
||||
const { tools, tool_choice } = body
|
||||
|
||||
if (!tools || tools.length === 0) {
|
||||
return { tools: undefined, toolChoice: undefined }
|
||||
}
|
||||
|
||||
const aiSdkTools = tools.reduce(
|
||||
(acc, tool) => {
|
||||
acc[tool.function.name] = {
|
||||
type: "function" as const,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
inputSchema: tool.function.parameters,
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
)
|
||||
|
||||
let aiSdkToolChoice
|
||||
if (tool_choice == null) {
|
||||
aiSdkToolChoice = undefined
|
||||
} else if (tool_choice === "auto") {
|
||||
aiSdkToolChoice = "auto"
|
||||
} else if (tool_choice === "none") {
|
||||
aiSdkToolChoice = "none"
|
||||
} else if (tool_choice === "required") {
|
||||
aiSdkToolChoice = "required"
|
||||
} else if (tool_choice.type === "function") {
|
||||
aiSdkToolChoice = {
|
||||
type: "tool",
|
||||
toolName: tool_choice.function.name,
|
||||
}
|
||||
}
|
||||
|
||||
return { tools: aiSdkTools, toolChoice: aiSdkToolChoice }
|
||||
}
|
||||
|
||||
function transformMessages() {
|
||||
const { messages } = body
|
||||
const prompt: LanguageModelV2Prompt = []
|
||||
|
||||
for (const message of messages) {
|
||||
switch (message.role) {
|
||||
case "system": {
|
||||
prompt.push({
|
||||
role: "system",
|
||||
content: message.content as string,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "user": {
|
||||
if (typeof message.content === "string") {
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: message.content }],
|
||||
})
|
||||
} else {
|
||||
const content = message.content.map((part) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return { type: "text" as const, text: part.text }
|
||||
case "image_url":
|
||||
return {
|
||||
type: "file" as const,
|
||||
mediaType: "image/jpeg" as const,
|
||||
data: part.image_url.url,
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported content part type: ${(part as any).type}`)
|
||||
}
|
||||
})
|
||||
prompt.push({
|
||||
role: "user",
|
||||
content,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "assistant": {
|
||||
const content: Array<
|
||||
| { type: "text"; text: string }
|
||||
| {
|
||||
type: "tool-call"
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
input: any
|
||||
}
|
||||
> = []
|
||||
|
||||
if (message.content) {
|
||||
content.push({
|
||||
type: "text",
|
||||
text: message.content as string,
|
||||
})
|
||||
}
|
||||
|
||||
if (message.tool_calls) {
|
||||
for (const toolCall of message.tool_calls) {
|
||||
content.push({
|
||||
type: "tool-call",
|
||||
toolCallId: toolCall.id,
|
||||
toolName: toolCall.function.name,
|
||||
input: JSON.parse(toolCall.function.arguments),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
prompt.push({
|
||||
role: "assistant",
|
||||
content,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "tool": {
|
||||
prompt.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolName: "placeholder",
|
||||
toolCallId: message.tool_call_id,
|
||||
output: {
|
||||
type: "text",
|
||||
value: message.content as string,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unsupported message role: ${message.role}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
return c.json({ error: { message: error.message } }, 500)
|
||||
}
|
||||
})
|
||||
.all("*", (c) => c.text("Not Found"))
|
||||
@@ -6,6 +6,10 @@
|
||||
import "sst"
|
||||
declare module "sst" {
|
||||
export interface Resource {
|
||||
"ANTHROPIC_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"GITHUB_APP_ID": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
@@ -14,10 +18,22 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"OPENAI_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"OPENCODE_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
}
|
||||
"ZHIPU_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
}
|
||||
}
|
||||
// cloudflare
|
||||
@@ -26,6 +42,7 @@ declare module "sst" {
|
||||
export interface Resource {
|
||||
"Api": cloudflare.Service
|
||||
"Bucket": cloudflare.R2Bucket
|
||||
"GatewayApi": cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 981 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="black"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="black"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="black"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="black"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="black"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="black"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 981 B |
@@ -0,0 +1,18 @@
|
||||
<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/>
|
||||
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/>
|
||||
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/>
|
||||
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg width="288" height="50" viewBox="0 0 288 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 16.5H24V33H8V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M48 16.5H64V33H48V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M120 16.5H136V33H120V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M160 16.5H176V33H160V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M192 16.5H208V33H192V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M232 16.5H248V33H232V16.5Z" fill="black" fill-opacity="0.15"/>
|
||||
<path d="M264 0H288V8.5H272V16.5H288V25H272V33H288V41.5H264V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M248 0H224V41.5H248V33H232V8.5H248V0Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M256 8.5H248V33H256V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184 0H216V41.5H184V0ZM208 8.5H192V33H208V8.5Z" fill="black" fill-opacity="0.95"/>
|
||||
<path d="M144 8.5H136V41.5H144V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M136 0H112V41.5H120V8.5H136V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M80 0H104V8.5H88V16.5H104V25H88V33H104V41.5H80V0Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 0H72V41.5H48V49.5H40V0ZM64 8.5H48V33H64V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black" fill-opacity="0.55"/>
|
||||
<path d="M152 0H176V8.5H160V33H176V41.5H152V0Z" fill="black" fill-opacity="0.95"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="white"/>
|
||||
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 13H35V58H0V13ZM26.25 22.1957H8.75V48.701H26.25V22.1957Z" fill="black"/>
|
||||
<path d="M43.75 13H70V22.1957H52.5V48.701H70V57.8967H43.75V13Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="white"/>
|
||||
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="64" height="42" viewBox="0 0 64 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V41.5955H0V0ZM24 8.5H8V33H24V8.5Z" fill="black"/>
|
||||
<path d="M40 0H64V8.5H48V33H64V41.5H40V0Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
@@ -17,19 +17,6 @@
|
||||
- **Error handling**: Use Result patterns, avoid throwing exceptions in tools
|
||||
- **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`)
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
- Try to keep things in one function unless composable or reusable
|
||||
- DO NOT do unnecessary destructuring of variables
|
||||
- DO NOT use `else` statements unless necessary
|
||||
- DO NOT use `try`/`catch` if it can be avoided
|
||||
- AVOID `try`/`catch` where possible
|
||||
- AVOID `else` statements
|
||||
- AVOID using `any` type
|
||||
- AVOID `let` statements
|
||||
- PREFER single word variable names where possible
|
||||
- Use as many bun apis as possible like Bun.file()
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Tools**: Implement `Tool.Info` interface with `execute()` method
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.133",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run ./src/index.ts"
|
||||
"dev": "bun run --conditions=development ./src/index.ts"
|
||||
},
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode"
|
||||
@@ -30,12 +30,21 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@clack/prompts": "0.11.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@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",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opentelemetry/auto-instrumentations-node": "0.62.0",
|
||||
"@opentelemetry/exporter-jaeger": "2.0.1",
|
||||
"@opentelemetry/exporter-otlp-http": "0.26.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",
|
||||
"@opentelemetry/instrumentation-fetch": "0.203.0",
|
||||
"@opentelemetry/sdk-node": "0.203.0",
|
||||
"@opentelemetry/sdk-trace-node": "2.0.1",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
@@ -46,11 +55,12 @@
|
||||
"hono-openapi": "0.4.8",
|
||||
"isomorphic-git": "1.32.1",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"minimatch": "10.0.3",
|
||||
"open": "10.1.2",
|
||||
"remeda": "catalog:",
|
||||
"turndown": "7.2.0",
|
||||
"tree-sitter": "0.22.4",
|
||||
"tree-sitter-bash": "0.23.3",
|
||||
"turndown": "7.2.0",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
|
||||
@@ -38,7 +38,7 @@ for (const [os, arch] of targets) {
|
||||
await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`.cwd(
|
||||
"../tui",
|
||||
)
|
||||
await $`bun build --define OPENCODE_VERSION="'${version}'" --compile --minify --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts ./dist/${name}/bin/tui`
|
||||
await $`bun build --define OPENCODE_TUI_PATH="'../../../dist/${name}/bin/tui'" --define OPENCODE_VERSION="'${version}'" --compile --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts`
|
||||
await $`rm -rf ./dist/${name}/bin/tui`
|
||||
await Bun.file(`dist/${name}/package.json`).write(
|
||||
JSON.stringify(
|
||||
@@ -104,6 +104,7 @@ if (!snapshot) {
|
||||
.filter((x: string) => {
|
||||
const lower = x.toLowerCase()
|
||||
return (
|
||||
!lower.includes("release:") &&
|
||||
!lower.includes("ignore:") &&
|
||||
!lower.includes("chore:") &&
|
||||
!lower.includes("ci:") &&
|
||||
|
||||
@@ -10,13 +10,16 @@ export namespace Agent {
|
||||
export const Info = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]),
|
||||
topP: z.number().optional(),
|
||||
temperature: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
description: z.string(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.boolean()),
|
||||
})
|
||||
@@ -24,6 +27,7 @@ export namespace Agent {
|
||||
ref: "Agent",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
const state = App.state("agent", async () => {
|
||||
const cfg = await Config.get()
|
||||
const result: Record<string, Info> = {
|
||||
@@ -35,22 +39,36 @@ export namespace Agent {
|
||||
todoread: false,
|
||||
todowrite: false,
|
||||
},
|
||||
mode: "subagent",
|
||||
},
|
||||
build: {
|
||||
name: "build",
|
||||
tools: {},
|
||||
mode: "primary",
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
tools: {
|
||||
write: false,
|
||||
edit: false,
|
||||
patch: false,
|
||||
},
|
||||
mode: "primary",
|
||||
},
|
||||
}
|
||||
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
||||
if (value.disable) continue
|
||||
if (value.disable) {
|
||||
delete result[key]
|
||||
continue
|
||||
}
|
||||
let item = result[key]
|
||||
if (!item)
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
description: "",
|
||||
tools: {
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
},
|
||||
mode: "all",
|
||||
tools: {},
|
||||
}
|
||||
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.tools)
|
||||
item.tools = {
|
||||
@@ -58,6 +76,9 @@ export namespace Agent {
|
||||
...value.tools,
|
||||
}
|
||||
if (value.description) item.description = value.description
|
||||
if (value.temperature != undefined) item.temperature = value.temperature
|
||||
if (value.top_p != undefined) item.topP = value.top_p
|
||||
if (value.mode) item.mode = value.mode
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { App } from "../app/app"
|
||||
import { ConfigHooks } from "../config/hooks"
|
||||
import { Format } from "../format"
|
||||
import { LSP } from "../lsp"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Share } from "../share/share"
|
||||
import { Snapshot } from "../snapshot"
|
||||
|
||||
@@ -9,6 +10,7 @@ export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Prom
|
||||
return App.provide(input, async (app) => {
|
||||
Share.init()
|
||||
Format.init()
|
||||
Plugin.init()
|
||||
ConfigHooks.init()
|
||||
LSP.init()
|
||||
Snapshot.init()
|
||||
|
||||
@@ -39,7 +39,7 @@ const AgentCreateCommand = cmd({
|
||||
const query = await prompts.text({
|
||||
message: "Description",
|
||||
placeholder: "What should this agent do?",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(query)) throw new UI.CancelledError()
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export const AuthLoginCommand = cmd({
|
||||
type: "string",
|
||||
}),
|
||||
async handler(args) {
|
||||
UI.empty()
|
||||
prompts.intro("Add credential")
|
||||
if (args.url) {
|
||||
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
|
||||
@@ -99,7 +100,7 @@ export const AuthLoginCommand = cmd({
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
UI.empty()
|
||||
await ModelsDev.refresh().catch(() => {})
|
||||
const providers = await ModelsDev.get()
|
||||
const priority: Record<string, number> = {
|
||||
anthropic: 0,
|
||||
@@ -109,7 +110,7 @@ export const AuthLoginCommand = cmd({
|
||||
openrouter: 4,
|
||||
vercel: 5,
|
||||
}
|
||||
let provider = await prompts.select({
|
||||
let provider = await prompts.autocomplete({
|
||||
message: "Select provider",
|
||||
maxItems: 8,
|
||||
options: [
|
||||
@@ -138,7 +139,7 @@ export const AuthLoginCommand = cmd({
|
||||
if (provider === "other") {
|
||||
provider = await prompts.text({
|
||||
message: "Enter provider id",
|
||||
validate: (x) => (x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
|
||||
validate: (x) => x && (x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
|
||||
})
|
||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||
provider = provider.replace(/^@ai-sdk\//, "")
|
||||
@@ -192,7 +193,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
|
||||
@@ -228,7 +229,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const code = await prompts.text({
|
||||
message: "Paste the authorization code here: ",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(code)) throw new UI.CancelledError()
|
||||
|
||||
@@ -301,7 +302,7 @@ export const AuthLoginCommand = cmd({
|
||||
|
||||
const key = await prompts.password({
|
||||
message: "Enter your API key",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(key)) throw new UI.CancelledError()
|
||||
await Auth.set(provider, {
|
||||
|
||||
@@ -318,8 +318,10 @@ on:
|
||||
jobs:
|
||||
opencode:
|
||||
if: |
|
||||
contains(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, '/opencode')
|
||||
contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
startsWith(github.event.comment.body, '/opencode')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -639,7 +641,7 @@ export const GithubRunCommand = cmd({
|
||||
messageID: Identifier.ascending("message"),
|
||||
providerID,
|
||||
modelID,
|
||||
mode: "build",
|
||||
agent: "build",
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -784,7 +786,7 @@ export const GithubRunCommand = cmd({
|
||||
console.log("Pushing to new branch...")
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push -u origin ${branch}`
|
||||
}
|
||||
@@ -793,7 +795,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
console.log("Pushing to local branch...")
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push`
|
||||
}
|
||||
@@ -805,7 +807,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
|
||||
await $`git add .`
|
||||
await $`git commit -m "${summary}
|
||||
|
||||
|
||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
await $`git push fork HEAD:${remoteBranch}`
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const McpAddCommand = cmd({
|
||||
|
||||
const name = await prompts.text({
|
||||
message: "Enter MCP server name",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(name)) throw new UI.CancelledError()
|
||||
|
||||
@@ -44,7 +44,7 @@ export const McpAddCommand = cmd({
|
||||
const command = await prompts.text({
|
||||
message: "Enter command to run",
|
||||
placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
|
||||
validate: (x) => (x.length > 0 ? undefined : "Required"),
|
||||
validate: (x) => x && (x.length > 0 ? undefined : "Required"),
|
||||
})
|
||||
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||
|
||||
@@ -58,6 +58,7 @@ export const McpAddCommand = cmd({
|
||||
message: "Enter MCP server URL",
|
||||
placeholder: "e.g., https://example.com/mcp",
|
||||
validate: (x) => {
|
||||
if (!x) return "Required"
|
||||
if (x.length === 0) return "Required"
|
||||
const isValid = URL.canParse(x)
|
||||
return isValid ? undefined : "Invalid URL"
|
||||
|
||||
@@ -8,8 +8,8 @@ import { Flag } from "../../flag/flag"
|
||||
import { Config } from "../../config/config"
|
||||
import { bootstrap } from "../bootstrap"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { Mode } from "../../session/mode"
|
||||
import { Identifier } from "../../id/id"
|
||||
import { Agent } from "../../agent/agent"
|
||||
|
||||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
@@ -54,9 +54,9 @@ export const RunCommand = cmd({
|
||||
alias: ["m"],
|
||||
describe: "model to use in the format of provider/model",
|
||||
})
|
||||
.option("mode", {
|
||||
.option("agent", {
|
||||
type: "string",
|
||||
describe: "mode to use",
|
||||
describe: "agent to use",
|
||||
})
|
||||
},
|
||||
handler: async (args) => {
|
||||
@@ -103,7 +103,19 @@ export const RunCommand = cmd({
|
||||
}
|
||||
UI.empty()
|
||||
|
||||
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : await Provider.defaultModel()
|
||||
const agent = await (async () => {
|
||||
if (args.agent) return Agent.get(args.agent)
|
||||
const build = Agent.get("build")
|
||||
if (build) return build
|
||||
return Agent.list().then((x) => x[0])
|
||||
})()
|
||||
|
||||
const { providerID, modelID } = await (() => {
|
||||
if (args.model) return Provider.parseModel(args.model)
|
||||
if (agent.model) return agent.model
|
||||
return Provider.defaultModel()
|
||||
})()
|
||||
|
||||
UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
|
||||
UI.empty()
|
||||
|
||||
@@ -156,19 +168,17 @@ export const RunCommand = cmd({
|
||||
UI.error(err)
|
||||
})
|
||||
|
||||
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
|
||||
|
||||
const messageID = Identifier.ascending("message")
|
||||
const result = await Session.chat({
|
||||
sessionID: session.id,
|
||||
messageID,
|
||||
...(mode.model
|
||||
? mode.model
|
||||
...(agent.model
|
||||
? agent.model
|
||||
: {
|
||||
providerID,
|
||||
modelID,
|
||||
}),
|
||||
mode: mode.name,
|
||||
agent: agent.name,
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -11,8 +11,18 @@ import { Config } from "../../config/config"
|
||||
import { Bus } from "../../bus"
|
||||
import { Log } from "../../util/log"
|
||||
import { FileWatcher } from "../../file/watch"
|
||||
import { Mode } from "../../session/mode"
|
||||
import { Ide } from "../../ide"
|
||||
import { Agent } from "../../agent/agent"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_TUI_PATH: string
|
||||
}
|
||||
|
||||
if (typeof OPENCODE_TUI_PATH !== "undefined") {
|
||||
await import(OPENCODE_TUI_PATH as string, {
|
||||
with: { type: "file" },
|
||||
})
|
||||
}
|
||||
|
||||
export const TuiCommand = cmd({
|
||||
command: "$0 [project]",
|
||||
@@ -71,16 +81,16 @@ export const TuiCommand = cmd({
|
||||
|
||||
let cmd = ["go", "run", "./main.go"]
|
||||
let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
||||
if (Bun.embeddedFiles.length > 0) {
|
||||
const blob = Bun.embeddedFiles[0] as File
|
||||
let binaryName = blob.name
|
||||
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
|
||||
if (tui) {
|
||||
let binaryName = tui.name
|
||||
if (process.platform === "win32" && !binaryName.endsWith(".exe")) {
|
||||
binaryName += ".exe"
|
||||
}
|
||||
const binary = path.join(Global.Path.cache, "tui", binaryName)
|
||||
const file = Bun.file(binary)
|
||||
if (!(await file.exists())) {
|
||||
await Bun.write(file, blob, { mode: 0o755 })
|
||||
await Bun.write(file, tui, { mode: 0o755 })
|
||||
await fs.chmod(binary, 0o755)
|
||||
}
|
||||
cwd = process.cwd()
|
||||
@@ -105,7 +115,7 @@ export const TuiCommand = cmd({
|
||||
CGO_ENABLED: "0",
|
||||
OPENCODE_SERVER: server.url.toString(),
|
||||
OPENCODE_APP_INFO: JSON.stringify(app),
|
||||
OPENCODE_MODES: JSON.stringify(await Mode.list()),
|
||||
OPENCODE_AGENTS: JSON.stringify(await Agent.list()),
|
||||
},
|
||||
onExit: () => {
|
||||
server.stop()
|
||||
|
||||
@@ -32,7 +32,7 @@ export const UpgradeCommand = {
|
||||
return
|
||||
}
|
||||
prompts.log.info("Using method: " + method)
|
||||
const target = args.target ?? (await Installation.latest())
|
||||
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
||||
|
||||
if (Installation.VERSION === target) {
|
||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { z } from "zod"
|
||||
import { App } from "../app/app"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
@@ -23,13 +24,13 @@ export namespace Config {
|
||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
|
||||
for (const resolved of found.toReversed()) {
|
||||
result = mergeDeep(result, await load(resolved))
|
||||
result = mergeDeep(result, await loadFile(resolved))
|
||||
}
|
||||
}
|
||||
|
||||
// Override with custom config if provided
|
||||
if (Flag.OPENCODE_CONFIG) {
|
||||
result = mergeDeep(result, await load(Flag.OPENCODE_CONFIG))
|
||||
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
|
||||
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
|
||||
}
|
||||
|
||||
@@ -37,7 +38,7 @@ export namespace Config {
|
||||
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 = mergeDeep(result, await load(JSON.stringify(wellknown.config ?? {}), process.cwd()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ export namespace Config {
|
||||
...md.data,
|
||||
prompt: md.content.trim(),
|
||||
}
|
||||
const parsed = Mode.safeParse(config)
|
||||
const parsed = Agent.safeParse(config)
|
||||
if (parsed.success) {
|
||||
result.mode = mergeDeep(result.mode, {
|
||||
[config.name]: parsed.data,
|
||||
@@ -91,6 +92,23 @@ export namespace Config {
|
||||
}
|
||||
throw new InvalidError({ path: item }, { cause: parsed.error })
|
||||
}
|
||||
// Migrate deprecated mode field to agent field
|
||||
for (const [name, mode] of Object.entries(result.mode)) {
|
||||
result.agent = mergeDeep(result.agent ?? {}, {
|
||||
[name]: {
|
||||
...mode,
|
||||
mode: "primary" as const,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
result.plugin = result.plugin || []
|
||||
result.plugin.push(
|
||||
...[
|
||||
...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)),
|
||||
...(await Filesystem.globUp(".opencode/plugin/*.ts", app.path.cwd, app.path.root)),
|
||||
].map((x) => "file://" + x),
|
||||
)
|
||||
|
||||
// Handle migration from autoshare to share field
|
||||
if (result.autoshare === true && !result.share) {
|
||||
@@ -99,6 +117,12 @@ export namespace Config {
|
||||
if (result.keybinds?.messages_revert && !result.keybinds.messages_undo) {
|
||||
result.keybinds.messages_undo = result.keybinds.messages_revert
|
||||
}
|
||||
if (result.keybinds?.switch_mode && !result.keybinds.switch_agent) {
|
||||
result.keybinds.switch_agent = result.keybinds.switch_mode
|
||||
}
|
||||
if (result.keybinds?.switch_mode_reverse && !result.keybinds.switch_agent_reverse) {
|
||||
result.keybinds.switch_agent_reverse = result.keybinds.switch_mode_reverse
|
||||
}
|
||||
|
||||
if (!result.username) {
|
||||
const os = await import("os")
|
||||
@@ -140,31 +164,34 @@ export namespace Config {
|
||||
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
|
||||
export type Mcp = z.infer<typeof Mcp>
|
||||
|
||||
export const Mode = z
|
||||
export const Agent = z
|
||||
.object({
|
||||
model: z.string().optional(),
|
||||
temperature: z.number().optional(),
|
||||
top_p: z.number().optional(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
disable: z.boolean().optional(),
|
||||
description: z.string().optional().describe("Description of when to use the agent"),
|
||||
mode: z.union([z.literal("subagent"), z.literal("primary"), z.literal("all")]).optional(),
|
||||
})
|
||||
.openapi({
|
||||
ref: "ModeConfig",
|
||||
ref: "AgentConfig",
|
||||
})
|
||||
export type Mode = z.infer<typeof Mode>
|
||||
|
||||
export const Agent = Mode.extend({
|
||||
description: z.string(),
|
||||
}).openapi({
|
||||
ref: "AgentConfig",
|
||||
})
|
||||
export type Agent = z.infer<typeof Agent>
|
||||
|
||||
export const Keybinds = z
|
||||
.object({
|
||||
leader: z.string().optional().default("ctrl+x").describe("Leader key for keybind combinations"),
|
||||
app_help: z.string().optional().default("<leader>h").describe("Show help dialog"),
|
||||
switch_mode: z.string().optional().default("tab").describe("Next mode"),
|
||||
switch_mode_reverse: z.string().optional().default("shift+tab").describe("Previous Mode"),
|
||||
switch_mode: z.string().optional().default("none").describe("@deprecated use switch_agent. Next mode"),
|
||||
switch_mode_reverse: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("none")
|
||||
.describe("@deprecated use switch_agent_reverse. Previous mode"),
|
||||
switch_agent: z.string().optional().default("tab").describe("Next agent"),
|
||||
switch_agent_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
||||
editor_open: z.string().optional().default("<leader>e").describe("Open external editor"),
|
||||
session_export: z.string().optional().default("<leader>x").describe("Export session to editor"),
|
||||
session_new: z.string().optional().default("<leader>n").describe("Create a new session"),
|
||||
@@ -214,7 +241,7 @@ export namespace Config {
|
||||
})
|
||||
export type Layout = z.infer<typeof Layout>
|
||||
|
||||
export const Permission = z.union([z.literal("ask"), z.literal("allow")])
|
||||
export const Permission = z.union([z.literal("ask"), z.literal("allow"), z.literal("deny")])
|
||||
export type Permission = z.infer<typeof Permission>
|
||||
|
||||
export const Info = z
|
||||
@@ -222,6 +249,8 @@ export namespace Config {
|
||||
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||
plugin: z.string().array().optional(),
|
||||
snapshot: z.boolean().optional(),
|
||||
share: z
|
||||
.enum(["manual", "auto", "disabled"])
|
||||
.optional()
|
||||
@@ -237,9 +266,7 @@ export namespace Config {
|
||||
model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
|
||||
small_model: z
|
||||
.string()
|
||||
.describe(
|
||||
"Small model to use for tasks like summarization and title generation in the format of provider/model",
|
||||
)
|
||||
.describe("Small model to use for tasks like title generation in the format of provider/model")
|
||||
.optional(),
|
||||
username: z
|
||||
.string()
|
||||
@@ -247,19 +274,21 @@ export namespace Config {
|
||||
.describe("Custom username to display in conversations instead of system username"),
|
||||
mode: z
|
||||
.object({
|
||||
build: Mode.optional(),
|
||||
plan: Mode.optional(),
|
||||
build: Agent.optional(),
|
||||
plan: Agent.optional(),
|
||||
})
|
||||
.catchall(Mode)
|
||||
.catchall(Agent)
|
||||
.optional()
|
||||
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
|
||||
.describe("@deprecated Use `agent` field instead."),
|
||||
agent: z
|
||||
.object({
|
||||
plan: Agent.optional(),
|
||||
build: Agent.optional(),
|
||||
general: Agent.optional(),
|
||||
})
|
||||
.catchall(Agent)
|
||||
.optional()
|
||||
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
|
||||
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
|
||||
provider: z
|
||||
.record(
|
||||
ModelsDev.Provider.partial()
|
||||
@@ -278,6 +307,34 @@ export namespace Config {
|
||||
.optional()
|
||||
.describe("Custom provider configurations and model overrides"),
|
||||
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||
formatter: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
disabled: z.boolean().optional(),
|
||||
command: z.array(z.string()).optional(),
|
||||
environment: z.record(z.string(), z.string()).optional(),
|
||||
extensions: z.array(z.string()).optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
lsp: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.union([
|
||||
z.object({
|
||||
disabled: z.literal(true),
|
||||
}),
|
||||
z.object({
|
||||
command: z.array(z.string()),
|
||||
extensions: z.array(z.string()).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
env: z.record(z.string(), z.string()).optional(),
|
||||
initialization: z.record(z.string(), z.any()).optional(),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.optional(),
|
||||
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
|
||||
@@ -321,10 +378,11 @@ export namespace Config {
|
||||
export type Info = z.output<typeof Info>
|
||||
|
||||
export const global = lazy(async () => {
|
||||
let result = pipe(
|
||||
let result: Info = pipe(
|
||||
{},
|
||||
mergeDeep(await load(path.join(Global.Path.config, "config.json"))),
|
||||
mergeDeep(await load(path.join(Global.Path.config, "opencode.json"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "config.json"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.json"))),
|
||||
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||
)
|
||||
|
||||
await import(path.join(Global.Path.config, "config"), {
|
||||
@@ -345,46 +403,65 @@ export namespace Config {
|
||||
return result
|
||||
})
|
||||
|
||||
async function load(configPath: string) {
|
||||
let text = await Bun.file(configPath)
|
||||
async function loadFile(filepath: string): Promise<Info> {
|
||||
log.info("loading", { path: filepath })
|
||||
let text = await Bun.file(filepath)
|
||||
.text()
|
||||
.catch((err) => {
|
||||
if (err.code === "ENOENT") return
|
||||
throw new JsonError({ path: configPath }, { cause: err })
|
||||
throw new JsonError({ path: filepath }, { cause: err })
|
||||
})
|
||||
if (!text) return {}
|
||||
return loadRaw(text, configPath)
|
||||
return load(text, filepath)
|
||||
}
|
||||
|
||||
async function loadRaw(text: string, configPath: string) {
|
||||
async function load(text: string, filepath: string) {
|
||||
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||
return process.env[varName] || ""
|
||||
})
|
||||
|
||||
const fileMatches = text.match(/"?\{file:([^}]+)\}"?/g)
|
||||
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
||||
if (fileMatches) {
|
||||
const configDir = path.dirname(configPath)
|
||||
const configDir = path.dirname(filepath)
|
||||
const lines = text.split("\n")
|
||||
|
||||
for (const match of fileMatches) {
|
||||
const filePath = match.replace(/^"?\{file:/, "").replace(/\}"?$/, "")
|
||||
const lineIndex = lines.findIndex((line) => line.includes(match))
|
||||
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) {
|
||||
continue // Skip if line is commented
|
||||
}
|
||||
let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
|
||||
if (filePath.startsWith("~/")) {
|
||||
filePath = path.join(os.homedir(), filePath.slice(2))
|
||||
}
|
||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
|
||||
const fileContent = await Bun.file(resolvedPath).text()
|
||||
text = text.replace(match, JSON.stringify(fileContent))
|
||||
const fileContent = (await Bun.file(resolvedPath).text()).trim()
|
||||
// escape newlines/quotes, strip outer quotes
|
||||
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
||||
}
|
||||
}
|
||||
|
||||
const errors: JsoncParseError[] = []
|
||||
const data = parseJsonc(text, errors, { allowTrailingComma: true })
|
||||
if (errors.length) {
|
||||
const lines = text.split("\n")
|
||||
const errorDetails = errors
|
||||
.map((e) => {
|
||||
const beforeOffset = text.substring(0, e.offset).split("\n")
|
||||
const line = beforeOffset.length
|
||||
const column = beforeOffset[beforeOffset.length - 1].length + 1
|
||||
const problemLine = lines[line - 1]
|
||||
|
||||
const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
|
||||
if (!problemLine) return error
|
||||
|
||||
return `${error}\n Line ${line}: ${problemLine}\n${"".padStart(column + 9)}^`
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
throw new JsonError({
|
||||
path: configPath,
|
||||
message: errors
|
||||
.map((e) => {
|
||||
const lines = text.substring(0, e.offset).split("\n")
|
||||
const line = lines.length
|
||||
const column = lines[lines.length - 1].length + 1
|
||||
return `${printParseErrorCode(e.error)} at line ${line}, column ${column}`
|
||||
})
|
||||
.join("; "),
|
||||
path: filepath,
|
||||
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,11 +469,21 @@ export namespace Config {
|
||||
if (parsed.success) {
|
||||
if (!parsed.data.$schema) {
|
||||
parsed.data.$schema = "https://opencode.ai/config.json"
|
||||
await Bun.write(configPath, JSON.stringify(parsed.data, null, 2))
|
||||
await Bun.write(filepath, JSON.stringify(parsed.data, null, 2))
|
||||
}
|
||||
return parsed.data
|
||||
const data = parsed.data
|
||||
if (data.plugin) {
|
||||
for (let i = 0; i < data.plugin?.length; i++) {
|
||||
const plugin = data.plugin[i]
|
||||
try {
|
||||
data.plugin[i] = import.meta.resolve(plugin, filepath)
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
throw new InvalidError({ path: configPath, issues: parsed.error.issues })
|
||||
|
||||
throw new InvalidError({ path: filepath, issues: parsed.error.issues })
|
||||
}
|
||||
export const JsonError = NamedError.create(
|
||||
"ConfigJsonError",
|
||||
|
||||
@@ -129,7 +129,9 @@ export const clang: Info = {
|
||||
command: ["clang-format", "-i", "$FILE"],
|
||||
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||
async enabled() {
|
||||
return Bun.which("clang-format") !== null
|
||||
const app = App.info()
|
||||
const items = await Filesystem.findUp(".clang-format", app.path.cwd, app.path.root)
|
||||
return items.length > 0
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,40 @@ import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
|
||||
import * as Formatter from "./formatter"
|
||||
import { Config } from "../config/config"
|
||||
import { mergeDeep } from "remeda"
|
||||
|
||||
export namespace Format {
|
||||
const log = Log.create({ service: "format" })
|
||||
|
||||
const state = App.state("format", () => {
|
||||
const state = App.state("format", async () => {
|
||||
const enabled: Record<string, boolean> = {}
|
||||
const cfg = await Config.get()
|
||||
|
||||
const formatters = { ...Formatter } as Record<string, Formatter.Info>
|
||||
for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
|
||||
if (item.disabled) {
|
||||
delete formatters[name]
|
||||
continue
|
||||
}
|
||||
const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
|
||||
command: [],
|
||||
extensions: [],
|
||||
...item,
|
||||
})
|
||||
result.enabled = async () => true
|
||||
result.name = name
|
||||
formatters[name] = result
|
||||
}
|
||||
|
||||
return {
|
||||
enabled,
|
||||
formatters,
|
||||
}
|
||||
})
|
||||
|
||||
async function isEnabled(item: Formatter.Info) {
|
||||
const s = state()
|
||||
const s = await state()
|
||||
let status = s.enabled[item.name]
|
||||
if (status === undefined) {
|
||||
status = await item.enabled()
|
||||
@@ -28,8 +48,10 @@ export namespace Format {
|
||||
}
|
||||
|
||||
async function getFormatter(ext: string) {
|
||||
const formatters = await state().then((x) => x.formatters)
|
||||
const result = []
|
||||
for (const item of Object.values(Formatter)) {
|
||||
for (const item of Object.values(formatters)) {
|
||||
log.info("checking", { name: item.name, ext })
|
||||
if (!item.extensions.includes(ext)) continue
|
||||
if (!(await isEnabled(item))) continue
|
||||
result.push(item)
|
||||
|
||||
@@ -27,7 +27,7 @@ await Promise.all([
|
||||
fs.mkdir(Global.Path.log, { recursive: true }),
|
||||
])
|
||||
|
||||
const CACHE_VERSION = "3"
|
||||
const CACHE_VERSION = "4"
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
|
||||
@@ -5,6 +5,7 @@ export namespace Identifier {
|
||||
const prefixes = {
|
||||
session: "ses",
|
||||
message: "msg",
|
||||
permission: "per",
|
||||
user: "usr",
|
||||
part: "prt",
|
||||
} as const
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import "zod-openapi/extend"
|
||||
import { Trace } from "./trace"
|
||||
Trace.init()
|
||||
import yargs from "yargs"
|
||||
import { hideBin } from "yargs/helpers"
|
||||
import { RunCommand } from "./cli/cmd/run"
|
||||
@@ -18,9 +20,6 @@ import { DebugCommand } from "./cli/cmd/debug"
|
||||
import { StatsCommand } from "./cli/cmd/stats"
|
||||
import { McpCommand } from "./cli/cmd/mcp"
|
||||
import { GithubCommand } from "./cli/cmd/github"
|
||||
import { Trace } from "./trace"
|
||||
|
||||
Trace.init()
|
||||
|
||||
const cancel = new AbortController()
|
||||
|
||||
@@ -83,6 +82,7 @@ const cli = yargs(hideBin(process.argv))
|
||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||
cli.showHelp("log")
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
.strict()
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ export namespace Installation {
|
||||
}
|
||||
|
||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||
export const USER_AGENT = `opencode/${VERSION}`
|
||||
|
||||
export async function latest() {
|
||||
return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
||||
|
||||
@@ -4,6 +4,8 @@ import { LSPClient } from "./client"
|
||||
import path from "path"
|
||||
import { LSPServer } from "./server"
|
||||
import { z } from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { spawn } from "child_process"
|
||||
|
||||
export namespace LSP {
|
||||
const log = Log.create({ service: "lsp" })
|
||||
@@ -55,8 +57,35 @@ export namespace LSP {
|
||||
"lsp",
|
||||
async () => {
|
||||
const clients: LSPClient.Info[] = []
|
||||
const servers: Record<string, LSPServer.Info> = LSPServer
|
||||
const cfg = await Config.get()
|
||||
for (const [name, item] of Object.entries(cfg.lsp ?? {})) {
|
||||
const existing = servers[name]
|
||||
if (item.disabled) {
|
||||
delete servers[name]
|
||||
continue
|
||||
}
|
||||
servers[name] = {
|
||||
...existing,
|
||||
root: existing?.root ?? (async (_file, app) => app.path.root),
|
||||
extensions: item.extensions ?? existing.extensions,
|
||||
spawn: async (_app, root) => {
|
||||
return {
|
||||
process: spawn(item.command[0], item.command.slice(1), {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
...item.env,
|
||||
},
|
||||
}),
|
||||
initialization: item.initialization,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
broken: new Set<string>(),
|
||||
servers,
|
||||
clients,
|
||||
}
|
||||
},
|
||||
@@ -76,7 +105,7 @@ export namespace LSP {
|
||||
const extension = path.parse(file).ext
|
||||
const result: LSPClient.Info[] = []
|
||||
for (const server of Object.values(LSPServer)) {
|
||||
if (!server.extensions.includes(extension)) continue
|
||||
if (server.extensions.length && !server.extensions.includes(extension)) continue
|
||||
const root = await server.root(file, App.info())
|
||||
if (!root) continue
|
||||
if (s.broken.has(root + server.id)) continue
|
||||
|
||||
@@ -115,7 +115,8 @@ export namespace MCP {
|
||||
const result: Record<string, Tool> = {}
|
||||
for (const [clientName, client] of Object.entries(await clients())) {
|
||||
for (const [toolName, tool] of Object.entries(await client.tools())) {
|
||||
result[clientName + "_" + toolName] = tool
|
||||
const sanitizedClientName = clientName.replace(/\s+/g, "_")
|
||||
result[sanitizedClientName + "_" + toolName] = tool
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -2,6 +2,8 @@ import { App } from "../app/app"
|
||||
import { z } from "zod"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Plugin } from "../plugin"
|
||||
|
||||
export namespace Permission {
|
||||
const log = Log.create({ service: "permission" })
|
||||
@@ -9,7 +11,11 @@ export namespace Permission {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
pattern: z.string().optional(),
|
||||
sessionID: z.string(),
|
||||
messageID: z.string(),
|
||||
callID: z.string().optional(),
|
||||
title: z.string(),
|
||||
metadata: z.record(z.any()),
|
||||
time: z.object({
|
||||
@@ -17,12 +23,16 @@ export namespace Permission {
|
||||
}),
|
||||
})
|
||||
.openapi({
|
||||
ref: "permission.info",
|
||||
ref: "Permission",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
export const Event = {
|
||||
Updated: Bus.event("permission.updated", Info),
|
||||
Replied: Bus.event(
|
||||
"permission.replied",
|
||||
z.object({ sessionID: z.string(), permissionID: z.string(), response: z.string() }),
|
||||
),
|
||||
}
|
||||
|
||||
const state = App.state(
|
||||
@@ -40,7 +50,7 @@ export namespace Permission {
|
||||
|
||||
const approved: {
|
||||
[sessionID: string]: {
|
||||
[permissionID: string]: Info
|
||||
[permissionID: string]: boolean
|
||||
}
|
||||
} = {}
|
||||
|
||||
@@ -52,76 +62,90 @@ export namespace Permission {
|
||||
async (state) => {
|
||||
for (const pending of Object.values(state.pending)) {
|
||||
for (const item of Object.values(pending)) {
|
||||
item.reject(new RejectedError(item.info.sessionID, item.info.id))
|
||||
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export function ask(input: {
|
||||
id: Info["id"]
|
||||
sessionID: Info["sessionID"]
|
||||
export async function ask(input: {
|
||||
type: Info["type"]
|
||||
title: Info["title"]
|
||||
pattern?: Info["pattern"]
|
||||
callID?: Info["callID"]
|
||||
sessionID: Info["sessionID"]
|
||||
messageID: Info["messageID"]
|
||||
metadata: Info["metadata"]
|
||||
}) {
|
||||
return
|
||||
const { pending, approved } = state()
|
||||
log.info("asking", {
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.id,
|
||||
messageID: input.messageID,
|
||||
toolCallID: input.callID,
|
||||
})
|
||||
if (approved[input.sessionID]?.[input.id]) {
|
||||
log.info("previously approved", {
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.id,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (approved[input.sessionID]?.[input.pattern ?? input.type]) return
|
||||
const info: Info = {
|
||||
id: input.id,
|
||||
id: Identifier.ascending("permission"),
|
||||
type: input.type,
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
callID: input.callID,
|
||||
title: input.title,
|
||||
metadata: input.metadata,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
|
||||
switch (
|
||||
await Plugin.trigger("permission.ask", info, {
|
||||
status: "ask",
|
||||
}).then((x) => x.status)
|
||||
) {
|
||||
case "deny":
|
||||
throw new RejectedError(info.sessionID, info.id, info.callID)
|
||||
case "allow":
|
||||
return
|
||||
}
|
||||
|
||||
pending[input.sessionID] = pending[input.sessionID] || {}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
pending[input.sessionID][input.id] = {
|
||||
pending[input.sessionID][info.id] = {
|
||||
info,
|
||||
resolve,
|
||||
reject,
|
||||
}
|
||||
setTimeout(() => {
|
||||
respond({
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.id,
|
||||
response: "always",
|
||||
})
|
||||
}, 1000)
|
||||
Bus.publish(Event.Updated, info)
|
||||
})
|
||||
}
|
||||
|
||||
export function respond(input: {
|
||||
sessionID: Info["sessionID"]
|
||||
permissionID: Info["id"]
|
||||
response: "once" | "always" | "reject"
|
||||
}) {
|
||||
export const Response = z.enum(["once", "always", "reject"])
|
||||
export type Response = z.infer<typeof Response>
|
||||
|
||||
export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) {
|
||||
log.info("response", input)
|
||||
const { pending, approved } = state()
|
||||
const match = pending[input.sessionID]?.[input.permissionID]
|
||||
if (!match) return
|
||||
delete pending[input.sessionID][input.permissionID]
|
||||
if (input.response === "reject") {
|
||||
match.reject(new RejectedError(input.sessionID, input.permissionID))
|
||||
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID))
|
||||
return
|
||||
}
|
||||
match.resolve()
|
||||
Bus.publish(Event.Replied, {
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.permissionID,
|
||||
response: input.response,
|
||||
})
|
||||
if (input.response === "always") {
|
||||
approved[input.sessionID] = approved[input.sessionID] || {}
|
||||
approved[input.sessionID][input.permissionID] = match.info
|
||||
approved[input.sessionID][match.info.pattern ?? match.info.type] = true
|
||||
for (const item of Object.values(pending[input.sessionID])) {
|
||||
if ((item.info.pattern ?? item.info.type) === (match.info.pattern ?? match.info.type)) {
|
||||
respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +153,7 @@ export namespace Permission {
|
||||
constructor(
|
||||
public readonly sessionID: string,
|
||||
public readonly permissionID: string,
|
||||
public readonly toolCallID?: string,
|
||||
) {
|
||||
super(`The user rejected permission to use this functionality`)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { Hooks, Plugin as PluginInstance } from "@opencode-ai/plugin"
|
||||
import { App } from "../app/app"
|
||||
import { Config } from "../config/config"
|
||||
import { Bus } from "../bus"
|
||||
import { Log } from "../util/log"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { Server } from "../server/server"
|
||||
import { BunProc } from "../bun"
|
||||
|
||||
export namespace Plugin {
|
||||
const log = Log.create({ service: "plugin" })
|
||||
|
||||
const state = App.state("plugin", async (app) => {
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
fetch: async (...args) => Server.app().fetch(...args),
|
||||
})
|
||||
const config = await Config.get()
|
||||
const hooks = []
|
||||
for (let plugin of config.plugin ?? []) {
|
||||
log.info("loading plugin", { path: plugin })
|
||||
if (!plugin.startsWith("file://")) {
|
||||
const [pkg, version] = plugin.split("@")
|
||||
plugin = await BunProc.install(pkg, version ?? "latest")
|
||||
}
|
||||
const mod = await import(plugin)
|
||||
for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
|
||||
const init = await fn({
|
||||
client,
|
||||
app,
|
||||
$: Bun.$,
|
||||
})
|
||||
hooks.push(init)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hooks,
|
||||
}
|
||||
})
|
||||
|
||||
export async function trigger<
|
||||
Name extends keyof Required<Hooks>,
|
||||
Input = Parameters<Required<Hooks>[Name]>[0],
|
||||
Output = Parameters<Required<Hooks>[Name]>[1],
|
||||
>(name: Name, input: Input, output: Output): Promise<Output> {
|
||||
if (!name) return output
|
||||
for (const hook of await state().then((x) => x.hooks)) {
|
||||
const fn = hook[name]
|
||||
if (!fn) continue
|
||||
// @ts-expect-error if you feel adventurous, please fix the typing, make sure to bump the try-counter if you
|
||||
// give up.
|
||||
// try-counter: 2
|
||||
await fn(input, output)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
export function init() {
|
||||
Bus.subscribeAll(async (input) => {
|
||||
const hooks = await state().then((x) => x.hooks)
|
||||
for (const hook of hooks) {
|
||||
hook["event"]?.({
|
||||
event: input,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { z } from "zod"
|
||||
import { data } from "./models-macro" with { type: "macro" }
|
||||
import { Installation } from "../installation"
|
||||
|
||||
export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
@@ -50,21 +51,30 @@ export namespace ModelsDev {
|
||||
export type Provider = z.infer<typeof Provider>
|
||||
|
||||
export async function get() {
|
||||
refresh()
|
||||
const file = Bun.file(filepath)
|
||||
const result = await file.json().catch(() => {})
|
||||
if (result) {
|
||||
refresh()
|
||||
return result as Record<string, Provider>
|
||||
}
|
||||
refresh()
|
||||
if (result) return result as Record<string, Provider>
|
||||
const json = await data()
|
||||
return JSON.parse(json) as Record<string, Provider>
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
export async function refresh() {
|
||||
const file = Bun.file(filepath)
|
||||
log.info("refreshing")
|
||||
const result = await fetch("https://models.dev/api.json").catch(() => {})
|
||||
if (result && result.ok) await Bun.write(file, result)
|
||||
log.info("refreshing", {
|
||||
file,
|
||||
})
|
||||
const result = await fetch("https://models.dev/api.json", {
|
||||
headers: {
|
||||
"User-Agent": Installation.USER_AGENT,
|
||||
},
|
||||
}).catch((e) => {
|
||||
log.error("Failed to fetch models.dev", {
|
||||
error: e,
|
||||
})
|
||||
})
|
||||
if (result && result.ok) await Bun.write(file, await result.text())
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref()
|
||||
|
||||
@@ -386,6 +386,10 @@ export namespace Provider {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getProvider(providerID: string) {
|
||||
return state().then((s) => s.providers[providerID])
|
||||
}
|
||||
|
||||
export async function getModel(providerID: string, modelID: string) {
|
||||
const key = `${providerID}/${modelID}`
|
||||
const s = await state()
|
||||
|
||||
@@ -2,47 +2,73 @@ import type { ModelMessage } from "ai"
|
||||
import { unique } from "remeda"
|
||||
|
||||
export namespace ProviderTransform {
|
||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||
function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] {
|
||||
return msgs.map((msg) => {
|
||||
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
||||
msg.content = msg.content.map((part) => {
|
||||
if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
|
||||
return {
|
||||
...part,
|
||||
toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
||||
}
|
||||
}
|
||||
return part
|
||||
})
|
||||
}
|
||||
return msg
|
||||
})
|
||||
}
|
||||
|
||||
const providerOptions = {
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
function applyCaching(msgs: ModelMessage[], providerID: string): ModelMessage[] {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
...providerOptions,
|
||||
}
|
||||
msg.providerOptions = {
|
||||
...msg.providerOptions,
|
||||
...providerOptions,
|
||||
}
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||
if (modelID.includes("claude")) {
|
||||
msgs = normalizeToolCallIds(msgs)
|
||||
}
|
||||
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
||||
msgs = applyCaching(msgs, providerID)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
@@ -50,4 +76,9 @@ export namespace ProviderTransform {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
||||
return 0
|
||||
}
|
||||
|
||||
export function topP(_providerID: string, modelID: string) {
|
||||
if (modelID.toLowerCase().includes("qwen")) return 1
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import { Config } from "../config/config"
|
||||
import { File } from "../file"
|
||||
import { LSP } from "../lsp"
|
||||
import { MessageV2 } from "../session/message-v2"
|
||||
import { Mode } from "../session/mode"
|
||||
import { callTui, TuiRoute } from "./tui"
|
||||
import { Permission } from "../permission"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -47,7 +49,7 @@ export namespace Server {
|
||||
Connected: Bus.event("server.connected", z.object({})),
|
||||
}
|
||||
|
||||
function app() {
|
||||
export const app = lazy(() => {
|
||||
const app = new Hono()
|
||||
|
||||
const result = app
|
||||
@@ -217,6 +219,34 @@ export namespace Server {
|
||||
return c.json(sessions)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Get session",
|
||||
operationId: "session.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const session = await Session.get(sessionID)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session",
|
||||
describeRoute({
|
||||
@@ -457,6 +487,40 @@ export namespace Server {
|
||||
return c.json(messages)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/message/:messageID",
|
||||
describeRoute({
|
||||
description: "Get a message from a session",
|
||||
operationId: "session.message",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Info,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().openapi({ description: "Session ID" }),
|
||||
messageID: z.string().openapi({ description: "Message ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const message = await Session.getMessage(params.id, params.messageID)
|
||||
return c.json(message)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
@@ -545,6 +609,37 @@ export namespace Server {
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/permissions/:permissionID",
|
||||
describeRoute({
|
||||
description: "Respond to a permission request",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Permission processed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
permissionID: z.string(),
|
||||
}),
|
||||
),
|
||||
zValidator("json", z.object({ response: Permission.Response })),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const id = params.id
|
||||
const permissionID = params.permissionID
|
||||
Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/config/providers",
|
||||
describeRoute({
|
||||
@@ -777,23 +872,23 @@ export namespace Server {
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/mode",
|
||||
"/agent",
|
||||
describeRoute({
|
||||
description: "List all modes",
|
||||
operationId: "app.modes",
|
||||
description: "List all agents",
|
||||
operationId: "app.agents",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of modes",
|
||||
description: "List of agents",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Mode.Info.array()),
|
||||
schema: resolver(Agent.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const modes = await Mode.list()
|
||||
const modes = await Agent.list()
|
||||
return c.json(modes)
|
||||
},
|
||||
)
|
||||
@@ -839,10 +934,124 @@ export namespace Server {
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-sessions",
|
||||
describeRoute({
|
||||
description: "Open the session dialog",
|
||||
operationId: "tui.openSessions",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Session dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-themes",
|
||||
describeRoute({
|
||||
description: "Open the theme dialog",
|
||||
operationId: "tui.openThemes",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Theme dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-models",
|
||||
describeRoute({
|
||||
description: "Open the model dialog",
|
||||
operationId: "tui.openModels",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Model dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/submit-prompt",
|
||||
describeRoute({
|
||||
description: "Submit the prompt",
|
||||
operationId: "tui.submitPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt submitted successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/clear-prompt",
|
||||
describeRoute({
|
||||
description: "Clear the prompt",
|
||||
operationId: "tui.clearPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt cleared successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/execute-command",
|
||||
describeRoute({
|
||||
description: "Execute a TUI command (e.g. switch_agent)",
|
||||
operationId: "tui.executeCommand",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Command executed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"json",
|
||||
z.object({
|
||||
command: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.route("/tui/control", TuiRoute)
|
||||
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
export async function openapi() {
|
||||
const a = app()
|
||||
|
||||
@@ -36,17 +36,29 @@ import { NamedError } from "../util/error"
|
||||
import { SystemPrompt } from "./system"
|
||||
import { FileTime } from "../file/time"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { Mode } from "./mode"
|
||||
import { LSP } from "../lsp"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { mergeDeep, pipe, splitWhen } from "remeda"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
|
||||
const OUTPUT_TOKEN_MAX = 32_000
|
||||
|
||||
const parentSessionTitlePrefix = "New session - "
|
||||
const childSessionTitlePrefix = "Child session - "
|
||||
|
||||
function createDefaultTitle(isChild = false) {
|
||||
return (isChild ? childSessionTitlePrefix : parentSessionTitlePrefix) + new Date().toISOString()
|
||||
}
|
||||
|
||||
function isDefaultTitle(title: string) {
|
||||
return title.startsWith(parentSessionTitlePrefix)
|
||||
}
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: Identifier.schema("session"),
|
||||
@@ -120,6 +132,7 @@ export namespace Session {
|
||||
const sessions = new Map<string, Info>()
|
||||
const messages = new Map<string, MessageV2.Info[]>()
|
||||
const pending = new Map<string, AbortController>()
|
||||
const autoCompacting = new Map<string, boolean>()
|
||||
const queued = new Map<
|
||||
string,
|
||||
{
|
||||
@@ -135,6 +148,7 @@ export namespace Session {
|
||||
sessions,
|
||||
messages,
|
||||
pending,
|
||||
autoCompacting,
|
||||
queued,
|
||||
}
|
||||
},
|
||||
@@ -150,7 +164,7 @@ export namespace Session {
|
||||
id: Identifier.descending("session"),
|
||||
version: Installation.VERSION,
|
||||
parentID,
|
||||
title: (parentID ? "Child session - " : "New Session - ") + new Date().toISOString(),
|
||||
title: createDefaultTitle(!!parentID),
|
||||
time: {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
@@ -256,7 +270,10 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function getMessage(sessionID: string, messageID: string) {
|
||||
return Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID)
|
||||
return {
|
||||
info: await Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID),
|
||||
parts: await getParts(sessionID, messageID),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getParts(sessionID: string, messageID: string) {
|
||||
@@ -305,9 +322,9 @@ export namespace Session {
|
||||
for (const child of await children(sessionID)) {
|
||||
await remove(child.id, false)
|
||||
}
|
||||
await unshare(sessionID).catch(() => {})
|
||||
await Storage.remove(`session/info/${sessionID}`).catch(() => {})
|
||||
await Storage.removeDir(`session/message/${sessionID}/`).catch(() => {})
|
||||
await unshare(sessionID).catch(() => { })
|
||||
await Storage.remove(`session/info/${sessionID}`).catch(() => { })
|
||||
await Storage.removeDir(`session/message/${sessionID}/`).catch(() => { })
|
||||
state().sessions.delete(sessionID)
|
||||
state().messages.delete(sessionID)
|
||||
if (emitEvent) {
|
||||
@@ -340,7 +357,7 @@ export namespace Session {
|
||||
messageID: Identifier.schema("message").optional(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
mode: z.string().optional(),
|
||||
agent: z.string().optional(),
|
||||
system: z.string().optional(),
|
||||
tools: z.record(z.boolean()).optional(),
|
||||
parts: z.array(
|
||||
@@ -365,6 +382,16 @@ export namespace Session {
|
||||
.openapi({
|
||||
ref: "FilePartInput",
|
||||
}),
|
||||
MessageV2.AgentPart.omit({
|
||||
messageID: true,
|
||||
sessionID: true,
|
||||
})
|
||||
.partial({
|
||||
id: true,
|
||||
})
|
||||
.openapi({
|
||||
ref: "AgentPartInput",
|
||||
}),
|
||||
]),
|
||||
),
|
||||
})
|
||||
@@ -376,7 +403,7 @@ export namespace Session {
|
||||
const l = log.clone().tag("session", input.sessionID)
|
||||
l.info("chatting")
|
||||
|
||||
const inputMode = input.mode ?? "build"
|
||||
const inputAgent = input.agent ?? "build"
|
||||
|
||||
// Process revert cleanup first, before creating new messages
|
||||
const session = await get(input.sessionID)
|
||||
@@ -496,7 +523,7 @@ export namespace Session {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
messageID: userMsg.id,
|
||||
metadata: async () => {},
|
||||
metadata: async () => { },
|
||||
}),
|
||||
)
|
||||
return [
|
||||
@@ -549,6 +576,28 @@ export namespace Session {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (part.type === "agent") {
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
...part,
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
},
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text:
|
||||
"Use the above message and context to generate a prompt and call the task tool with subagent: " +
|
||||
part.name,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -559,7 +608,7 @@ export namespace Session {
|
||||
]
|
||||
}),
|
||||
).then((x) => x.flat())
|
||||
if (inputMode === "plan")
|
||||
if (inputAgent === "plan")
|
||||
userParts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
@@ -568,12 +617,23 @@ export namespace Session {
|
||||
text: PROMPT_PLAN,
|
||||
synthetic: true,
|
||||
})
|
||||
|
||||
await Plugin.trigger(
|
||||
"chat.message",
|
||||
{},
|
||||
{
|
||||
message: userMsg,
|
||||
parts: userParts,
|
||||
},
|
||||
)
|
||||
await updateMessage(userMsg)
|
||||
for (const part of userParts) {
|
||||
await updatePart(part)
|
||||
}
|
||||
|
||||
// mark session as updated
|
||||
// used for session list sorting (indicates when session was most recently interacted with)
|
||||
await update(input.sessionID, (_draft) => { })
|
||||
|
||||
if (isLocked(input.sessionID)) {
|
||||
return new Promise((resolve) => {
|
||||
const queue = state().queued.get(input.sessionID) ?? []
|
||||
@@ -599,6 +659,8 @@ export namespace Session {
|
||||
const tokens =
|
||||
previous.tokens.input + previous.tokens.cache.read + previous.tokens.cache.write + previous.tokens.output
|
||||
if (model.info.limit.context && tokens > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
|
||||
state().autoCompacting.set(input.sessionID, true)
|
||||
|
||||
await summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: input.providerID,
|
||||
@@ -607,13 +669,12 @@ export namespace Session {
|
||||
return chat(input)
|
||||
}
|
||||
}
|
||||
|
||||
using abort = lock(input.sessionID)
|
||||
|
||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
|
||||
|
||||
if (msgs.length === 1 && !session.parentID) {
|
||||
if (msgs.length === 1 && !session.parentID && isDefaultTitle(session.title)) {
|
||||
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
||||
generateText({
|
||||
maxOutputTokens: small.info.reasoning ? 1024 : 20,
|
||||
@@ -651,15 +712,15 @@ export namespace Session {
|
||||
draft.title = title.trim()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(() => { })
|
||||
}
|
||||
|
||||
const mode = await Mode.get(inputMode)
|
||||
const agent = await Agent.get(inputAgent)
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
if (input.system) return [input.system]
|
||||
if (mode.prompt) return [mode.prompt]
|
||||
if (agent.prompt) return [agent.prompt]
|
||||
return SystemPrompt.provider(input.modelID)
|
||||
})(),
|
||||
)
|
||||
@@ -673,7 +734,7 @@ export namespace Session {
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
system,
|
||||
mode: inputMode,
|
||||
mode: inputAgent,
|
||||
path: {
|
||||
cwd: app.path.cwd,
|
||||
root: app.path.root,
|
||||
@@ -698,8 +759,8 @@ export namespace Session {
|
||||
const processor = createProcessor(assistantMsg, model.info)
|
||||
|
||||
const enabledTools = pipe(
|
||||
mode.tools,
|
||||
mergeDeep(ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID)),
|
||||
mergeDeep(input.tools ?? {}),
|
||||
)
|
||||
for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) {
|
||||
@@ -709,11 +770,22 @@ export namespace Session {
|
||||
description: item.description,
|
||||
inputSchema: item.parameters as ZodSchema,
|
||||
async execute(args, options) {
|
||||
await processor.track(options.toolCallId)
|
||||
await Plugin.trigger(
|
||||
"tool.execute.before",
|
||||
{
|
||||
tool: item.id,
|
||||
sessionID: input.sessionID,
|
||||
callID: options.toolCallId,
|
||||
},
|
||||
{
|
||||
args,
|
||||
},
|
||||
)
|
||||
const result = await item.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: abort.signal,
|
||||
abort: options.abortSignal!,
|
||||
messageID: assistantMsg.id,
|
||||
callID: options.toolCallId,
|
||||
metadata: async (val) => {
|
||||
const match = processor.partFromToolCall(options.toolCallId)
|
||||
if (match && match.state.status === "running") {
|
||||
@@ -732,6 +804,15 @@ export namespace Session {
|
||||
}
|
||||
},
|
||||
})
|
||||
await Plugin.trigger(
|
||||
"tool.execute.after",
|
||||
{
|
||||
tool: item.id,
|
||||
sessionID: input.sessionID,
|
||||
callID: options.toolCallId,
|
||||
},
|
||||
result,
|
||||
)
|
||||
return result
|
||||
},
|
||||
toModelOutput(result) {
|
||||
@@ -744,11 +825,10 @@ export namespace Session {
|
||||
}
|
||||
|
||||
for (const [key, item] of Object.entries(await MCP.tools())) {
|
||||
if (mode.tools[key] === false) continue
|
||||
if (enabledTools[key] === false) continue
|
||||
const execute = item.execute
|
||||
if (!execute) continue
|
||||
item.execute = async (args, opts) => {
|
||||
await processor.track(opts.toolCallId)
|
||||
const result = await execute(args, opts)
|
||||
const output = result.content
|
||||
.filter((x: any) => x.type === "text")
|
||||
@@ -768,6 +848,21 @@ export namespace Session {
|
||||
tools[key] = item
|
||||
}
|
||||
|
||||
const params = {
|
||||
temperature: model.info.temperature
|
||||
? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID))
|
||||
: undefined,
|
||||
topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID),
|
||||
}
|
||||
await Plugin.trigger(
|
||||
"chat.params",
|
||||
{
|
||||
model: model.info,
|
||||
provider: await Provider.getProvider(input.providerID),
|
||||
message: userMsg,
|
||||
},
|
||||
params,
|
||||
)
|
||||
const stream = streamText({
|
||||
onError(e) {
|
||||
log.error("streamText error", {
|
||||
@@ -808,7 +903,7 @@ export namespace Session {
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: input.providerID,
|
||||
mode: inputMode,
|
||||
mode: inputAgent,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
@@ -820,13 +915,26 @@ export namespace Session {
|
||||
messages,
|
||||
}
|
||||
},
|
||||
async experimental_repairToolCall(input) {
|
||||
return {
|
||||
...input.toolCall,
|
||||
input: JSON.stringify({
|
||||
tool: input.toolCall.toolName,
|
||||
error: input.error.message,
|
||||
}),
|
||||
toolName: "invalid",
|
||||
}
|
||||
},
|
||||
maxRetries: 3,
|
||||
maxOutputTokens: outputLimit,
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
maxOutputTokens: model.info.id.startsWith("gpt-5") ? undefined : outputLimit,
|
||||
abortSignal: abort.signal,
|
||||
stopWhen: stepCountIs(1000),
|
||||
providerOptions: {
|
||||
[input.providerID]: model.info.options,
|
||||
},
|
||||
temperature: params.temperature,
|
||||
topP: params.topP,
|
||||
messages: [
|
||||
...system.map(
|
||||
(x): ModelMessage => ({
|
||||
@@ -836,9 +944,6 @@ export namespace Session {
|
||||
),
|
||||
...MessageV2.toModelMessage(msgs),
|
||||
],
|
||||
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,
|
||||
@@ -870,15 +975,11 @@ export namespace Session {
|
||||
}
|
||||
|
||||
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
||||
const toolCalls: Record<string, MessageV2.ToolPart> = {}
|
||||
const snapshots: Record<string, string> = {}
|
||||
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||
let snapshot: string | undefined
|
||||
return {
|
||||
async track(toolCallID: string) {
|
||||
const hash = await Snapshot.track()
|
||||
if (hash) snapshots[toolCallID] = hash
|
||||
},
|
||||
partFromToolCall(toolCallID: string) {
|
||||
return toolCalls[toolCallID]
|
||||
return toolcalls[toolCallID]
|
||||
},
|
||||
async process(stream: StreamTextResult<Record<string, AITool>, never>) {
|
||||
try {
|
||||
@@ -894,7 +995,7 @@ export namespace Session {
|
||||
|
||||
case "tool-input-start":
|
||||
const part = await updatePart({
|
||||
id: toolCalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||
id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "tool",
|
||||
@@ -904,7 +1005,7 @@ export namespace Session {
|
||||
status: "pending",
|
||||
},
|
||||
})
|
||||
toolCalls[value.id] = part as MessageV2.ToolPart
|
||||
toolcalls[value.id] = part as MessageV2.ToolPart
|
||||
break
|
||||
|
||||
case "tool-input-delta":
|
||||
@@ -914,10 +1015,11 @@ export namespace Session {
|
||||
break
|
||||
|
||||
case "tool-call": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match) {
|
||||
const part = await updatePart({
|
||||
...match,
|
||||
tool: value.toolName,
|
||||
state: {
|
||||
status: "running",
|
||||
input: value.input,
|
||||
@@ -926,12 +1028,12 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
toolCalls[value.toolCallId] = part as MessageV2.ToolPart
|
||||
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
|
||||
}
|
||||
break
|
||||
}
|
||||
case "tool-result": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match && match.state.status === "running") {
|
||||
await updatePart({
|
||||
...match,
|
||||
@@ -947,27 +1049,13 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
delete toolCalls[value.toolCallId]
|
||||
const snapshot = snapshots[value.toolCallId]
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
if (patch.files.length) {
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
}
|
||||
delete toolcalls[value.toolCallId]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "tool-error": {
|
||||
const match = toolCalls[value.toolCallId]
|
||||
const match = toolcalls[value.toolCallId]
|
||||
if (match && match.state.status === "running") {
|
||||
await updatePart({
|
||||
...match,
|
||||
@@ -981,19 +1069,7 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
})
|
||||
delete toolCalls[value.toolCallId]
|
||||
const snapshot = snapshots[value.toolCallId]
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
delete toolcalls[value.toolCallId]
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -1008,6 +1084,7 @@ export namespace Session {
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "step-start",
|
||||
})
|
||||
snapshot = await Snapshot.track()
|
||||
break
|
||||
|
||||
case "finish-step":
|
||||
@@ -1023,6 +1100,20 @@ export namespace Session {
|
||||
cost: usage.cost,
|
||||
})
|
||||
await updateMessage(assistantMsg)
|
||||
if (snapshot) {
|
||||
const patch = await Snapshot.patch(snapshot)
|
||||
if (patch.files.length) {
|
||||
await updatePart({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: assistantMsg.id,
|
||||
sessionID: assistantMsg.sessionID,
|
||||
type: "patch",
|
||||
hash: patch.hash,
|
||||
files: patch.files,
|
||||
})
|
||||
}
|
||||
snapshot = undefined
|
||||
}
|
||||
break
|
||||
|
||||
case "text-start":
|
||||
@@ -1107,7 +1198,7 @@ export namespace Session {
|
||||
}
|
||||
const p = await getParts(assistantMsg.sessionID, assistantMsg.id)
|
||||
for (const part of p) {
|
||||
if (part.type === "tool" && part.state.status !== "completed") {
|
||||
if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
|
||||
updatePart({
|
||||
...part,
|
||||
state: {
|
||||
@@ -1270,9 +1361,19 @@ export namespace Session {
|
||||
state().pending.set(sessionID, controller)
|
||||
return {
|
||||
signal: controller.signal,
|
||||
[Symbol.dispose]() {
|
||||
async [Symbol.dispose]() {
|
||||
log.info("unlocking", { sessionID })
|
||||
state().pending.delete(sessionID)
|
||||
|
||||
const isAutoCompacting = state().autoCompacting.get(sessionID) ?? false
|
||||
if (isAutoCompacting) {
|
||||
state().autoCompacting.delete(sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
const session = await get(sessionID)
|
||||
if (session.parentID) return
|
||||
|
||||
Bus.publish(Event.Idle, {
|
||||
sessionID,
|
||||
})
|
||||
@@ -1295,10 +1396,10 @@ export namespace Session {
|
||||
}
|
||||
return {
|
||||
cost: new Decimal(0)
|
||||
.add(new Decimal(tokens.input).mul(model.cost.input).div(1_000_000))
|
||||
.add(new Decimal(tokens.output).mul(model.cost.output).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(model.cost.cache_read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(model.cost.cache_write ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.input).mul(model.cost?.input ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.output).mul(model.cost?.output ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(model.cost?.cache_read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(model.cost?.cache_write ?? 0).div(1_000_000))
|
||||
.toNumber(),
|
||||
tokens,
|
||||
}
|
||||
|
||||
@@ -172,6 +172,21 @@ export namespace MessageV2 {
|
||||
})
|
||||
export type FilePart = z.infer<typeof FilePart>
|
||||
|
||||
export const AgentPart = PartBase.extend({
|
||||
type: z.literal("agent"),
|
||||
name: z.string(),
|
||||
source: z
|
||||
.object({
|
||||
value: z.string(),
|
||||
start: z.number().int(),
|
||||
end: z.number().int(),
|
||||
})
|
||||
.optional(),
|
||||
}).openapi({
|
||||
ref: "AgentPart",
|
||||
})
|
||||
export type AgentPart = z.infer<typeof AgentPart>
|
||||
|
||||
export const StepStartPart = PartBase.extend({
|
||||
type: z.literal("step-start"),
|
||||
}).openapi({
|
||||
@@ -212,7 +227,16 @@ export namespace MessageV2 {
|
||||
export type User = z.infer<typeof User>
|
||||
|
||||
export const Part = z
|
||||
.discriminatedUnion("type", [TextPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart])
|
||||
.discriminatedUnion("type", [
|
||||
TextPart,
|
||||
FilePart,
|
||||
ToolPart,
|
||||
StepStartPart,
|
||||
StepFinishPart,
|
||||
SnapshotPart,
|
||||
PatchPart,
|
||||
AgentPart,
|
||||
])
|
||||
.openapi({
|
||||
ref: "Part",
|
||||
})
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { App } from "../app/app"
|
||||
import { Config } from "../config/config"
|
||||
import z from "zod"
|
||||
import { Provider } from "../provider/provider"
|
||||
|
||||
export namespace Mode {
|
||||
export const Info = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
temperature: z.number().optional(),
|
||||
model: z
|
||||
.object({
|
||||
modelID: z.string(),
|
||||
providerID: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
prompt: z.string().optional(),
|
||||
tools: z.record(z.boolean()),
|
||||
})
|
||||
.openapi({
|
||||
ref: "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,
|
||||
patch: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for (const [key, value] of Object.entries(cfg.mode ?? {})) {
|
||||
if (value.disable) continue
|
||||
let item = result[key]
|
||||
if (!item)
|
||||
item = result[key] = {
|
||||
name: key,
|
||||
tools: {},
|
||||
}
|
||||
item.name = key
|
||||
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,
|
||||
...item.tools,
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
export async function get(mode: string) {
|
||||
return state().then((x) => x[mode])
|
||||
}
|
||||
|
||||
export async function list() {
|
||||
return state().then((x) => Object.values(x))
|
||||
}
|
||||
}
|
||||
@@ -93,18 +93,6 @@ Carefully read the issue and think hard about a plan to solve it before coding.
|
||||
- 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 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.
|
||||
|
||||
@@ -60,33 +60,28 @@ export namespace SystemPrompt {
|
||||
export async function custom() {
|
||||
const { cwd, root } = App.info().path
|
||||
const config = await Config.get()
|
||||
const found = []
|
||||
const paths = new Set<string>()
|
||||
|
||||
for (const item of CUSTOM_FILES) {
|
||||
const matches = await Filesystem.findUp(item, cwd, root)
|
||||
found.push(...matches.map((x) => Bun.file(x).text()))
|
||||
matches.forEach((path) => paths.add(path))
|
||||
}
|
||||
found.push(
|
||||
Bun.file(path.join(Global.Path.config, "AGENTS.md"))
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
found.push(
|
||||
Bun.file(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
|
||||
paths.add(path.join(Global.Path.config, "AGENTS.md"))
|
||||
paths.add(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||
|
||||
if (config.instructions) {
|
||||
for (const instruction of config.instructions) {
|
||||
try {
|
||||
const matches = await Filesystem.globUp(instruction, cwd, root)
|
||||
found.push(...matches.map((x) => Bun.file(x).text()))
|
||||
} catch {
|
||||
continue // Skip invalid glob patterns
|
||||
}
|
||||
const matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
|
||||
matches.forEach((path) => paths.add(path))
|
||||
}
|
||||
}
|
||||
|
||||
const found = Array.from(paths).map((p) =>
|
||||
Bun.file(p)
|
||||
.text()
|
||||
.catch(() => ""),
|
||||
)
|
||||
return Promise.all(found).then((result) => result.filter(Boolean))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "fs/promises"
|
||||
import { Log } from "../util/log"
|
||||
import { Global } from "../global"
|
||||
import { z } from "zod"
|
||||
import { Config } from "../config/config"
|
||||
|
||||
export namespace Snapshot {
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
@@ -26,6 +27,8 @@ export namespace Snapshot {
|
||||
export async function track() {
|
||||
const app = App.info()
|
||||
if (!app.git) return
|
||||
const cfg = await Config.get()
|
||||
if (cfg.snapshot === false) return
|
||||
const git = gitdir()
|
||||
if (await fs.mkdir(git, { recursive: true })) {
|
||||
await $`git init`
|
||||
@@ -39,7 +42,7 @@ export namespace Snapshot {
|
||||
log.info("initialized")
|
||||
}
|
||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
||||
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).text()
|
||||
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).nothrow().text()
|
||||
return hash.trim()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
import { z } from "zod"
|
||||
import { exec } from "child_process"
|
||||
import { text } from "stream/consumers"
|
||||
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"
|
||||
import { Permission } from "../permission"
|
||||
import { Config } from "../config/config"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { Log } from "../util/log"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
import { $ } from "bun"
|
||||
|
||||
const MAX_OUTPUT_LENGTH = 30000
|
||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||
|
||||
// const parser = new Parser()
|
||||
// parser.setLanguage(Bash.language as any)
|
||||
const log = Log.create({ service: "bash-tool" })
|
||||
|
||||
const parser = lazy(async () => {
|
||||
const { default: Parser } = await import("tree-sitter")
|
||||
const Bash = await import("tree-sitter-bash")
|
||||
const p = new Parser()
|
||||
p.setLanguage(Bash.language as any)
|
||||
return p
|
||||
})
|
||||
|
||||
export const BashTool = Tool.define("bash", {
|
||||
description: DESCRIPTION,
|
||||
@@ -28,9 +40,8 @@ export const BashTool = Tool.define("bash", {
|
||||
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 cfg = await Config.get()
|
||||
const tree = await parser().then((p) => p.parse(params.command))
|
||||
const permissions = (() => {
|
||||
const value = cfg.permission?.bash
|
||||
if (!value)
|
||||
@@ -65,9 +76,14 @@ export const BashTool = Tool.define("bash", {
|
||||
// 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)) {
|
||||
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
|
||||
const resolved = await $`realpath ${arg}`
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
.then((x) => x.trim())
|
||||
log.info("resolved path", { arg, resolved })
|
||||
if (resolved && !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.`,
|
||||
)
|
||||
@@ -77,42 +93,54 @@ export const BashTool = Tool.define("bash", {
|
||||
|
||||
// always allow cd if it passes above check
|
||||
if (!needsAsk && command[0] !== "cd") {
|
||||
const ask = (() => {
|
||||
const action = (() => {
|
||||
for (const [pattern, value] of Object.entries(permissions)) {
|
||||
if (new Bun.Glob(pattern).match(node.text)) {
|
||||
return value
|
||||
}
|
||||
const match = Wildcard.match(node.text, pattern)
|
||||
log.info("checking", { text: node.text.trim(), pattern, match })
|
||||
if (match) return value
|
||||
}
|
||||
return "ask"
|
||||
})()
|
||||
if (ask === "ask") needsAsk = true
|
||||
if (action === "deny") {
|
||||
throw new Error(
|
||||
"The user has specifically restricted access to this command, you are not allowed to execute it.",
|
||||
)
|
||||
}
|
||||
if (action === "ask") needsAsk = true
|
||||
}
|
||||
}
|
||||
|
||||
if (needsAsk) {
|
||||
await Permission.ask({
|
||||
id: "bash",
|
||||
type: "bash",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: params.command,
|
||||
metadata: {
|
||||
command: params.command,
|
||||
},
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
const process = Bun.spawn({
|
||||
cmd: ["bash", "-c", params.command],
|
||||
const process = exec(params.command, {
|
||||
cwd: app.path.cwd,
|
||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||
signal: ctx.abort,
|
||||
timeout: timeout,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||
timeout,
|
||||
})
|
||||
await process.exited
|
||||
const stdout = await new Response(process.stdout).text()
|
||||
const stderr = await new Response(process.stderr).text()
|
||||
|
||||
const stdoutPromise = text(process.stdout!)
|
||||
const stderrPromise = text(process.stderr!)
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
process.on("close", () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const stdout = await stdoutPromise
|
||||
const stderr = await stderrPromise
|
||||
|
||||
return {
|
||||
title: params.command,
|
||||
|
||||
@@ -35,61 +35,78 @@ export const EditTool = Tool.define("edit", {
|
||||
}
|
||||
|
||||
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 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 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 diff = ""
|
||||
let contentOld = ""
|
||||
let contentNew = ""
|
||||
await (async () => {
|
||||
if (params.oldString === "") {
|
||||
contentNew = params.newString
|
||||
await Bun.write(filepath, params.newString)
|
||||
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||
if (cfg.permission?.edit === "ask") {
|
||||
await Permission.ask({
|
||||
type: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "Edit this file: " + filePath,
|
||||
metadata: {
|
||||
filePath,
|
||||
diff,
|
||||
},
|
||||
})
|
||||
}
|
||||
await Bun.write(filePath, params.newString)
|
||||
await Bus.publish(File.Event.Edited, {
|
||||
file: filepath,
|
||||
file: filePath,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const file = Bun.file(filepath)
|
||||
const file = Bun.file(filePath)
|
||||
const stats = await file.stat().catch(() => {})
|
||||
if (!stats) throw new Error(`File ${filepath} not found`)
|
||||
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filepath}`)
|
||||
await FileTime.assert(ctx.sessionID, filepath)
|
||||
if (!stats) throw new Error(`File ${filePath} not found`)
|
||||
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
|
||||
await FileTime.assert(ctx.sessionID, filePath)
|
||||
contentOld = await file.text()
|
||||
|
||||
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)
|
||||
|
||||
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||
if (cfg.permission?.edit === "ask") {
|
||||
await Permission.ask({
|
||||
type: "edit",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: "Edit this file: " + filePath,
|
||||
metadata: {
|
||||
filePath,
|
||||
diff,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await file.write(contentNew)
|
||||
await Bus.publish(File.Event.Edited, {
|
||||
file: filepath,
|
||||
file: filePath,
|
||||
})
|
||||
contentNew = await file.text()
|
||||
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||
})()
|
||||
|
||||
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, contentNew))
|
||||
|
||||
FileTime.read(ctx.sessionID, filepath)
|
||||
FileTime.read(ctx.sessionID, filePath)
|
||||
|
||||
let output = ""
|
||||
await LSP.touchFile(filepath, true)
|
||||
await LSP.touchFile(filePath, true)
|
||||
const diagnostics = await LSP.diagnostics()
|
||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||
if (issues.length === 0) continue
|
||||
if (file === filepath) {
|
||||
if (file === filePath) {
|
||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
|
||||
continue
|
||||
}
|
||||
@@ -104,7 +121,7 @@ export const EditTool = Tool.define("edit", {
|
||||
diagnostics,
|
||||
diff,
|
||||
},
|
||||
title: `${path.relative(app.path.root, filepath)}`,
|
||||
title: `${path.relative(app.path.root, filePath)}`,
|
||||
output,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from "zod"
|
||||
import { Tool } from "./tool"
|
||||
|
||||
export const InvalidTool = Tool.define("invalid", {
|
||||
description: "Do not use",
|
||||
parameters: z.object({
|
||||
tool: z.string(),
|
||||
error: z.string(),
|
||||
}),
|
||||
async execute(params) {
|
||||
return {
|
||||
title: "Invalid Tool",
|
||||
output: `The arguments provided to the tool are invalid: ${params.error}`,
|
||||
metadata: {},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -10,9 +10,12 @@ import { TaskTool } from "./task"
|
||||
import { TodoWriteTool, TodoReadTool } from "./todo"
|
||||
import { WebFetchTool } from "./webfetch"
|
||||
import { WriteTool } from "./write"
|
||||
import { InvalidTool } from "./invalid"
|
||||
import { Config } from "../config/config"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
const ALL = [
|
||||
InvalidTool,
|
||||
BashTool,
|
||||
EditTool,
|
||||
WebFetchTool,
|
||||
@@ -63,27 +66,21 @@ export namespace ToolRegistry {
|
||||
return result
|
||||
}
|
||||
|
||||
export function enabled(_providerID: string, modelID: string): Record<string, boolean> {
|
||||
if (modelID.toLowerCase().includes("claude")) {
|
||||
return {
|
||||
patch: false,
|
||||
}
|
||||
export async function enabled(_providerID: string, _modelID: string): Promise<Record<string, boolean>> {
|
||||
const cfg = await Config.get()
|
||||
const result: Record<string, boolean> = {}
|
||||
result["patch"] = false
|
||||
|
||||
if (cfg.permission?.edit === "deny") {
|
||||
result["edit"] = false
|
||||
result["patch"] = false
|
||||
result["write"] = false
|
||||
}
|
||||
if (cfg?.permission?.bash === "deny") {
|
||||
result["bash"] = 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 {}
|
||||
return result
|
||||
}
|
||||
|
||||
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
|
||||
|
||||
@@ -8,8 +8,13 @@ import { Identifier } from "../id/id"
|
||||
import { Agent } from "../agent/agent"
|
||||
|
||||
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"))
|
||||
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
|
||||
const description = DESCRIPTION.replace(
|
||||
"{agents}",
|
||||
agents
|
||||
.map((a) => `- ${a.name}: ${a.description ?? "This subagent should only be called manually by the user."}`)
|
||||
.join("\n"),
|
||||
)
|
||||
return {
|
||||
description,
|
||||
parameters: z.object({
|
||||
@@ -20,8 +25,9 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
async execute(params, ctx) {
|
||||
const session = await Session.create(ctx.sessionID)
|
||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||
if (msg.role !== "assistant") throw new Error("Not an assistant message")
|
||||
if (msg.info.role !== "assistant") throw new Error("Not an assistant message")
|
||||
const agent = await Agent.get(params.subagent_type)
|
||||
if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
|
||||
const messageID = Identifier.ascending("message")
|
||||
const parts: Record<string, MessageV2.ToolPart> = {}
|
||||
const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
|
||||
@@ -38,8 +44,8 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
})
|
||||
|
||||
const model = agent.model ?? {
|
||||
modelID: msg.modelID,
|
||||
providerID: msg.providerID,
|
||||
modelID: msg.info.modelID,
|
||||
providerID: msg.info.providerID,
|
||||
}
|
||||
|
||||
ctx.abort.addEventListener("abort", () => {
|
||||
@@ -50,11 +56,12 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
sessionID: session.id,
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: msg.mode,
|
||||
system: agent.prompt,
|
||||
agent: agent.name,
|
||||
tools: {
|
||||
...agent.tools,
|
||||
todowrite: false,
|
||||
todoread: false,
|
||||
task: false,
|
||||
...agent.tools,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import Parser from "tree-sitter";
|
||||
import Bash from "tree-sitter-bash";
|
||||
|
||||
const parser = new Parser();
|
||||
parser.setLanguage(Bash.language as any);
|
||||
|
||||
const sourceCode = `cd --foo foo/bar && echo "hello" && cd ../baz`;
|
||||
|
||||
const tree = parser.parse(sourceCode);
|
||||
|
||||
// Function to extract commands and arguments
|
||||
function extractCommands(
|
||||
node: any,
|
||||
): Array<{ command: string; args: string[] }> {
|
||||
const commands: Array<{ command: string; args: string[] }> = [];
|
||||
|
||||
function traverse(node: any) {
|
||||
if (node.type === "command") {
|
||||
const commandNode = node.child(0);
|
||||
if (commandNode) {
|
||||
const command = commandNode.text;
|
||||
const args: string[] = [];
|
||||
|
||||
// Extract arguments
|
||||
for (let i = 1; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child && child.type === "word") {
|
||||
args.push(child.text);
|
||||
}
|
||||
}
|
||||
|
||||
commands.push({ command, args });
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse children
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
traverse(node.child(i));
|
||||
}
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Extract and display commands
|
||||
console.log("Source code: " + sourceCode);
|
||||
const commands = extractCommands(tree.rootNode);
|
||||
console.log("Extracted commands:");
|
||||
commands.forEach((cmd, index) => {
|
||||
console.log(`${index + 1}. Command: ${cmd.command}`);
|
||||
console.log(` Args: [${cmd.args.join(", ")}]`);
|
||||
});
|
||||
@@ -5,8 +5,8 @@ import { App } from "../app/app"
|
||||
|
||||
const TodoInfo = z.object({
|
||||
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"),
|
||||
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
|
||||
priority: z.string().describe("Priority level of the task: high, medium, low"),
|
||||
id: z.string().describe("Unique identifier for the todo item"),
|
||||
})
|
||||
type TodoInfo = z.infer<typeof TodoInfo>
|
||||
|
||||
@@ -7,6 +7,7 @@ export namespace Tool {
|
||||
export type Context<M extends Metadata = Metadata> = {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
callID?: string
|
||||
abort: AbortSignal
|
||||
metadata(input: { title?: string; metadata?: M }): void
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ export const WriteTool = Tool.define("write", {
|
||||
const cfg = await Config.get()
|
||||
if (cfg.permission?.edit === "ask")
|
||||
await Permission.ask({
|
||||
id: "write",
|
||||
type: "write",
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
||||
metadata: {
|
||||
filePath: filepath,
|
||||
|
||||
@@ -1,53 +1,17 @@
|
||||
import { Global } from "../global"
|
||||
import { Installation } from "../installation"
|
||||
import path from "path"
|
||||
import { NodeSDK } from "@opentelemetry/sdk-node"
|
||||
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch"
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
|
||||
|
||||
export namespace Trace {
|
||||
export function init() {
|
||||
if (!Installation.isDev()) return
|
||||
const writer = Bun.file(path.join(Global.Path.data, "log", "fetch.log")).writer()
|
||||
const sdk = new NodeSDK({
|
||||
serviceName: "opencode",
|
||||
instrumentations: [new FetchInstrumentation()],
|
||||
traceExporter: new OTLPTraceExporter({
|
||||
url: "http://localhost:4318/v1/traces", // or your OTLP endpoint
|
||||
}),
|
||||
})
|
||||
|
||||
const originalFetch = globalThis.fetch
|
||||
// @ts-expect-error
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
|
||||
const method = init?.method || "GET"
|
||||
|
||||
const urlObj = new URL(url)
|
||||
|
||||
writer.write(`\n${method} ${urlObj.pathname}${urlObj.search} HTTP/1.1\n`)
|
||||
writer.write(`Host: ${urlObj.host}\n`)
|
||||
|
||||
if (init?.headers) {
|
||||
if (init.headers instanceof Headers) {
|
||||
init.headers.forEach((value, key) => {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
})
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(init.headers)) {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (init?.body) {
|
||||
writer.write(`\n${init.body}`)
|
||||
}
|
||||
writer.flush()
|
||||
const response = await originalFetch(input, init)
|
||||
const clonedResponse = response.clone()
|
||||
writer.write(`\nHTTP/1.1 ${response.status} ${response.statusText}\n`)
|
||||
response.headers.forEach((value, key) => {
|
||||
writer.write(`${key}: ${value}\n`)
|
||||
})
|
||||
if (clonedResponse.body) {
|
||||
clonedResponse.text().then(async (x) => {
|
||||
writer.write(`\n${x}\n`)
|
||||
})
|
||||
}
|
||||
writer.flush()
|
||||
|
||||
return response
|
||||
}
|
||||
sdk.start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export namespace Wildcard {
|
||||
export function match(str: string, pattern: string) {
|
||||
const regex = new RegExp(
|
||||
"^" +
|
||||
pattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape special regex chars
|
||||
.replace(/\*/g, ".*") // * becomes .*
|
||||
.replace(/\?/g, ".") + // ? becomes .
|
||||
"$",
|
||||
"s", // s flag enables multiline matching
|
||||
)
|
||||
return regex.test(str)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Log } from "../../src/util/log"
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
toolCallID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
@@ -18,19 +19,21 @@ Log.init({ print: false })
|
||||
describe("tool.bash", () => {
|
||||
test("basic", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
await bash.execute(
|
||||
const result = await bash.execute(
|
||||
{
|
||||
command: "cd foo/bar && ls",
|
||||
description: "List files in foo/bar",
|
||||
command: "echo 'test'",
|
||||
description: "Echo test message",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata.exit).toBe(0)
|
||||
expect(result.metadata.stdout).toContain("test")
|
||||
})
|
||||
})
|
||||
|
||||
test("cd ../ should fail", async () => {
|
||||
test("cd ../ should fail outside of project root", async () => {
|
||||
await App.provide({ cwd: projectRoot }, async () => {
|
||||
expect(
|
||||
await expect(
|
||||
bash.execute(
|
||||
{
|
||||
command: "cd ../",
|
||||
@@ -38,7 +41,7 @@ describe("tool.bash", () => {
|
||||
},
|
||||
ctx,
|
||||
),
|
||||
).rejects.toThrow()
|
||||
).rejects.toThrow("This command references paths outside of")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import path from "path"
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
toolCallID: "",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {}
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"customConditions": [
|
||||
"development"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.3.133",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:",
|
||||
"@hey-api/openapi-ts": "0.80.1",
|
||||
"@tsconfig/node22": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
|
||||
import { $ } from "bun"
|
||||
|
||||
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
|
||||
|
||||
await $`bun tsc`
|
||||
|
||||
if (snapshot) {
|
||||
await $`bun publish --tag snapshot --access public`
|
||||
await $`git checkout package.json`
|
||||
}
|
||||
if (!snapshot) {
|
||||
await $`bun publish --access public`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Plugin } from "./index"
|
||||
|
||||
export const ExamplePlugin: Plugin = async ({ app, client, $ }) => {
|
||||
return {
|
||||
permission: {},
|
||||
async "chat.params"(input, output) {
|
||||
output.topP = 1
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Event, createOpencodeClient, App, Model, Provider, Permission, UserMessage, Part } from "@opencode-ai/sdk"
|
||||
import type { BunShell } from "./shell"
|
||||
|
||||
export type PluginInput = {
|
||||
client: ReturnType<typeof createOpencodeClient>
|
||||
app: App
|
||||
$: BunShell
|
||||
}
|
||||
export type Plugin = (input: PluginInput) => Promise<Hooks>
|
||||
|
||||
export interface Hooks {
|
||||
event?: (input: { event: Event }) => Promise<void>
|
||||
/**
|
||||
* Called when a new message is received
|
||||
*/
|
||||
"chat.message"?: (input: {}, output: { message: UserMessage; parts: Part[] }) => Promise<void>
|
||||
/**
|
||||
* Modify parameters sent to LLM
|
||||
*/
|
||||
"chat.params"?: (
|
||||
input: { model: Model; provider: Provider; message: UserMessage },
|
||||
output: { temperature: number; topP: number },
|
||||
) => Promise<void>
|
||||
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
|
||||
"tool.execute.before"?: (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: { args: any },
|
||||
) => Promise<void>
|
||||
"tool.execute.after"?: (
|
||||
input: { tool: string; sessionID: string; callID: string },
|
||||
output: {
|
||||
title: string
|
||||
output: string
|
||||
metadata: any
|
||||
},
|
||||
) => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
export type ShellFunction = (input: Uint8Array) => Uint8Array
|
||||
|
||||
export type ShellExpression =
|
||||
| { toString(): string }
|
||||
| Array<ShellExpression>
|
||||
| string
|
||||
| { raw: string }
|
||||
| ReadableStream
|
||||
|
||||
export interface BunShell {
|
||||
(strings: TemplateStringsArray, ...expressions: ShellExpression[]): BunShellPromise
|
||||
|
||||
/**
|
||||
* Perform bash-like brace expansion on the given pattern.
|
||||
* @param pattern - Brace pattern to expand
|
||||
*/
|
||||
braces(pattern: string): string[]
|
||||
|
||||
/**
|
||||
* Escape strings for input into shell commands.
|
||||
*/
|
||||
escape(input: string): string
|
||||
|
||||
/**
|
||||
* Change the default environment variables for shells created by this instance.
|
||||
*/
|
||||
env(newEnv?: Record<string, string | undefined>): BunShell
|
||||
|
||||
/**
|
||||
* Default working directory to use for shells created by this instance.
|
||||
*/
|
||||
cwd(newCwd?: string): BunShell
|
||||
|
||||
/**
|
||||
* Configure the shell to not throw an exception on non-zero exit codes.
|
||||
*/
|
||||
nothrow(): BunShell
|
||||
|
||||
/**
|
||||
* Configure whether or not the shell should throw an exception on non-zero exit codes.
|
||||
*/
|
||||
throws(shouldThrow: boolean): BunShell
|
||||
}
|
||||
|
||||
export interface BunShellPromise extends Promise<BunShellOutput> {
|
||||
readonly stdin: WritableStream
|
||||
|
||||
/**
|
||||
* Change the current working directory of the shell.
|
||||
*/
|
||||
cwd(newCwd: string): this
|
||||
|
||||
/**
|
||||
* Set environment variables for the shell.
|
||||
*/
|
||||
env(newEnv: Record<string, string> | undefined): this
|
||||
|
||||
/**
|
||||
* By default, the shell will write to the current process's stdout and stderr, as well as buffering that output.
|
||||
* This configures the shell to only buffer the output.
|
||||
*/
|
||||
quiet(): this
|
||||
|
||||
/**
|
||||
* Read from stdout as a string, line by line
|
||||
* Automatically calls quiet() to disable echoing to stdout.
|
||||
*/
|
||||
lines(): AsyncIterable<string>
|
||||
|
||||
/**
|
||||
* Read from stdout as a string.
|
||||
* Automatically calls quiet() to disable echoing to stdout.
|
||||
*/
|
||||
text(encoding?: BufferEncoding): Promise<string>
|
||||
|
||||
/**
|
||||
* Read from stdout as a JSON object
|
||||
* Automatically calls quiet()
|
||||
*/
|
||||
json(): Promise<any>
|
||||
|
||||
/**
|
||||
* Read from stdout as an ArrayBuffer
|
||||
* Automatically calls quiet()
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>
|
||||
|
||||
/**
|
||||
* Read from stdout as a Blob
|
||||
* Automatically calls quiet()
|
||||
*/
|
||||
blob(): Promise<Blob>
|
||||
|
||||
/**
|
||||
* Configure the shell to not throw an exception on non-zero exit codes.
|
||||
*/
|
||||
nothrow(): this
|
||||
|
||||
/**
|
||||
* Configure whether or not the shell should throw an exception on non-zero exit codes.
|
||||
*/
|
||||
throws(shouldThrow: boolean): this
|
||||
}
|
||||
|
||||
export interface BunShellOutput {
|
||||
readonly stdout: Buffer
|
||||
readonly stderr: Buffer
|
||||
readonly exitCode: number
|
||||
|
||||
/**
|
||||
* Read from stdout as a string
|
||||
*/
|
||||
text(encoding?: BufferEncoding): string
|
||||
|
||||
/**
|
||||
* Read from stdout as a JSON object
|
||||
*/
|
||||
json(): any
|
||||
|
||||
/**
|
||||
* Read from stdout as an ArrayBuffer
|
||||
*/
|
||||
arrayBuffer(): ArrayBuffer
|
||||
|
||||
/**
|
||||
* Read from stdout as an Uint8Array
|
||||
*/
|
||||
bytes(): Uint8Array
|
||||
|
||||
/**
|
||||
* Read from stdout as a Blob
|
||||
*/
|
||||
blob(): Blob
|
||||
}
|
||||
|
||||
export type BunShellError = Error & BunShellOutput
|
||||
@@ -0,0 +1,9 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig.json",
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"module": "preserve",
|
||||
"declaration": true,
|
||||
"moduleResolution": "bundler",
|
||||
"customConditions": [
|
||||
"development"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
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-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Bootstrap
|
||||
run: ./scripts/bootstrap
|
||||
|
||||
- name: Check types
|
||||
run: ./scripts/lint
|
||||
|
||||
build:
|
||||
timeout-minutes: 5
|
||||
name: build
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/opencode-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Bootstrap
|
||||
run: ./scripts/bootstrap
|
||||
|
||||
- name: Check build
|
||||
run: ./scripts/build
|
||||
|
||||
- name: Get GitHub OIDC Token
|
||||
if: github.repository == 'stainless-sdks/opencode-typescript'
|
||||
id: github-oidc
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: core.setOutput('github_token', await core.getIDToken());
|
||||
|
||||
- name: Upload tarball
|
||||
if: github.repository == 'stainless-sdks/opencode-typescript'
|
||||
env:
|
||||
URL: https://pkg.stainless.com/s
|
||||
AUTH: ${{ steps.github-oidc.outputs.github_token }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: ./scripts/utils/upload-artifact.sh
|
||||
test:
|
||||
timeout-minutes: 10
|
||||
name: test
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/opencode-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Bootstrap
|
||||
run: ./scripts/bootstrap
|
||||
|
||||
- name: Run tests
|
||||
run: ./scripts/test
|
||||
@@ -1,32 +0,0 @@
|
||||
# This workflow is triggered when a GitHub release is created.
|
||||
# It can also be run manually to re-publish to NPM in case it failed for some reason.
|
||||
# You can run this workflow by navigating to https://www.github.com/sst/opencode-sdk-js/actions/workflows/publish-npm.yml
|
||||
name: Publish NPM
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn install
|
||||
|
||||
- name: Publish to NPM
|
||||
run: |
|
||||
bash ./bin/publish-npm
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.OPENCODE_NPM_TOKEN || secrets.NPM_TOKEN }}
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Release Doctor
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release_doctor:
|
||||
name: release doctor
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'sst/opencode-sdk-js' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check release environment
|
||||
run: |
|
||||
bash ./bin/check-release-environment
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.OPENCODE_NPM_TOKEN || secrets.NPM_TOKEN }}
|
||||
@@ -1,4 +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
|
||||
configured_endpoints: 34
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-da1c340135c3dd6b1edb4e00e7039d2ac54d59271683a8b6ed528e51137ce41a.yml
|
||||
openapi_spec_hash: 0cdd9b6273d72f5a6f484a2999ff0632
|
||||
config_hash: 7581d5948150d4ef7dd7b13d0845dbeb
|
||||
|
||||
@@ -18,18 +18,18 @@ Methods:
|
||||
|
||||
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#Agent">Agent</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/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 /agent">client.App.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AppService.Agents">Agents</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#Agent">Agent</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||
- <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
|
||||
@@ -65,7 +65,6 @@ 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#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:
|
||||
|
||||
@@ -75,6 +74,7 @@ Methods:
|
||||
|
||||
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#AgentPartInputParam">AgentPartInputParam</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#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>
|
||||
@@ -84,6 +84,7 @@ Params Types:
|
||||
|
||||
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#AgentPart">AgentPart</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/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>
|
||||
@@ -103,6 +104,7 @@ 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#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#SessionMessageResponse">SessionMessageResponse</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:
|
||||
@@ -113,6 +115,7 @@ Methods:
|
||||
- <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/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</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>, messageID <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#SessionMessageResponse">SessionMessageResponse</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>
|
||||
@@ -120,9 +123,25 @@ Methods:
|
||||
- <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>
|
||||
|
||||
## Permissions
|
||||
|
||||
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#Permission">Permission</a>
|
||||
|
||||
Methods:
|
||||
|
||||
- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</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>, permissionID <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#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</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/clear-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ClearPrompt">ClearPrompt</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 /tui/execute-command">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.ExecuteCommand">ExecuteCommand</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#TuiExecuteCommandParams">TuiExecuteCommandParams</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>
|
||||
- <code title="post /tui/open-models">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenModels">OpenModels</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 /tui/open-sessions">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenSessions">OpenSessions</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 /tui/open-themes">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.OpenThemes">OpenThemes</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 /tui/submit-prompt">client.Tui.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TuiService.SubmitPrompt">SubmitPrompt</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>
|
||||
|
||||
@@ -31,6 +31,14 @@ func NewAppService(opts ...option.RequestOption) (r *AppService) {
|
||||
return
|
||||
}
|
||||
|
||||
// List all agents
|
||||
func (r *AppService) Agents(ctx context.Context, opts ...option.RequestOption) (res *[]Agent, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "agent"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Get app info
|
||||
func (r *AppService) Get(ctx context.Context, opts ...option.RequestOption) (res *App, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
@@ -55,14 +63,6 @@ func (r *AppService) Log(ctx context.Context, body AppLogParams, opts ...option.
|
||||
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...)
|
||||
@@ -71,6 +71,78 @@ func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption
|
||||
return
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
Mode AgentMode `json:"mode,required"`
|
||||
Name string `json:"name,required"`
|
||||
Tools map[string]bool `json:"tools,required"`
|
||||
Description string `json:"description"`
|
||||
Model AgentModel `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"topP"`
|
||||
JSON agentJSON `json:"-"`
|
||||
}
|
||||
|
||||
// agentJSON contains the JSON metadata for the struct [Agent]
|
||||
type agentJSON struct {
|
||||
Mode apijson.Field
|
||||
Name apijson.Field
|
||||
Tools apijson.Field
|
||||
Description apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Agent) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r agentJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AgentMode string
|
||||
|
||||
const (
|
||||
AgentModeSubagent AgentMode = "subagent"
|
||||
AgentModePrimary AgentMode = "primary"
|
||||
AgentModeAll AgentMode = "all"
|
||||
)
|
||||
|
||||
func (r AgentMode) IsKnown() bool {
|
||||
switch r {
|
||||
case AgentModeSubagent, AgentModePrimary, AgentModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AgentModel struct {
|
||||
ModelID string `json:"modelID,required"`
|
||||
ProviderID string `json:"providerID,required"`
|
||||
JSON agentModelJSON `json:"-"`
|
||||
}
|
||||
|
||||
// agentModelJSON contains the JSON metadata for the struct [AgentModel]
|
||||
type agentModelJSON struct {
|
||||
ModelID apijson.Field
|
||||
ProviderID apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AgentModel) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r agentModelJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Git bool `json:"git,required"`
|
||||
Hostname string `json:"hostname,required"`
|
||||
@@ -145,56 +217,6 @@ 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"`
|
||||
|
||||
@@ -13,6 +13,28 @@ import (
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
func TestAppAgents(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.Agents(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 TestAppGet(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
@@ -86,28 +108,6 @@ func TestAppLogWithOptionalParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@@ -43,7 +43,7 @@ func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (
|
||||
type Config struct {
|
||||
// JSON schema reference for configuration validation
|
||||
Schema string `json:"$schema"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
// Agent configuration, see https://opencode.ai/docs/agent
|
||||
Agent ConfigAgent `json:"agent"`
|
||||
// @deprecated Use 'share' field instead. Share newly created sessions
|
||||
// automatically
|
||||
@@ -51,27 +51,30 @@ type Config struct {
|
||||
// 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"`
|
||||
DisabledProviders []string `json:"disabled_providers"`
|
||||
Experimental ConfigExperimental `json:"experimental"`
|
||||
Formatter map[string]ConfigFormatter `json:"formatter"`
|
||||
// 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"`
|
||||
Layout ConfigLayout `json:"layout"`
|
||||
Lsp map[string]ConfigLsp `json:"lsp"`
|
||||
// MCP (Model Context Protocol) server configurations
|
||||
Mcp map[string]ConfigMcp `json:"mcp"`
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
// @deprecated Use `agent` field instead.
|
||||
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"`
|
||||
Plugin []string `json:"plugin"`
|
||||
// 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
|
||||
// Small model to use for tasks like title generation in the
|
||||
// format of provider/model
|
||||
SmallModel string `json:"small_model"`
|
||||
// Theme name to use for the interface
|
||||
@@ -89,13 +92,16 @@ type configJSON struct {
|
||||
Autoupdate apijson.Field
|
||||
DisabledProviders apijson.Field
|
||||
Experimental apijson.Field
|
||||
Formatter apijson.Field
|
||||
Instructions apijson.Field
|
||||
Keybinds apijson.Field
|
||||
Layout apijson.Field
|
||||
Lsp apijson.Field
|
||||
Mcp apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Permission apijson.Field
|
||||
Plugin apijson.Field
|
||||
Provider apijson.Field
|
||||
Share apijson.Field
|
||||
SmallModel apijson.Field
|
||||
@@ -113,16 +119,20 @@ func (r configJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
// Agent configuration, see https://opencode.ai/docs/agent
|
||||
type ConfigAgent struct {
|
||||
Build ConfigAgentBuild `json:"build"`
|
||||
General ConfigAgentGeneral `json:"general"`
|
||||
Plan ConfigAgentPlan `json:"plan"`
|
||||
ExtraFields map[string]ConfigAgent `json:"-,extras"`
|
||||
JSON configAgentJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configAgentJSON contains the JSON metadata for the struct [ConfigAgent]
|
||||
type configAgentJSON struct {
|
||||
Build apijson.Field
|
||||
General apijson.Field
|
||||
Plan apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
@@ -135,16 +145,82 @@ func (r configAgentJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigAgentBuild struct {
|
||||
// Description of when to use the agent
|
||||
Description string `json:"description"`
|
||||
Disable bool `json:"disable"`
|
||||
Mode ConfigAgentBuildMode `json:"mode"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON configAgentBuildJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configAgentBuildJSON contains the JSON metadata for the struct
|
||||
// [ConfigAgentBuild]
|
||||
type configAgentBuildJSON struct {
|
||||
Description apijson.Field
|
||||
Disable apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigAgentBuild) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configAgentBuildJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigAgentBuildMode string
|
||||
|
||||
const (
|
||||
ConfigAgentBuildModeSubagent ConfigAgentBuildMode = "subagent"
|
||||
ConfigAgentBuildModePrimary ConfigAgentBuildMode = "primary"
|
||||
ConfigAgentBuildModeAll ConfigAgentBuildMode = "all"
|
||||
)
|
||||
|
||||
func (r ConfigAgentBuildMode) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigAgentBuildModeSubagent, ConfigAgentBuildModePrimary, ConfigAgentBuildModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigAgentGeneral struct {
|
||||
Description string `json:"description,required"`
|
||||
// Description of when to use the agent
|
||||
Description string `json:"description"`
|
||||
Disable bool `json:"disable"`
|
||||
Mode ConfigAgentGeneralMode `json:"mode"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON configAgentGeneralJSON `json:"-"`
|
||||
ModeConfig
|
||||
}
|
||||
|
||||
// configAgentGeneralJSON contains the JSON metadata for the struct
|
||||
// [ConfigAgentGeneral]
|
||||
type configAgentGeneralJSON struct {
|
||||
Description apijson.Field
|
||||
Disable apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
@@ -157,6 +233,73 @@ func (r configAgentGeneralJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigAgentGeneralMode string
|
||||
|
||||
const (
|
||||
ConfigAgentGeneralModeSubagent ConfigAgentGeneralMode = "subagent"
|
||||
ConfigAgentGeneralModePrimary ConfigAgentGeneralMode = "primary"
|
||||
ConfigAgentGeneralModeAll ConfigAgentGeneralMode = "all"
|
||||
)
|
||||
|
||||
func (r ConfigAgentGeneralMode) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigAgentGeneralModeSubagent, ConfigAgentGeneralModePrimary, ConfigAgentGeneralModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigAgentPlan struct {
|
||||
// Description of when to use the agent
|
||||
Description string `json:"description"`
|
||||
Disable bool `json:"disable"`
|
||||
Mode ConfigAgentPlanMode `json:"mode"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON configAgentPlanJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configAgentPlanJSON contains the JSON metadata for the struct [ConfigAgentPlan]
|
||||
type configAgentPlanJSON struct {
|
||||
Description apijson.Field
|
||||
Disable apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigAgentPlan) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configAgentPlanJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigAgentPlanMode string
|
||||
|
||||
const (
|
||||
ConfigAgentPlanModeSubagent ConfigAgentPlanMode = "subagent"
|
||||
ConfigAgentPlanModePrimary ConfigAgentPlanMode = "primary"
|
||||
ConfigAgentPlanModeAll ConfigAgentPlanMode = "all"
|
||||
)
|
||||
|
||||
func (r ConfigAgentPlanMode) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigAgentPlanModeSubagent, ConfigAgentPlanModePrimary, ConfigAgentPlanModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigExperimental struct {
|
||||
Hook ConfigExperimentalHook `json:"hook"`
|
||||
JSON configExperimentalJSON `json:"-"`
|
||||
@@ -247,6 +390,32 @@ func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigFormatter struct {
|
||||
Command []string `json:"command"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Environment map[string]string `json:"environment"`
|
||||
Extensions []string `json:"extensions"`
|
||||
JSON configFormatterJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configFormatterJSON contains the JSON metadata for the struct [ConfigFormatter]
|
||||
type configFormatterJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Environment apijson.Field
|
||||
Extensions apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigFormatter) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configFormatterJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// @deprecated Always uses stretch layout.
|
||||
type ConfigLayout string
|
||||
|
||||
@@ -263,6 +432,139 @@ func (r ConfigLayout) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigLsp struct {
|
||||
// This field can have the runtime type of [[]string].
|
||||
Command interface{} `json:"command"`
|
||||
Disabled bool `json:"disabled"`
|
||||
// This field can have the runtime type of [map[string]string].
|
||||
Env interface{} `json:"env"`
|
||||
// This field can have the runtime type of [[]string].
|
||||
Extensions interface{} `json:"extensions"`
|
||||
// This field can have the runtime type of [map[string]interface{}].
|
||||
Initialization interface{} `json:"initialization"`
|
||||
JSON configLspJSON `json:"-"`
|
||||
union ConfigLspUnion
|
||||
}
|
||||
|
||||
// configLspJSON contains the JSON metadata for the struct [ConfigLsp]
|
||||
type configLspJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Env apijson.Field
|
||||
Extensions apijson.Field
|
||||
Initialization apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r configLspJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r *ConfigLsp) UnmarshalJSON(data []byte) (err error) {
|
||||
*r = ConfigLsp{}
|
||||
err = apijson.UnmarshalRoot(data, &r.union)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apijson.Port(r.union, &r)
|
||||
}
|
||||
|
||||
// AsUnion returns a [ConfigLspUnion] interface which you can cast to the specific
|
||||
// types for more type safety.
|
||||
//
|
||||
// Possible runtime types of the union are [ConfigLspDisabled], [ConfigLspObject].
|
||||
func (r ConfigLsp) AsUnion() ConfigLspUnion {
|
||||
return r.union
|
||||
}
|
||||
|
||||
// Union satisfied by [ConfigLspDisabled] or [ConfigLspObject].
|
||||
type ConfigLspUnion interface {
|
||||
implementsConfigLsp()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*ConfigLspUnion)(nil)).Elem(),
|
||||
"",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(ConfigLspDisabled{}),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(ConfigLspObject{}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type ConfigLspDisabled struct {
|
||||
Disabled ConfigLspDisabledDisabled `json:"disabled,required"`
|
||||
JSON configLspDisabledJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configLspDisabledJSON contains the JSON metadata for the struct
|
||||
// [ConfigLspDisabled]
|
||||
type configLspDisabledJSON struct {
|
||||
Disabled apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigLspDisabled) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configLspDisabledJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r ConfigLspDisabled) implementsConfigLsp() {}
|
||||
|
||||
type ConfigLspDisabledDisabled bool
|
||||
|
||||
const (
|
||||
ConfigLspDisabledDisabledTrue ConfigLspDisabledDisabled = true
|
||||
)
|
||||
|
||||
func (r ConfigLspDisabledDisabled) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigLspDisabledDisabledTrue:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigLspObject struct {
|
||||
Command []string `json:"command,required"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Env map[string]string `json:"env"`
|
||||
Extensions []string `json:"extensions"`
|
||||
Initialization map[string]interface{} `json:"initialization"`
|
||||
JSON configLspObjectJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configLspObjectJSON contains the JSON metadata for the struct [ConfigLspObject]
|
||||
type configLspObjectJSON struct {
|
||||
Command apijson.Field
|
||||
Disabled apijson.Field
|
||||
Env apijson.Field
|
||||
Extensions apijson.Field
|
||||
Initialization apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigLspObject) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configLspObjectJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r ConfigLspObject) implementsConfigLsp() {}
|
||||
|
||||
type ConfigMcp struct {
|
||||
// Type of MCP server connection
|
||||
Type ConfigMcpType `json:"type,required"`
|
||||
@@ -351,11 +653,11 @@ func (r ConfigMcpType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Modes configuration, see https://opencode.ai/docs/modes
|
||||
// @deprecated Use `agent` field instead.
|
||||
type ConfigMode struct {
|
||||
Build ModeConfig `json:"build"`
|
||||
Plan ModeConfig `json:"plan"`
|
||||
ExtraFields map[string]ModeConfig `json:"-,extras"`
|
||||
Build ConfigModeBuild `json:"build"`
|
||||
Plan ConfigModePlan `json:"plan"`
|
||||
ExtraFields map[string]ConfigMode `json:"-,extras"`
|
||||
JSON configModeJSON `json:"-"`
|
||||
}
|
||||
|
||||
@@ -375,6 +677,108 @@ func (r configModeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigModeBuild struct {
|
||||
// Description of when to use the agent
|
||||
Description string `json:"description"`
|
||||
Disable bool `json:"disable"`
|
||||
Mode ConfigModeBuildMode `json:"mode"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON configModeBuildJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configModeBuildJSON contains the JSON metadata for the struct [ConfigModeBuild]
|
||||
type configModeBuildJSON struct {
|
||||
Description apijson.Field
|
||||
Disable apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigModeBuild) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configModeBuildJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigModeBuildMode string
|
||||
|
||||
const (
|
||||
ConfigModeBuildModeSubagent ConfigModeBuildMode = "subagent"
|
||||
ConfigModeBuildModePrimary ConfigModeBuildMode = "primary"
|
||||
ConfigModeBuildModeAll ConfigModeBuildMode = "all"
|
||||
)
|
||||
|
||||
func (r ConfigModeBuildMode) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigModeBuildModeSubagent, ConfigModeBuildModePrimary, ConfigModeBuildModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigModePlan struct {
|
||||
// Description of when to use the agent
|
||||
Description string `json:"description"`
|
||||
Disable bool `json:"disable"`
|
||||
Mode ConfigModePlanMode `json:"mode"`
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
Tools map[string]bool `json:"tools"`
|
||||
TopP float64 `json:"top_p"`
|
||||
JSON configModePlanJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configModePlanJSON contains the JSON metadata for the struct [ConfigModePlan]
|
||||
type configModePlanJSON struct {
|
||||
Description apijson.Field
|
||||
Disable apijson.Field
|
||||
Mode apijson.Field
|
||||
Model apijson.Field
|
||||
Prompt apijson.Field
|
||||
Temperature apijson.Field
|
||||
Tools apijson.Field
|
||||
TopP apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigModePlan) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configModePlanJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigModePlanMode string
|
||||
|
||||
const (
|
||||
ConfigModePlanModeSubagent ConfigModePlanMode = "subagent"
|
||||
ConfigModePlanModePrimary ConfigModePlanMode = "primary"
|
||||
ConfigModePlanModeAll ConfigModePlanMode = "all"
|
||||
)
|
||||
|
||||
func (r ConfigModePlanMode) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigModePlanModeSubagent, ConfigModePlanModePrimary, ConfigModePlanModeAll:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigPermission struct {
|
||||
Bash ConfigPermissionBashUnion `json:"bash"`
|
||||
Edit ConfigPermissionEdit `json:"edit"`
|
||||
@@ -423,11 +827,12 @@ type ConfigPermissionBashString string
|
||||
const (
|
||||
ConfigPermissionBashStringAsk ConfigPermissionBashString = "ask"
|
||||
ConfigPermissionBashStringAllow ConfigPermissionBashString = "allow"
|
||||
ConfigPermissionBashStringDeny ConfigPermissionBashString = "deny"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionBashString) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow:
|
||||
case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow, ConfigPermissionBashStringDeny:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -444,11 +849,12 @@ type ConfigPermissionBashMapItem string
|
||||
const (
|
||||
ConfigPermissionBashMapAsk ConfigPermissionBashMapItem = "ask"
|
||||
ConfigPermissionBashMapAllow ConfigPermissionBashMapItem = "allow"
|
||||
ConfigPermissionBashMapDeny ConfigPermissionBashMapItem = "deny"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionBashMapItem) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow:
|
||||
case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow, ConfigPermissionBashMapDeny:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -459,11 +865,12 @@ type ConfigPermissionEdit string
|
||||
const (
|
||||
ConfigPermissionEditAsk ConfigPermissionEdit = "ask"
|
||||
ConfigPermissionEditAllow ConfigPermissionEdit = "allow"
|
||||
ConfigPermissionEditDeny ConfigPermissionEdit = "deny"
|
||||
)
|
||||
|
||||
func (r ConfigPermissionEdit) IsKnown() bool {
|
||||
switch r {
|
||||
case ConfigPermissionEditAsk, ConfigPermissionEditAllow:
|
||||
case ConfigPermissionEditAsk, ConfigPermissionEditAllow, ConfigPermissionEditDeny:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -701,9 +1108,13 @@ type KeybindsConfig struct {
|
||||
SessionShare string `json:"session_share,required"`
|
||||
// Unshare current session
|
||||
SessionUnshare string `json:"session_unshare,required"`
|
||||
// Next mode
|
||||
// Next agent
|
||||
SwitchAgent string `json:"switch_agent,required"`
|
||||
// Previous agent
|
||||
SwitchAgentReverse string `json:"switch_agent_reverse,required"`
|
||||
// @deprecated use switch_agent. Next mode
|
||||
SwitchMode string `json:"switch_mode,required"`
|
||||
// Previous Mode
|
||||
// @deprecated use switch_agent_reverse. Previous mode
|
||||
SwitchModeReverse string `json:"switch_mode_reverse,required"`
|
||||
// List available themes
|
||||
ThemeList string `json:"theme_list,required"`
|
||||
@@ -748,6 +1159,8 @@ type keybindsConfigJSON struct {
|
||||
SessionNew apijson.Field
|
||||
SessionShare apijson.Field
|
||||
SessionUnshare apijson.Field
|
||||
SwitchAgent apijson.Field
|
||||
SwitchAgentReverse apijson.Field
|
||||
SwitchMode apijson.Field
|
||||
SwitchModeReverse apijson.Field
|
||||
ThemeList apijson.Field
|
||||
@@ -857,31 +1270,3 @@ func (r McpRemoteConfigType) IsKnown() bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ type EventListResponse struct {
|
||||
// [EventListResponseEventMessagePartUpdatedProperties],
|
||||
// [EventListResponseEventMessagePartRemovedProperties],
|
||||
// [EventListResponseEventStorageWriteProperties],
|
||||
// [EventListResponseEventPermissionUpdatedProperties],
|
||||
// [EventListResponseEventFileEditedProperties],
|
||||
// [EventListResponseEventFileEditedProperties], [interface{}], [Permission],
|
||||
// [EventListResponseEventPermissionRepliedProperties],
|
||||
// [EventListResponseEventSessionUpdatedProperties],
|
||||
// [EventListResponseEventSessionDeletedProperties],
|
||||
// [EventListResponseEventSessionIdleProperties],
|
||||
// [EventListResponseEventSessionErrorProperties], [interface{}],
|
||||
// [EventListResponseEventSessionErrorProperties],
|
||||
// [EventListResponseEventFileWatcherUpdatedProperties],
|
||||
// [EventListResponseEventIdeInstalledProperties].
|
||||
Properties interface{} `json:"properties,required"`
|
||||
@@ -100,10 +100,12 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||
// [EventListResponseEventMessagePartUpdated],
|
||||
// [EventListResponseEventMessagePartRemoved],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
|
||||
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
|
||||
// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied],
|
||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventFileWatcherUpdated],
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
@@ -115,10 +117,12 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
||||
// [EventListResponseEventMessagePartUpdated],
|
||||
// [EventListResponseEventMessagePartRemoved],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated],
|
||||
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle],
|
||||
// [EventListResponseEventSessionError], [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventStorageWrite], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied],
|
||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventFileWatcherUpdated] or
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
type EventListResponseUnion interface {
|
||||
@@ -164,6 +168,16 @@ func init() {
|
||||
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
||||
DiscriminatorValue: "storage.write",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
|
||||
DiscriminatorValue: "file.edited",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||
DiscriminatorValue: "server.connected",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
|
||||
@@ -171,8 +185,8 @@ func init() {
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
|
||||
DiscriminatorValue: "file.edited",
|
||||
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
|
||||
DiscriminatorValue: "permission.replied",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
@@ -194,11 +208,6 @@ func init() {
|
||||
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
|
||||
DiscriminatorValue: "session.error",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||
DiscriminatorValue: "server.connected",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
|
||||
@@ -642,95 +651,6 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionUpdated struct {
|
||||
Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
||||
JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventPermissionUpdated]
|
||||
type eventListResponseEventPermissionUpdatedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionUpdated) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventPermissionUpdatedProperties struct {
|
||||
ID string `json:"id,required"`
|
||||
Metadata map[string]interface{} `json:"metadata,required"`
|
||||
SessionID string `json:"sessionID,required"`
|
||||
Time EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"`
|
||||
Title string `json:"title,required"`
|
||||
JSON eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata
|
||||
// for the struct [EventListResponseEventPermissionUpdatedProperties]
|
||||
type eventListResponseEventPermissionUpdatedPropertiesJSON struct {
|
||||
ID apijson.Field
|
||||
Metadata apijson.Field
|
||||
SessionID apijson.Field
|
||||
Time apijson.Field
|
||||
Title apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionUpdatedPropertiesTime struct {
|
||||
Created float64 `json:"created,required"`
|
||||
JSON eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON
|
||||
// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime]
|
||||
type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct {
|
||||
Created apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionUpdatedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventPermissionUpdatedTypePermissionUpdated EventListResponseEventPermissionUpdatedType = "permission.updated"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventPermissionUpdatedTypePermissionUpdated:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventFileEdited struct {
|
||||
Properties EventListResponseEventFileEditedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventFileEditedType `json:"type,required"`
|
||||
@@ -791,6 +711,148 @@ func (r EventListResponseEventFileEditedType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventServerConnected struct {
|
||||
Properties interface{} `json:"properties,required"`
|
||||
Type EventListResponseEventServerConnectedType `json:"type,required"`
|
||||
JSON eventListResponseEventServerConnectedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventServerConnected]
|
||||
type eventListResponseEventServerConnectedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventServerConnectedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventServerConnectedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventServerConnectedTypeServerConnected:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionUpdated struct {
|
||||
Properties Permission `json:"properties,required"`
|
||||
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
||||
JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventPermissionUpdated]
|
||||
type eventListResponseEventPermissionUpdatedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionUpdated) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventPermissionUpdatedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventPermissionUpdatedTypePermissionUpdated EventListResponseEventPermissionUpdatedType = "permission.updated"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventPermissionUpdatedTypePermissionUpdated:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionReplied struct {
|
||||
Properties EventListResponseEventPermissionRepliedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventPermissionRepliedType `json:"type,required"`
|
||||
JSON eventListResponseEventPermissionRepliedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionRepliedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventPermissionReplied]
|
||||
type eventListResponseEventPermissionRepliedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionReplied) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionRepliedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventPermissionReplied) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventPermissionRepliedProperties struct {
|
||||
PermissionID string `json:"permissionID,required"`
|
||||
Response string `json:"response,required"`
|
||||
SessionID string `json:"sessionID,required"`
|
||||
JSON eventListResponseEventPermissionRepliedPropertiesJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventPermissionRepliedPropertiesJSON contains the JSON metadata
|
||||
// for the struct [EventListResponseEventPermissionRepliedProperties]
|
||||
type eventListResponseEventPermissionRepliedPropertiesJSON struct {
|
||||
PermissionID apijson.Field
|
||||
Response apijson.Field
|
||||
SessionID apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventPermissionRepliedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventPermissionRepliedPropertiesJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type EventListResponseEventPermissionRepliedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventPermissionRepliedTypePermissionReplied EventListResponseEventPermissionRepliedType = "permission.replied"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventPermissionRepliedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventPermissionRepliedTypePermissionReplied:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventSessionUpdated struct {
|
||||
Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventSessionUpdatedType `json:"type,required"`
|
||||
@@ -1167,45 +1229,6 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventServerConnected struct {
|
||||
Properties interface{} `json:"properties,required"`
|
||||
Type EventListResponseEventServerConnectedType `json:"type,required"`
|
||||
JSON eventListResponseEventServerConnectedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventServerConnectedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventServerConnected]
|
||||
type eventListResponseEventServerConnectedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventServerConnectedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventServerConnected) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventServerConnectedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventServerConnectedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventServerConnectedTypeServerConnected:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdated struct {
|
||||
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
|
||||
@@ -1353,20 +1376,21 @@ const (
|
||||
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
||||
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
|
||||
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
||||
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
||||
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
||||
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
|
||||
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
|
||||
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
||||
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
|
||||
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
|
||||
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
|
||||
)
|
||||
|
||||
func (r EventListResponseType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
|
||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypeFileEdited, EventListResponseTypeServerConnected, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -133,11 +133,13 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa
|
||||
// Fallback to json serialization if none of the serialization functions that we expect
|
||||
// to see is present.
|
||||
if body != nil && !hasSerializationFunc {
|
||||
content, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(true)
|
||||
if err := enc.Encode(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader = bytes.NewBuffer(content)
|
||||
reader = buf
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, u, nil)
|
||||
|
||||
@@ -24,7 +24,8 @@ import (
|
||||
// automatically. You should not instantiate this service directly, and instead use
|
||||
// the [NewSessionService] method instead.
|
||||
type SessionService struct {
|
||||
Options []option.RequestOption
|
||||
Options []option.RequestOption
|
||||
Permissions *SessionPermissionService
|
||||
}
|
||||
|
||||
// NewSessionService generates a new service that applies the given options to each
|
||||
@@ -33,6 +34,7 @@ type SessionService struct {
|
||||
func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
|
||||
r = &SessionService{}
|
||||
r.Options = opts
|
||||
r.Permissions = NewSessionPermissionService(opts...)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,6 +102,22 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
|
||||
return
|
||||
}
|
||||
|
||||
// Get a message from a session
|
||||
func (r *SessionService) Message(ctx context.Context, id string, messageID string, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
if messageID == "" {
|
||||
err = errors.New("missing required messageID parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/message/%s", id, messageID)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// List messages for a session
|
||||
func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
@@ -172,6 +190,113 @@ func (r *SessionService) Unshare(ctx context.Context, id string, opts ...option.
|
||||
return
|
||||
}
|
||||
|
||||
type AgentPart struct {
|
||||
ID string `json:"id,required"`
|
||||
MessageID string `json:"messageID,required"`
|
||||
Name string `json:"name,required"`
|
||||
SessionID string `json:"sessionID,required"`
|
||||
Type AgentPartType `json:"type,required"`
|
||||
Source AgentPartSource `json:"source"`
|
||||
JSON agentPartJSON `json:"-"`
|
||||
}
|
||||
|
||||
// agentPartJSON contains the JSON metadata for the struct [AgentPart]
|
||||
type agentPartJSON struct {
|
||||
ID apijson.Field
|
||||
MessageID apijson.Field
|
||||
Name apijson.Field
|
||||
SessionID apijson.Field
|
||||
Type apijson.Field
|
||||
Source apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AgentPart) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r agentPartJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r AgentPart) implementsPart() {}
|
||||
|
||||
type AgentPartType string
|
||||
|
||||
const (
|
||||
AgentPartTypeAgent AgentPartType = "agent"
|
||||
)
|
||||
|
||||
func (r AgentPartType) IsKnown() bool {
|
||||
switch r {
|
||||
case AgentPartTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AgentPartSource struct {
|
||||
End int64 `json:"end,required"`
|
||||
Start int64 `json:"start,required"`
|
||||
Value string `json:"value,required"`
|
||||
JSON agentPartSourceJSON `json:"-"`
|
||||
}
|
||||
|
||||
// agentPartSourceJSON contains the JSON metadata for the struct [AgentPartSource]
|
||||
type agentPartSourceJSON struct {
|
||||
End apijson.Field
|
||||
Start apijson.Field
|
||||
Value apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *AgentPartSource) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r agentPartSourceJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type AgentPartInputParam struct {
|
||||
Name param.Field[string] `json:"name,required"`
|
||||
Type param.Field[AgentPartInputType] `json:"type,required"`
|
||||
ID param.Field[string] `json:"id"`
|
||||
Source param.Field[AgentPartInputSourceParam] `json:"source"`
|
||||
}
|
||||
|
||||
func (r AgentPartInputParam) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
func (r AgentPartInputParam) implementsSessionChatParamsPartUnion() {}
|
||||
|
||||
type AgentPartInputType string
|
||||
|
||||
const (
|
||||
AgentPartInputTypeAgent AgentPartInputType = "agent"
|
||||
)
|
||||
|
||||
func (r AgentPartInputType) IsKnown() bool {
|
||||
switch r {
|
||||
case AgentPartInputTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AgentPartInputSourceParam struct {
|
||||
End param.Field[int64] `json:"end,required"`
|
||||
Start param.Field[int64] `json:"start,required"`
|
||||
Value param.Field[string] `json:"value,required"`
|
||||
}
|
||||
|
||||
func (r AgentPartInputSourceParam) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type AssistantMessage struct {
|
||||
ID string `json:"id,required"`
|
||||
Cost float64 `json:"cost,required"`
|
||||
@@ -837,11 +962,13 @@ type Part struct {
|
||||
Cost float64 `json:"cost"`
|
||||
Filename string `json:"filename"`
|
||||
// This field can have the runtime type of [[]string].
|
||||
Files interface{} `json:"files"`
|
||||
Hash string `json:"hash"`
|
||||
Mime string `json:"mime"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
Source FilePartSource `json:"source"`
|
||||
Files interface{} `json:"files"`
|
||||
Hash string `json:"hash"`
|
||||
Mime string `json:"mime"`
|
||||
Name string `json:"name"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
// This field can have the runtime type of [FilePartSource], [AgentPartSource].
|
||||
Source interface{} `json:"source"`
|
||||
// This field can have the runtime type of [ToolPartState].
|
||||
State interface{} `json:"state"`
|
||||
Synthetic bool `json:"synthetic"`
|
||||
@@ -868,6 +995,7 @@ type partJSON struct {
|
||||
Files apijson.Field
|
||||
Hash apijson.Field
|
||||
Mime apijson.Field
|
||||
Name apijson.Field
|
||||
Snapshot apijson.Field
|
||||
Source apijson.Field
|
||||
State apijson.Field
|
||||
@@ -898,13 +1026,13 @@ func (r *Part) UnmarshalJSON(data []byte) (err error) {
|
||||
// for more type safety.
|
||||
//
|
||||
// Possible runtime types of the union are [TextPart], [FilePart], [ToolPart],
|
||||
// [StepStartPart], [StepFinishPart], [SnapshotPart], [PartPatchPart].
|
||||
// [StepStartPart], [StepFinishPart], [SnapshotPart], [PartPatchPart], [AgentPart].
|
||||
func (r Part) AsUnion() PartUnion {
|
||||
return r.union
|
||||
}
|
||||
|
||||
// Union satisfied by [TextPart], [FilePart], [ToolPart], [StepStartPart],
|
||||
// [StepFinishPart], [SnapshotPart] or [PartPatchPart].
|
||||
// [StepFinishPart], [SnapshotPart], [PartPatchPart] or [AgentPart].
|
||||
type PartUnion interface {
|
||||
implementsPart()
|
||||
}
|
||||
@@ -948,6 +1076,11 @@ func init() {
|
||||
Type: reflect.TypeOf(PartPatchPart{}),
|
||||
DiscriminatorValue: "patch",
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(AgentPart{}),
|
||||
DiscriminatorValue: "agent",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1007,11 +1140,12 @@ const (
|
||||
PartTypeStepFinish PartType = "step-finish"
|
||||
PartTypeSnapshot PartType = "snapshot"
|
||||
PartTypePatch PartType = "patch"
|
||||
PartTypeAgent PartType = "agent"
|
||||
)
|
||||
|
||||
func (r PartType) IsKnown() bool {
|
||||
switch r {
|
||||
case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish, PartTypeSnapshot, PartTypePatch:
|
||||
case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish, PartTypeSnapshot, PartTypePatch, PartTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -2012,6 +2146,29 @@ func (r userMessageTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionMessageResponse struct {
|
||||
Info Message `json:"info,required"`
|
||||
Parts []Part `json:"parts,required"`
|
||||
JSON sessionMessageResponseJSON `json:"-"`
|
||||
}
|
||||
|
||||
// sessionMessageResponseJSON contains the JSON metadata for the struct
|
||||
// [SessionMessageResponse]
|
||||
type sessionMessageResponseJSON struct {
|
||||
Info apijson.Field
|
||||
Parts apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *SessionMessageResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r sessionMessageResponseJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionMessagesResponse struct {
|
||||
Info Message `json:"info,required"`
|
||||
Parts []Part `json:"parts,required"`
|
||||
@@ -2039,8 +2196,8 @@ type SessionChatParams struct {
|
||||
ModelID param.Field[string] `json:"modelID,required"`
|
||||
Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
|
||||
ProviderID param.Field[string] `json:"providerID,required"`
|
||||
Agent param.Field[string] `json:"agent"`
|
||||
MessageID param.Field[string] `json:"messageID"`
|
||||
Mode param.Field[string] `json:"mode"`
|
||||
System param.Field[string] `json:"system"`
|
||||
Tools param.Field[map[string]bool] `json:"tools"`
|
||||
}
|
||||
@@ -2054,7 +2211,8 @@ type SessionChatParamsPart struct {
|
||||
ID param.Field[string] `json:"id"`
|
||||
Filename param.Field[string] `json:"filename"`
|
||||
Mime param.Field[string] `json:"mime"`
|
||||
Source param.Field[FilePartSourceUnionParam] `json:"source"`
|
||||
Name param.Field[string] `json:"name"`
|
||||
Source param.Field[interface{}] `json:"source"`
|
||||
Synthetic param.Field[bool] `json:"synthetic"`
|
||||
Text param.Field[string] `json:"text"`
|
||||
Time param.Field[interface{}] `json:"time"`
|
||||
@@ -2067,7 +2225,7 @@ func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
|
||||
|
||||
func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
|
||||
|
||||
// Satisfied by [TextPartInputParam], [FilePartInputParam],
|
||||
// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam],
|
||||
// [SessionChatParamsPart].
|
||||
type SessionChatParamsPartUnion interface {
|
||||
implementsSessionChatParamsPartUnion()
|
||||
@@ -2076,13 +2234,14 @@ type SessionChatParamsPartUnion interface {
|
||||
type SessionChatParamsPartsType string
|
||||
|
||||
const (
|
||||
SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
|
||||
SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
|
||||
SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
|
||||
SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
|
||||
SessionChatParamsPartsTypeAgent SessionChatParamsPartsType = "agent"
|
||||
)
|
||||
|
||||
func (r SessionChatParamsPartsType) IsKnown() bool {
|
||||
switch r {
|
||||
case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile:
|
||||
case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeAgent:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -129,8 +129,8 @@ func TestSessionChatWithOptionalParams(t *testing.T) {
|
||||
}),
|
||||
}}),
|
||||
ProviderID: opencode.F("providerID"),
|
||||
Agent: opencode.F("agent"),
|
||||
MessageID: opencode.F("msg"),
|
||||
Mode: opencode.F("mode"),
|
||||
System: opencode.F("system"),
|
||||
Tools: opencode.F(map[string]bool{
|
||||
"foo": true,
|
||||
@@ -176,6 +176,32 @@ func TestSessionInit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionMessage(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.Session.Message(
|
||||
context.TODO(),
|
||||
"id",
|
||||
"messageID",
|
||||
)
|
||||
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 TestSessionMessages(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
|
||||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
// SessionPermissionService 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 [NewSessionPermissionService] method instead.
|
||||
type SessionPermissionService struct {
|
||||
Options []option.RequestOption
|
||||
}
|
||||
|
||||
// NewSessionPermissionService 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 NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermissionService) {
|
||||
r = &SessionPermissionService{}
|
||||
r.Options = opts
|
||||
return
|
||||
}
|
||||
|
||||
// Respond to a permission request
|
||||
func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
if id == "" {
|
||||
err = errors.New("missing required id parameter")
|
||||
return
|
||||
}
|
||||
if permissionID == "" {
|
||||
err = errors.New("missing required permissionID parameter")
|
||||
return
|
||||
}
|
||||
path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ID string `json:"id,required"`
|
||||
MessageID string `json:"messageID,required"`
|
||||
Metadata map[string]interface{} `json:"metadata,required"`
|
||||
SessionID string `json:"sessionID,required"`
|
||||
Time PermissionTime `json:"time,required"`
|
||||
Title string `json:"title,required"`
|
||||
Type string `json:"type,required"`
|
||||
CallID string `json:"callID"`
|
||||
Pattern string `json:"pattern"`
|
||||
JSON permissionJSON `json:"-"`
|
||||
}
|
||||
|
||||
// permissionJSON contains the JSON metadata for the struct [Permission]
|
||||
type permissionJSON struct {
|
||||
ID apijson.Field
|
||||
MessageID apijson.Field
|
||||
Metadata apijson.Field
|
||||
SessionID apijson.Field
|
||||
Time apijson.Field
|
||||
Title apijson.Field
|
||||
Type apijson.Field
|
||||
CallID apijson.Field
|
||||
Pattern apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *Permission) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r permissionJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type PermissionTime struct {
|
||||
Created float64 `json:"created,required"`
|
||||
JSON permissionTimeJSON `json:"-"`
|
||||
}
|
||||
|
||||
// permissionTimeJSON contains the JSON metadata for the struct [PermissionTime]
|
||||
type permissionTimeJSON struct {
|
||||
Created apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *PermissionTime) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r permissionTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type SessionPermissionRespondParams struct {
|
||||
Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
|
||||
}
|
||||
|
||||
func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type SessionPermissionRespondParamsResponse string
|
||||
|
||||
const (
|
||||
SessionPermissionRespondParamsResponseOnce SessionPermissionRespondParamsResponse = "once"
|
||||
SessionPermissionRespondParamsResponseAlways SessionPermissionRespondParamsResponse = "always"
|
||||
SessionPermissionRespondParamsResponseReject SessionPermissionRespondParamsResponse = "reject"
|
||||
)
|
||||
|
||||
func (r SessionPermissionRespondParamsResponse) IsKnown() bool {
|
||||
switch r {
|
||||
case SessionPermissionRespondParamsResponseOnce, SessionPermissionRespondParamsResponseAlways, SessionPermissionRespondParamsResponseReject:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
)
|
||||
|
||||
func TestConfigGet(t *testing.T) {
|
||||
func TestSessionPermissionRespond(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 {
|
||||
@@ -25,7 +25,14 @@ func TestConfigGet(t *testing.T) {
|
||||
client := opencode.NewClient(
|
||||
option.WithBaseURL(baseURL),
|
||||
)
|
||||
_, err := client.Config.Get(context.TODO())
|
||||
_, err := client.Session.Permissions.Respond(
|
||||
context.TODO(),
|
||||
"id",
|
||||
"permissionID",
|
||||
opencode.SessionPermissionRespondParams{
|
||||
Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var apierr *opencode.Error
|
||||
if errors.As(err, &apierr) {
|
||||
@@ -39,6 +39,22 @@ func (r *TuiService) AppendPrompt(ctx context.Context, body TuiAppendPromptParam
|
||||
return
|
||||
}
|
||||
|
||||
// Clear the prompt
|
||||
func (r *TuiService) ClearPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/clear-prompt"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Execute a TUI command (e.g. switch_agent)
|
||||
func (r *TuiService) ExecuteCommand(ctx context.Context, body TuiExecuteCommandParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/execute-command"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Open the help dialog
|
||||
func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
@@ -47,6 +63,38 @@ func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption)
|
||||
return
|
||||
}
|
||||
|
||||
// Open the model dialog
|
||||
func (r *TuiService) OpenModels(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/open-models"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Open the session dialog
|
||||
func (r *TuiService) OpenSessions(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/open-sessions"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Open the theme dialog
|
||||
func (r *TuiService) OpenThemes(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/open-themes"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
// Submit the prompt
|
||||
func (r *TuiService) SubmitPrompt(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) {
|
||||
opts = append(r.Options[:], opts...)
|
||||
path := "tui/submit-prompt"
|
||||
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
|
||||
return
|
||||
}
|
||||
|
||||
type TuiAppendPromptParams struct {
|
||||
Text param.Field[string] `json:"text,required"`
|
||||
}
|
||||
@@ -54,3 +102,11 @@ type TuiAppendPromptParams struct {
|
||||
func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
type TuiExecuteCommandParams struct {
|
||||
Command param.Field[string] `json:"command,required"`
|
||||
}
|
||||
|
||||
func (r TuiExecuteCommandParams) MarshalJSON() (data []byte, err error) {
|
||||
return apijson.MarshalRoot(r)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,52 @@ func TestTuiAppendPrompt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuiClearPrompt(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.Tui.ClearPrompt(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 TestTuiExecuteCommand(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.Tui.ExecuteCommand(context.TODO(), opencode.TuiExecuteCommandParams{
|
||||
Command: opencode.F("command"),
|
||||
})
|
||||
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 TestTuiOpenHelp(t *testing.T) {
|
||||
t.Skip("skipped: tests are disabled for the time being")
|
||||
baseURL := "http://localhost:4010"
|
||||
@@ -58,3 +104,91 @@ func TestTuiOpenHelp(t *testing.T) {
|
||||
t.Fatalf("err should be nil: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTuiOpenModels(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.Tui.OpenModels(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 TestTuiOpenSessions(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.Tui.OpenSessions(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 TestTuiOpenThemes(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.Tui.OpenThemes(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 TestTuiSubmitPrompt(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.Tui.SubmitPrompt(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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.0.0",
|
||||
"version": "0.3.133",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
".": {
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -4,16 +4,11 @@ const dir = new URL("..", import.meta.url).pathname
|
||||
process.chdir(dir)
|
||||
|
||||
import { $ } from "bun"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
console.log("=== Generating JS SDK ===")
|
||||
console.log()
|
||||
|
||||
import { createClient } from "@hey-api/openapi-ts"
|
||||
|
||||
await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true })
|
||||
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
|
||||
await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode"))
|
||||
|
||||
await createClient({
|
||||
input: "./openapi.json",
|
||||
@@ -36,6 +31,4 @@ await createClient({
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await $`rm -rf dist`
|
||||
await $`bun tsc`
|
||||
await $`bun prettier --write src/gen`
|
||||
|
||||